Skip to content

CORS & Sanctum (SPA authentication)

ExchangePro uses Laravel Sanctum in stateful (cookie) mode. The Nuxt app and API must be configured so cookies and CSRF tokens work across origins.

How authentication works

mermaid
sequenceDiagram
  participant Browser
  participant Nuxt
  participant API as Laravel API

  Browser->>Nuxt: Open /signin
  Nuxt->>API: GET /sanctum/csrf-cookie
  API-->>Browser: Set XSRF-TOKEN cookie
  Nuxt->>API: POST /api/auth/signin (with X-XSRF-TOKEN)
  API-->>Browser: Set session cookie
  Nuxt->>API: GET /api/user (authenticated)
  1. Frontend fetches CSRF cookie from {API_URL}/sanctum/csrf-cookie.
  2. Login POST includes X-XSRF-TOKEN header (must match the XSRF-TOKEN cookie).
  3. Laravel session cookie is stored for the shared cookie domain.
  4. Subsequent API calls send cookies (credentials: include).

CORS configuration

Origins are configured in backend/.env, not hardcoded in config/cors.php.

By default, FRONTEND_URL is the only allowed origin. For multiple frontends (e.g. www + apex, or local + LAN), set:

ini
FRONTEND_URL=http://localhost:4000

# Optional - full URLs with scheme, comma-separated
CORS_ALLOWED_ORIGINS=http://localhost:4000,http://127.0.0.1:4000,https://yourdomain.com,https://www.yourdomain.com

If CORS_ALLOWED_ORIGINS is omitted, Laravel uses FRONTEND_URL only.

Rules

  • Each entry must be a full origin: scheme + host + port (e.g. https://yourdomain.com).
  • Do not use * when supports_credentials is true.
  • Match the URL users type in the browser (including www if you use it).

After changing .env, clear config cache:

bash
php artisan config:clear
php artisan config:cache

Session cookies (cross-subdomain setup)

When the API and Nuxt site run on different subdomains (recommended production layout), you must share session and CSRF cookies across the root domain.

URLRole
https://exchangepro.xorinlab.comNuxt frontend
https://api-exchangepro.xorinlab.comLaravel API

Backend .env (production):

ini
APP_URL=https://api-exchangepro.xorinlab.com
FRONTEND_URL=https://exchangepro.xorinlab.com

SESSION_DOMAIN=.xorinlab.com
SESSION_SECURE_COOKIE=true
SESSION_SAME_SITE=lax

Replace xorinlab.com with your own root domain (leading dot is required).

Why SESSION_DOMAIN matters

Without it, cookies are scoped only to api.yourdomain.com. The frontend JavaScript on yourdomain.com cannot read the XSRF-TOKEN cookie, so login POSTs fail with 419 CSRF token mismatch even though CORS headers look correct.

After changing session settings:

  1. Run php artisan config:cache on the server.
  2. Clear browser cookies for your domain (or use a private window).
  3. Retry sign-in - confirm GET /sanctum/csrf-cookie runs before POST /api/auth/signin.

Local development

Leave session domain unset for localhost:

ini
SESSION_DOMAIN=null
SESSION_SECURE_COOKIE=
SESSION_SAME_SITE=lax

On localhost, cookies for localhost (no port in the cookie domain) are usually shared between :4000 (Nuxt) and :8000 (API).

Sanctum stateful domains

File: backend/config/sanctum.php

Stateful domains are built automatically from:

  • Hardcoded local dev hosts (localhost, localhost:4000, 127.0.0.1, etc.)
  • The host (and port, if any) parsed from FRONTEND_URL in .env

Example: FRONTEND_URL=https://exchangepro.xorinlab.com adds exchangepro.xorinlab.com to the stateful list.

You normally do not need a separate env variable - set FRONTEND_URL correctly and run php artisan config:cache.

Same-site deployment tips

SetupCookie notes
API api.domain.com + app domain.comRequired: SESSION_DOMAIN=.domain.com, SESSION_SECURE_COOKIE=true
Same origin /api reverse proxySimplest cookie behavior; SESSION_DOMAIN can stay null
Different registrable domains entirelyCookie auth is unreliable; consider same root domain or a reverse proxy

Admin API middleware

Admin routes use:

text
middleware: auth:sanctum, admin, lastSeen

The admin middleware checks that the logged-in user has role = admin (works with Sanctum cookie sessions).

If admin requests return 401/403, see Troubleshooting.

HTTPS requirement

Production must use HTTPS on both frontend and API so browsers accept Secure cookies when SESSION_SECURE_COOKIE=true.

Quick test

bash
# CSRF cookie - check Set-Cookie includes Domain=.yourdomain.com
curl -I -c cookies.txt \
  -H "Origin: https://exchangepro.xorinlab.com" \
  https://api-exchangepro.xorinlab.com/sanctum/csrf-cookie

# Login (replace token from XSRF-TOKEN cookie)
curl -c cookies.txt -b cookies.txt \
  -H "Origin: https://exchangepro.xorinlab.com" \
  -H "X-XSRF-TOKEN: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@example.com","password":"secret"}' \
  https://api-exchangepro.xorinlab.com/api/auth/signin

Browser-based login via the UI is the recommended test.

Need help? support@xorinlab.com · Website: xorinlab.com