Cross-Origin Resource Sharing (CORS)

Comprehensive guide to configuring CORS for the Klinik Gunung Health Screening System with Laravel backend and Next.js frontend

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:3000 in development)
  • Backend: Laravel API (typically localhost:8000 in 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_origins configuration
  • 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

  1. Open Network tab in browser dev tools
  2. Make a cross-origin request
  3. Check for OPTIONS preflight request
  4. 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

  1. Clear Laravel Cache
    php artisan config:clear
    php artisan cache:clear
    
  2. Check Middleware Order Ensure CORS middleware is registered before other middleware
  3. Verify Route Matching Ensure your routes match the paths pattern

Browser Still Blocking Requests

  1. Disable Browser Cache Hard refresh (Ctrl+F5) or open incognito mode
  2. Check for Mixed Content Ensure all resources are served over HTTPS
  3. 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.