CORS Configuration Problems When Using Next.js on Vercel with Laravel Backend Through Ngrok Tunnel

Need Help with CORS Setup for My Student Project

I’m building a student event management platform using Next.js frontend and Laravel API. Here’s my setup:

  • Frontend: Next.js deployed on Vercel (HTTPS)
  • Backend: Laravel running locally, exposed via ngrok tunnel (HTTPS)

The issue started when I connected my Vercel app to the ngrok URL. I keep getting CORS errors no matter what I try.

Browser Error Messages

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at 'https://abc123-45-67-89-10.ngrok-free.app/sanctum/csrf-cookie'. 
(Reason: Credential is not supported if the CORS header 'Access-Control-Allow-Origin' is '*')

My Current Laravel CORS Configuration

<?php

return [
    'paths' => ['api/*', 'sanctum/csrf-cookie'],
    
    'allowed_methods' => ['*'],
    
    'allowed_origins' => [
        'http://localhost:3000',
        'https://my-student-project.vercel.app',
        'https://abc123-45-67-89-10.ngrok-free.app'
    ],
    
    'allowed_origins_patterns' => [
        'https://[a-z0-9-]+\.ngrok-free\.app',
    ],
    
    'allowed_headers' => ['*'],
    
    'supports_credentials' => true,
];

Environment Variables

SANCTUM_STATEFUL_DOMAINS=localhost:3000,my-student-project.vercel.app,abc123-45-67-89-10.ngrok-free.app
SESSION_SECURE_COOKIE=true
SESSION_DOMAIN=.ngrok-free.app

Sanctum Config

<?php

use Laravel\Sanctum\Sanctum;

return [
    'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', implode(',', [
        'localhost',
        'localhost:3000',
        '127.0.0.1',
        '127.0.0.1:8000',
        '::1',
        Sanctum::currentApplicationUrlWithPort(),
    ]))),
    
    'guard' => ['web'],
    'expiration' => null,
    'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
    'same-site' => 'none',
    
    'middleware' => [
        'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
        'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
        'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
    ],
];

Frontend HTTP Client Setup

// httpClient.js
import axios from 'axios';

// Helper function to get cookies
function extractCookie(cookieName) {
  const cookieString = `; ${document.cookie}`;
  const segments = cookieString.split(`; ${cookieName}=`);
  if (segments.length === 2) return decodeURIComponent(segments.pop().split(';').shift());
  return null;
}

// HTTP client configuration
const apiClient = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URL,
  withCredentials: true,
  timeout: 15000,
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
  },
});

// Add CSRF token to requests
apiClient.interceptors.request.use(
  (requestConfig) => {
    const csrfToken = extractCookie('XSRF-TOKEN');
    const sessionToken = extractCookie('session_token_admin');
    
    if (csrfToken) {
      requestConfig.headers['X-XSRF-TOKEN'] = csrfToken;
    }
    if (sessionToken) {
      requestConfig.headers['session_token_admin'] = sessionToken;
    }
    return requestConfig;
  },
  (requestError) => {
    return Promise.reject(requestError);
  }
);

export default apiClient;

I’ve been stuck on this for days. Can someone explain why this is happening and provide a working solution? I really need to understand the root cause of this CORS issue.

your session domain’s probably causing this. remove SESSION_DOMAIN=.ngrok-free.app from your env - cross-domain sessions are a pain with ngrok. also, test your csrf endpoint directly in the browser first to make sure ngrok’s serving it right.

interesting setup! have you tried testing cors without credentials first? set supports_credentials to false temporarily and see if basic requests work. also, try hardcoding your specific ngrok url in allowed_origins instead of using the pattern. wildcards + credentials can be tricky together!