A FIELD GUIDE TO LARAVEL · VOLUME II · THE ECOSYSTEM
ENTRY II.20 · API AUTHENTICATION
Sanctum
Laravel\Sanctum · Package · first recorded January 2020, as Airlock; introduced with Laravel 7
Authentication for SPAs, mobile apps and plain token APIs, without OAuth. Released as Airlock in January 2020 and renamed Sanctum at 2.0 that March. Your own SPA gets the session cookie; everyone else gets a hashed bearer token with abilities.
Specification
Kind Package, in-process. A guard, its middleware and one table
Interface The auth:sanctum guard: cookie if present, else the Authorization header
Client A first-party SPA on the same top-level domain, a mobile app, a third party
Installed by php artisan install:api
Voice sanctum:prune-expired
Scales by One row per token in personal_access_tokens; sessions as for any web app
Range Cloud: yes. Nothing resident beyond the app
Forge, VPS: yes, the same way
Vapor: yes. Vapor defaults sessions to the cookie driver
Neighbours
Passport is the OAuth2 server; Sanctum deliberately is not, and issues tokens to your own users rather than to other apps. Fortify is often confused with it: Fortify provides the login and registration routes, while Sanctum authenticates requests once the user is signed in.
Field marks
SANCTUM_STATEFUL_DOMAINS
config/sanctum.php
HasApiTokens on User
middleware('auth:sanctum')
Fig. II.20.1 · Two doors, one guard
Parts
1 SPA. Same top-level domain, a subdomain is fine. It calls /sanctum/csrf-cookie first, then POSTs to /login.
2 Session cookie. No token at all: Laravel's web guard and CSRF protection, so XSS cannot leak a credential. statefulApi() turns it on.
3 Third party. A mobile app or another server sends Authorization: Bearer with a token made by createToken().
4 Tokens table. personal_access_tokens. The token is SHA-256 hashed; the plain text is shown once and never stored.
5 Guard. auth:sanctum checks for a session cookie first and the header second, so one route serves both doors.
6 Abilities. Scopes on a token, tokenCan('server:update'). Always true for first-party sessions, so policies read the same.
Hidden joinery
Tokens never expire by default; expiration in config or a third argument to createToken sets one, and sanctum:prune-expired sweeps the table. The stateful domain list must include the port if the URL has one. Sanctum::currentRequestHost() makes the request's own host stateful, so one config serves every environment.
Every claim checked against the Sanctum docs, source and changelog, and Packagist, on 2026-10-08.
TITLE
Sanctum · cookie, then token
SOURCES
laravel.com/docs/13.x/sanctum · github.com/laravel/sanctum · github.com/laravel/sanctum/blob/4.x/CHANGELOG.md · repo.packagist.org/p2/laravel/sanctum.json · laravel.com/docs/13.x/passport · laravel.com/docs/13.x/fortify · github.com/laravel/framework/blob/13.x/src/Illuminate/Foundation/Console/ApiInstallCommand.php · laravel.com/docs/7.x/releases · docs.vapor.build
STATUS
Verified · 2026-10-08
SHEET
II.20 of 48
REV
A · 2026-10-08
DRAWN
BS
FINISH
All faces, including the back