What two authentication mechanisms does Laravel Sanctum provide, and which one suits a first-party SPA versus a mobile app?
answer
- one guard, two credentials
- session cookie for your own SPA
- Bearer header for everything else
- config('sanctum.guard') defaults to ['web']
- personal_access_tokens table
basics
~20 sSanctum offers cookie-based session authentication for a first-party SPA on a stateful domain, and database-backed personal access tokens sent as a Bearer header for mobile apps and scripts. Your own SPA uses the session; the mobile app uses a token.
solid answer
~40 sSanctum solves two separate problems behind one `sanctum` guard, used as `auth:sanctum`. For your **first-party SPA** it issues no token at all: the SPA logs in through Laravel's ordinary `web` session and every later API call is authenticated by the session cookie, with CSRF protection. For **mobile apps and scripts** it issues **personal access tokens** with `createToken()`, stored hashed in `personal_access_tokens` and sent as `Authorization: Bearer ...`. On each request the guard first asks the guards in `config('sanctum.guard')` (`['web']` by default) for a session user and only then reads the Bearer header. For a pet-sitting marketplace, the owners' SPA uses the session and the sitters' mobile app stores a token. Neither mode is OAuth2.
code
php · 24 lines<?php
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Route;
use Illuminate\Validation\ValidationException;
// routes/api.php: one route serves the SPA (session) and the sitter app (token)
Route::get('/user', fn (Request $request) => $request->user())
->middleware('auth:sanctum');
// Sitter mobile app exchanges credentials for a token once
Route::post('/sitter/token', function (Request $request) {
$request->validate(['email' => 'required|email', 'password' => 'required', 'device_name' => 'required']);
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages(['email' => ['The provided credentials are incorrect.']]);
}
return ['token' => $user->createToken($request->device_name)->plainTextToken];
});go deeper
Name the two mechanisms and match each to a client: session cookie for your own SPA, personal access token for a mobile app or script, both behind auth:sanctum.
Explain the guard's order: session guards from config('sanctum.guard') first, Bearer token second, and why the SPA path needs no token at all.
Justify keeping tokens out of the browser, and describe how one API safely serves an SPA, a mobile app and customer scripts with different credentials.
Frame the boundary: Sanctum covers first-party clients; the moment third parties act on behalf of your users you are choosing to run an authorization server.
## What Sanctum is **Laravel Sanctum** is Laravel's first-party package for authenticating API requests without running an OAuth2 server. In Laravel 13 it is installed by `php artisan install:api`, which also creates `routes/api.php`. It adds a guard driver named `sanctum`, so a route protected with the `auth:sanctum` middleware accepts a request authenticated by **either** of Sanctum's two mechanisms. The docs are explicit that the two are separate features: an app may use only one of them. ## Mode 1: session cookies for a first-party SPA For a single-page application you ship yourself, Sanctum deliberately uses **no token**: - The SPA fetches a CSRF cookie, then posts credentials to an ordinary login route that signs the user in through the `web` guard. - Later calls to `/api/...` carry the **session cookie**; the API treats them as authenticated because the request comes from a domain listed as *stateful*. - The browser never holds a bearer credential that JavaScript can read, so an XSS bug cannot copy a token out of `localStorage`, and state-changing requests are CSRF-checked. - The SPA and the API must share a top-level domain; they may sit on different subdomains. ## Mode 2: personal access tokens for mobile apps and scripts A native mobile app has no browser cookie jar tied to your domain, and a script has no session at all. For them Sanctum issues **personal access tokens**: - The model uses the `Laravel\Sanctum\HasApiTokens` trait (the skeleton's `User` does not have it until you add it). - `$user->createToken('nina-phone', ['bookings:read'])` writes a row to `personal_access_tokens` holding a **SHA-256 hash** of the secret, the abilities and an optional expiry, and returns the plain-text value once. - The client sends `Authorization: Bearer <id>|<secret>` on every request. - Tokens are revoked by deleting their row; by default they never expire. ## How the `sanctum` guard decides 1. It walks the guards named in `config('sanctum.guard')` (default `['web']`). If one returns a user, that user is authenticated and, when the model uses `HasApiTokens`, gets a stand-in token object so `tokenCan()` keeps working. 2. Otherwise it reads the Bearer token from the request, finds the row, checks expiry and the hash, and attaches that token to the user. 3. If neither works, the guard returns no user and `auth:sanctum` answers **401** for a JSON request. ## Choosing per client | Client | Mechanism | Why | |---|---|---| | Your SPA on `app.` subdomain of your domain | Session cookie via `web` guard | No readable token in the browser; CSRF protection | | Your iOS/Android app | Personal access token | No shared cookie domain; token stored in the device keychain | | A customer's cron script | Personal access token with narrow abilities | Revocable per token | | A third-party developer's app acting for your users | Not Sanctum | That needs an OAuth2 server | ## Common confusions - Sanctum is **not** a slim OAuth2 server: no clients, no grants, no refresh tokens. - Tokens are **opaque random strings** looked up in the database, not signed JWTs. - The docs warn **against** using API tokens for your own SPA; issuing one at SPA login and keeping it in `localStorage` throws away the protection session mode gives you. - A single route can serve both clients, because `auth:sanctum` tries the session first and the token second. ## Getting it installed In a fresh Laravel 13 app the API layer is opt-in. `php artisan install:api`: 1. requires `laravel/sanctum` through Composer and publishes the `create_personal_access_tokens_table` migration, offering to run it; 2. copies a `routes/api.php` whose only route is `GET /user` guarded by `auth:sanctum`, and registers the file in `bootstrap/app.php`; 3. reminds you to add the `HasApiTokens` trait to `App\Models\User`. SPA mode needs one more step, `statefulApi()` in `bootstrap/app.php`, and a list of stateful domains; token mode needs nothing else. ## How to explain it in one breath "Sanctum is two things behind one guard. My own browser app never sees a token: it logs in with the normal session and calls the API with cookies. Everything that is not my browser app — the phone app, a customer's script — gets a hashed, revocable personal access token with abilities. If a third party ever needs to act for my users, that is OAuth, and Sanctum is not the tool."
- Can one route protected by auth:sanctum serve both the SPA and the mobile app?Yes. The `sanctum` guard first asks the guards in `config('sanctum.guard')` for a session user; a stateful SPA request is authenticated there. A mobile request has no session, so the guard falls through to the Bearer header and the token row. The controller simply calls `$request->user()` and does not need to know which path succeeded.
- Why does the documentation say not to use API tokens for your own SPA?A token must be stored somewhere JavaScript can read it, so any XSS bug can copy out a credential that stays valid until revoked. Session mode keeps the credential in an HttpOnly cookie that scripts cannot read, rotates it with the session, and CSRF-checks state-changing requests.
saying these in an interview costs you the question
- Sanctum is a lightweight OAuth2 server with clients and refresh tokens
- Sanctum SPA mode keeps a token in localStorage and sends it as Bearer
- Sanctum tokens are signed JWTs checked without a database lookup
- The mobile app should reuse the SPA's session cookie
- You must pick either SPA mode or tokens for the whole application