Token Authentication
Sanctum authenticates an API two ways: hashed bearer tokens carrying abilities, or your own front end's session cookie.
$user->createToken(string $name, array $abilities = ['*'], ?DateTimeInterface $expiresAt = null): NewAccessTokenSanctum is Laravel's API authentication package, and php artisan install:api installs it. It answers two different questions with one guard, auth:sanctum.
Token mode
Add the Laravel\Sanctum\HasApiTokens trait to your User model; install:api prints a reminder but does not edit the file. $user->createToken('Phone', ['posts:write']) then returns a Laravel\Sanctum\NewAccessToken. Read its plainTextToken property once and show it to the user: the personal_access_tokens table stores a SHA-256 hash of the token, so it cannot be recovered later, only replaced. The client sends it as an Authorization: Bearer header.
Abilities
Abilities are the strings you passed to createToken, defaulting to ['*']. Test one with $user->tokenCan('posts:write') or tokenCant(). Sanctum also ships two middleware, CheckAbilities and CheckForAnyAbility, which you register as the abilities and ability aliases in bootstrap/app.php before naming them on a route.
Cookie mode
For your own single-page front end, call $middleware->statefulApi() in bootstrap/app.php. That prepends EnsureFrontendRequestsAreStateful to the api group. A request from a domain listed in config/sanctum.php's stateful array is then authenticated by the ordinary session cookie, after the front end has called /sanctum/csrf-cookie. No token is involved. Sanctum checks for that cookie first and only looks at the Authorization header when it is absent.
Revoking
A token is a database row, so revoking is a delete.
Example
<?php
// app/Http/Controllers/Api/TokenController.php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class TokenController extends Controller
{
/**
* Exchange credentials for a token. POST /api/tokens
*/
public function store(Request $request): JsonResponse
{
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required', 'string'],
'device_name' => ['required', 'string', 'max:100'],
]);
$user = User::query()->where('email', $credentials['email'])->first();
// One failure message for both cases: a different message for a
// missing email tells an attacker which addresses are registered.
if (! $user || ! Hash::check($credentials['password'], $user->password)) {
throw ValidationException::withMessages([
'email' => ['The provided credentials are incorrect.'],
]);
}
$token = $user->createToken(
$credentials['device_name'],
['posts:read', 'posts:write'],
);
// plainTextToken exists only here. The row holds a SHA-256 hash.
return response()->json([
'token' => $token->plainTextToken,
], 201);
}
/**
* Revoke the token that made this request. DELETE /api/tokens/current
*/
public function destroy(Request $request): Response
{
$request->user()->currentAccessToken()->delete();
return response()->noContent();
}
}
When to use it
- Let a user generate a personal access token in account settings for scripting against your API.
- Authenticate a mobile app that exchanges email and password for a long-lived token.
- Authenticate your own Vue or React front end with the session cookie instead of a token.
- Issue a token that may read posts but not delete them.
More examples
The trait, the aliases and the routes
Three files have to agree before a token works: the trait gives the model its tokens relationship, the aliases make the ability middleware nameable, and the guard on the route resolves the user. A missing trait produces a call to an undefined method createToken, not a 401.
<?php
// app/Models/User.php — install:api does not add this trait for you.
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}
// bootstrap/app.php — register Sanctum's ability middleware under short names.
use Laravel\Sanctum\Http\Middleware\CheckAbilities;
use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;
->withMiddleware(function (Middleware $middleware): void {
$middleware->alias([
'abilities' => CheckAbilities::class, // token must have ALL of them
'ability' => CheckForAnyAbility::class, // token must have ONE of them
]);
// Cookie authentication for your own SPA on a stateful domain.
$middleware->statefulApi();
})
// routes/api.php
Route::middleware(['auth:sanctum', 'abilities:posts:write'])->group(function (): void {
Route::post('/posts', [PostController::class, 'store']);
Route::delete('/posts/{post}', [PostController::class, 'destroy']);
});
Route::get('/posts', [PostController::class, 'index'])
->middleware(['auth:sanctum', 'ability:posts:read,posts:write']);
An account settings screen: list and revoke
What a personal access token screen needs. last_used_at is maintained by Sanctum on each authenticated request, which makes it the fastest way to spot a token no client has used for months.
<?php
// Tokens are models, so the toolkit applies. $user->tokens is a relationship
// added by HasApiTokens.
$tokens = $request->user()->tokens
->map(fn ($token): array => [
'id' => $token->id,
'name' => $token->name,
'abilities' => $token->abilities,
'last_used_at' => $token->last_used_at?->diffForHumans(),
'is_current' => $token->id === $request->user()->currentAccessToken()->id,
]);
// Revoke one, by id, scoped to the owner so a user cannot delete
// someone else's token by guessing a number.
$request->user()->tokens()->where('id', $tokenId)->delete();
// Revoke everything, for a "sign out everywhere" button.
$request->user()->tokens()->delete();
Expiring tokens
Use a short expiry for anything machine-issued, such as a CI key, and leave long-lived tokens to humans who can see and revoke them. Pruning is housekeeping only: an expired token stops authenticating the moment it expires, whether or not the row is still there.
<?php
// config/sanctum.php — minutes until a token is considered expired.
// By default this is null and tokens never expire.
'expiration' => 525600, // one year
// Per token, as the third argument. This overrides the config value.
$token = $user->createToken('CI deploy key', ['deploy'], now()->plus(weeks: 1));
// Expired rows stay in the table until you remove them. Sanctum ships
// a command for that:
//
// php artisan sanctum:prune-expired --hours=24
Discussion