Sanctum solves a narrower problem than Passport, deliberately: authenticating a first-party SPA or mobile app against your own API, without building a full OAuth2 server for a case that doesn't need one. For a SPA served from the same top-level domain as its API, Sanctum's cookie-based approach means you never touch a bearer token in JavaScript at all — the browser handles the cookie, and CSRF protection covers the gap that pure cookie auth would otherwise leave open.
Cookies, not tokens, for same-site SPAs
The SPA authentication flow doesn't issue an API token the frontend stores and attaches to requests. Instead, the SPA hits `/sanctum/csrf-cookie` first to get a CSRF cookie, then logs in through your normal session-based login route, and every subsequent request rides on the same encrypted, HttpOnly session cookie the browser already manages. There's no token to accidentally log, no token sitting in localStorage where an XSS payload can read it — the browser enforces same-origin cookie access, which is a meaningfully stronger default than "store a JWT in localStorage and attach it manually."
// config/sanctum.php
'stateful' => explode(',', env(
'SANCTUM_STATEFUL_DOMAINS',
'localhost,localhost:3000,app.example.com'
)),
// config/cors.php — must allow credentials for cookie auth to work
'supports_credentials' => true,
// Frontend: fetch CSRF cookie before the first stateful request
await axios.get('/sanctum/csrf-cookie');
await axios.post('/login', { email, password });
// Every request after this carries the session cookie automatically
const { data } = await axios.get('/api/user');
The stateful domains list is the security boundary
Sanctum decides whether a request should be treated as "cookie-authenticated, CSRF-protected" or "token-authenticated" based on whether the request's origin matches an entry in `stateful` domains. Anything not in that list falls back to token auth if a bearer token is present. Getting this list wrong — including a domain you don't control, or forgetting to include a staging subdomain — either breaks auth for legitimate frontends or, worse, extends cookie-based trust to an origin that shouldn't have it. This config is the actual access control decision, not a formality.
supports_credentials => true in CORS and a matching origin in Sanctum's stateful domains are both required together — one without the other either breaks the cookie flow or silently falls through to token auth in a way that's confusing to debug. If cookie auth "isn't working" in a new environment, check both configs before assuming the frontend code is wrong.
Mobile and third-party clients still use tokens
The cookie flow is specifically for same-site, first-party SPAs. A mobile app, or any client that isn't running inside a browser sharing your domain's cookie jar, uses Sanctum's other mode: personal access tokens, created via `$user->createToken('device-name')` and sent as a normal `Authorization: Bearer` header. Both modes coexist in the same app — Sanctum picks the right one per request based on whether a valid session cookie or a bearer token is present — so a single API can serve a web SPA over cookies and a mobile app over tokens without maintaining two separate auth systems.
Sanctum tokens support "abilities" — string identifiers checked via $token->can('posts:update') — which lets you issue a mobile app token that can only read, not write, without needing an OAuth2 scope negotiation flow. It's a lighter-weight mechanism than Passport's scopes, appropriate for first-party clients where you control both ends, not for handing access to an untrusted third party.
Wrapping up
Sanctum's cookie-based SPA auth is the right choice specifically because it avoids the token-in-JavaScript problem for the common case of a first-party frontend on the same top-level domain — CSRF and same-site cookies do the security work a hand-rolled token store would otherwise need to reinvent. Reach for personal access tokens for mobile and third-party clients that can't share your cookie jar, and if you find yourself needing real OAuth2 grant types or scoped third-party access, that's the point where Passport, not Sanctum, is the right tool.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.