Appearance
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)- Frontend fetches CSRF cookie from
{API_URL}/sanctum/csrf-cookie. - Login POST includes
X-XSRF-TOKENheader (must match theXSRF-TOKENcookie). - Laravel session cookie is stored for the shared cookie domain.
- 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.comIf 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
*whensupports_credentialsistrue. - Match the URL users type in the browser (including
wwwif you use it).
After changing .env, clear config cache:
bash
php artisan config:clear
php artisan config:cacheSession 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.
| URL | Role |
|---|---|
https://exchangepro.xorinlab.com | Nuxt frontend |
https://api-exchangepro.xorinlab.com | Laravel 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=laxReplace 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:
- Run
php artisan config:cacheon the server. - Clear browser cookies for your domain (or use a private window).
- Retry sign-in - confirm
GET /sanctum/csrf-cookieruns beforePOST /api/auth/signin.
Local development
Leave session domain unset for localhost:
ini
SESSION_DOMAIN=null
SESSION_SECURE_COOKIE=
SESSION_SAME_SITE=laxOn 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_URLin.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
| Setup | Cookie notes |
|---|---|
API api.domain.com + app domain.com | Required: SESSION_DOMAIN=.domain.com, SESSION_SECURE_COOKIE=true |
Same origin /api reverse proxy | Simplest cookie behavior; SESSION_DOMAIN can stay null |
| Different registrable domains entirely | Cookie auth is unreliable; consider same root domain or a reverse proxy |
Admin API middleware
Admin routes use:
text
middleware: auth:sanctum, admin, lastSeenThe 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/signinBrowser-based login via the UI is the recommended test.