Cross-Origin Resource Sharing (CORS)
Cross-Origin Resource Sharing (CORS)
This guide provides comprehensive information about configuring Cross-Origin Resource Sharing (CORS) for the Klinik Gunung Health Screening System. CORS is essential when your Next.js frontend (running on one domain/port) needs to communicate with your Laravel API backend (running on another domain/port).
Understanding CORS
CORS is a security feature implemented by web browsers that controls which web pages can request resources from servers on different domains. Without proper CORS configuration, browsers will block cross-origin requests for security reasons.
Why CORS Matters in Klinik Gunung
The Klinik Gunung system consists of:
- Frontend: Next.js application (typically
localhost:3000in development) - Backend: Laravel API (typically
localhost:8000in development)
When the frontend makes API calls to the backend, these are cross-origin requests that require CORS configuration.
Laravel CORS Configuration
Configuration File Location
The CORS configuration is located at config/cors.php in your Laravel application.
Current Configuration
<?php
return [
/*
|--------------------------------------------------------------------------
| CORS Configuration
|--------------------------------------------------------------------------
|
| This file defines the settings for Cross-Origin Resource Sharing (CORS).
| CORS allows your API to be accessed from different domains. You can
| customize these options based on your application's security policy.
|
*/
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
];
Configuration Options Explained
paths
Current: ['api/*', 'sanctum/csrf-cookie']
Specifies which routes should have CORS headers applied. The wildcard api/* covers all API routes.
allowed_methods
Current: ['*']
Defines which HTTP methods are allowed for cross-origin requests. The asterisk allows all methods (GET, POST, PUT, DELETE, PATCH, OPTIONS).
allowed_origins
Current: ['*']
⚠️ Security Note: Using ['*'] allows requests from any domain. This is acceptable for development but should be restricted in production.
Recommended Production Configuration:
'allowed_origins' => [
'https://klinikgunung.com',
'https://app.klinikgunung.com',
'https://admin.klinikgunung.com'
],
allowed_origins_patterns
Allows using regular expressions to match multiple origins. Useful for dynamic environments.
Example:
'allowed_origins_patterns' => [
'/^https:\/\/.*\.klinikgunung\.com$/',
'/^https:\/\/klinikgunung-.*\.vercel\.app$/'
],
allowed_headers
Current: ['*']
Specifies which HTTP headers can be included in requests. The asterisk allows all headers.
exposed_headers
Current: []
Defines which response headers should be exposed to the browser. Leave empty unless you need to expose custom headers.
max_age
Current: 0
Controls how long browsers can cache preflight request results. Setting to 0 disables caching. For better performance, set to a reasonable value like 86400 (24 hours).
supports_credentials
Current: true
Allows cross-origin requests to include credentials (cookies, authorization headers). Required for JWT authentication.
Environment-Specific Configuration
Development Environment
For local development, the current configuration with allowed_origins => ['*'] is acceptable since you're working on localhost.
Production Environment
Create environment-specific CORS configuration:
// config/cors.php
<?php
$environment = app()->environment();
if ($environment === 'production') {
return [
'paths' => ['api/*'],
'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
'allowed_origins' => [
'https://klinikgunung.com',
'https://app.klinikgunung.com',
],
'allowed_origins_patterns' => [],
'allowed_headers' => [
'Accept',
'Authorization',
'Content-Type',
'X-Requested-With',
],
'exposed_headers' => [],
'max_age' => 86400, // 24 hours
'supports_credentials' => true,
];
}
return [
// Development configuration
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
];
Frontend CORS Considerations
Next.js API Routes
If you're using Next.js API routes, configure CORS in your API handlers:
// pages/api/patients.js
import Cors from 'cors'
// Initialize CORS middleware
const cors = Cors({
methods: ['GET', 'POST', 'PUT', 'DELETE'],
origin: process.env.NODE_ENV === 'production'
? ['https://klinikgunung.com']
: ['http://localhost:3000'],
credentials: true,
})
// Helper method to wait for middleware
function runMiddleware(req, res, fn) {
return new Promise((resolve, reject) => {
fn(req, res, (result) => {
if (result instanceof Error) {
return reject(result)
}
return resolve(result)
})
})
}
export default async function handler(req, res) {
await runMiddleware(req, res, cors)
// Your API logic here
res.status(200).json({ message: 'Success' })
}
React Query Configuration
Ensure your TanStack Query client includes credentials:
// lib/api/client.js
import { QueryClient } from '@tanstack/react-query'
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
// Include credentials in requests
fetchOptions: {
credentials: 'include',
},
},
mutations: {
fetchOptions: {
credentials: 'include',
},
},
},
})
Common CORS Issues and Solutions
1. CORS Error: "No 'Access-Control-Allow-Origin' header"
Cause: CORS not configured or request from unauthorized origin.
Solution:
- Check
allowed_originsconfiguration - Clear browser cache
- Verify request origin matches allowed origins
2. CORS Error: "Method not allowed"
Cause: HTTP method not in allowed_methods.
Solution:
'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
3. CORS Error: "Credentials not allowed"
Cause: supports_credentials set to false but frontend sending credentials.
Solution:
'supports_credentials' => true,
4. Preflight Request Failing
Cause: Browser sending OPTIONS request that fails.
Solution:
- Ensure OPTIONS method is allowed
- Check that all required headers are permitted
- Verify the request path matches CORS paths
Testing CORS Configuration
Browser Developer Tools
- Open Network tab in browser dev tools
- Make a cross-origin request
- Check for OPTIONS preflight request
- Verify CORS headers in response
Command Line Testing
Test CORS with curl:
# Test preflight request
curl -X OPTIONS \
-H "Origin: http://localhost:3000" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type,Authorization" \
-v \
http://localhost:8000/api/patients
# Test actual request
curl -X GET \
-H "Origin: http://localhost:3000" \
-H "Authorization: Bearer your-token" \
-v \
http://localhost:8000/api/patients
Online CORS Testing Tools
Security Best Practices
1. Restrict Origins in Production
Never use allowed_origins => ['*'] in production:
// ✅ Good - Production
'allowed_origins' => [
'https://yourdomain.com',
'https://app.yourdomain.com'
],
// ❌ Bad - Never in production
'allowed_origins' => ['*'],
2. Limit Allowed Headers
Specify only necessary headers:
'allowed_headers' => [
'Accept',
'Authorization',
'Content-Type',
'X-Requested-With',
],
3. Use HTTPS in Production
Always use HTTPS URLs in allowed_origins:
'allowed_origins' => [
'https://klinikgunung.com', // ✅ HTTPS
'http://klinikgunung.com', // ❌ HTTP - insecure
],
4. Implement Rate Limiting
Combine CORS with Laravel's throttling middleware:
// routes/api.php
Route::middleware(['throttle:api', 'cors'])->group(function () {
// Your API routes
});
Troubleshooting
CORS Headers Not Appearing
- Clear Laravel Cache
php artisan config:clear php artisan cache:clear - Check Middleware Order Ensure CORS middleware is registered before other middleware
- Verify Route Matching
Ensure your routes match the
pathspattern
Browser Still Blocking Requests
- Disable Browser Cache Hard refresh (Ctrl+F5) or open incognito mode
- Check for Mixed Content Ensure all resources are served over HTTPS
- Verify Credentials Settings Both frontend and backend must agree on credentials
Advanced Configuration
Custom CORS Middleware
Create custom middleware for complex CORS logic:
// app/Http/Middleware/CustomCors.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class CustomCors
{
public function handle(Request $request, Closure $next)
{
$response = $next($request);
$allowedOrigins = [
'http://localhost:3000',
'https://klinikgunung.com',
];
$origin = $request->header('Origin');
if (in_array($origin, $allowedOrigins)) {
$response->headers->set('Access-Control-Allow-Origin', $origin);
$response->headers->set('Access-Control-Allow-Credentials', 'true');
$response->headers->set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
$response->headers->set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
}
return $response;
}
}
Dynamic Origin Resolution
For dynamic environments, resolve origins programmatically:
'allowed_origins' => function () {
$allowed = ['https://klinikgunung.com'];
// Add staging domains
if (app()->environment('staging')) {
$allowed[] = 'https://staging.klinikgunung.com';
}
// Add local development
if (app()->environment('local')) {
$allowed[] = 'http://localhost:3000';
$allowed[] = 'http://127.0.0.1:3000';
}
return $allowed;
},
Monitoring and Logging
Log CORS Requests
Enable CORS logging for debugging:
// In CORS service provider or middleware
if (config('app.debug')) {
Log::info('CORS Request', [
'origin' => $request->header('Origin'),
'method' => $request->method(),
'headers' => $request->headers->all()
]);
}
Monitor CORS Errors
Set up monitoring for CORS-related errors in your error tracking system.
This comprehensive CORS guide ensures secure and proper cross-origin communication between your Klinik Gunung frontend and backend. Always test CORS configuration thoroughly in both development and production environments.