SPA Authentication Development
This project uses a decoupled SPA architecture where Vue handles all routing and Laravel serves as a JSON API with Sanctum cookie-based authentication.
Auth Flow
- Vue app calls
GET /sanctum/csrf-cookieto get XSRF token - User submits login form →
POST /login(Fortify handles this) - Laravel returns session cookie + user data
- All subsequent API calls include the session cookie automatically (
withCredentials: true) - Protected routes use
auth:sanctummiddleware
Key Files
Backend
config/sanctum.php— Stateful domains, guards, CSRF middlewareconfig/cors.php— CORS paths (api/*, login, logout, register, etc.) withsupports_credentials: trueconfig/fortify.php— Auth features (registration, password reset, email verification)app/Providers/FortifyServiceProvider.php— Binds Fortify actions and custom LoginResponseapp/Http/Responses/LoginResponse.php— Returns JSON with user data for SPAapp/Http/Middleware/ApplyLocale.php— Sets locale from X-Locale headerbootstrap/app.php—statefulApi()middleware,apply_localealias
Frontend
stores/auth.js— Pinia store with login, register, logout, getCurrentUserservices/AuthService.ts— API calls for auth endpoints (login, register, forgot/reset password, etc.)router/index.js— Navigation guards checkingrequiresAuthandrequiresAbilityroute metaplugins/axios.js— Axios configured withwithCredentials, X-Locale header, base URL fromwindow.AppConfig
CORS Configuration
For the SPA to work, these .env values must match:
APP_URL=http://localhost:8000 SANCTUM_STATEFUL_DOMAINS=localhost:8000 SESSION_DOMAIN=localhost
Route Guards
Routes use meta fields for auth:
requiresAuth: true— Redirects to login if not authenticatedrequiresAbility: 'ability_name'— Checks user abilities via BouncerisPublicAuthPage: true— Redirects to dashboard if already authenticatedisOwner: true— Owner-only routes
Authorization
Uses Silber Bouncer with abilities:
- Abilities defined in
resources/app/stub/abilities.js(CREATE_USER, EDIT_USER, etc.) - Checked in Vue Router guards and sidebar menu visibility
- Backend uses
Gate::authorize()in controllers - Roles seeded in
database/seeders/BouncerSeeder.php


