REST API Development

API Authentication with Sanctum

20 min Lesson 9 of 35

Understanding Laravel Sanctum

Laravel Sanctum provides a lightweight authentication system for SPAs (Single Page Applications), mobile applications, and simple token-based APIs. It's perfect for authenticating APIs without the complexity of OAuth2.

What is Sanctum?

Sanctum offers two authentication methods:

  • API Tokens: Simple token-based authentication for mobile apps and third-party consumers
  • SPA Authentication: Cookie-based authentication for JavaScript SPAs running on the same domain
Note: Sanctum is ideal for most APIs. Use Passport only if you need full OAuth2 server implementation with multiple grant types.

Installing Sanctum

Install Sanctum via Composer:

composer require laravel/sanctum php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider" php artisan migrate

This creates the personal_access_tokens table to store API tokens.

Configuring Sanctum

Add Sanctum middleware to app/Http/Kernel.php:

'api' => [ \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, ],

Add the HasApiTokens trait to your User model:

use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable; // ... rest of model }

Creating API Tokens

Generate tokens for users after authentication:

use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; use Illuminate\Validation\ValidationException; public function login(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.'], ]); } // Create token $token = $user->createToken($request->device_name)->plainTextToken; return response()->json([ 'success' => true, 'message' => 'Login successful', 'data' => [ 'user' => $user, 'token' => $token, ] ]); }
Tip: The device_name helps users identify which device or application a token belongs to.

Protecting Routes with Sanctum

Apply the auth:sanctum middleware to protect routes:

// routes/api.php use Illuminate\Support\Facades\Route; // Public routes Route::post('/register', [AuthController::class, 'register']); Route::post('/login', [AuthController::class, 'login']); // Protected routes Route::middleware('auth:sanctum')->group(function () { Route::get('/user', function (Request $request) { return $request->user(); }); Route::post('/logout', [AuthController::class, 'logout']); Route::apiResource('posts', PostController::class); });

Making Authenticated Requests

Include the token in the Authorization header:

# Using cURL curl -H "Accept: application/json" \ -H "Authorization: Bearer YOUR_TOKEN_HERE" \ https://api.example.com/api/user # Using JavaScript Fetch fetch('https://api.example.com/api/user', { headers: { 'Accept': 'application/json', 'Authorization': 'Bearer ' + token } })

Revoking Tokens

Allow users to logout by revoking their current token:

public function logout(Request $request) { // Revoke current token $request->user()->currentAccessToken()->delete(); return response()->json([ 'success' => true, 'message' => 'Logged out successfully' ]); }

Revoke all tokens for a user:

// Revoke all tokens $user->tokens()->delete(); // Revoke specific token by ID $user->tokens()->where('id', $tokenId)->delete();

Token Abilities (Scopes)

Grant specific permissions to tokens:

// Create token with specific abilities $token = $user->createToken('mobile-app', [ 'post:create', 'post:update', 'post:delete', 'comment:create' ])->plainTextToken; // Create read-only token $readOnlyToken = $user->createToken('analytics', [ 'post:read', 'user:read' ])->plainTextToken;

Check token abilities in your code:

public function store(Request $request) { // Check if token has required ability if (!$request->user()->tokenCan('post:create')) { return response()->json([ 'success' => false, 'message' => 'Insufficient permissions' ], 403); } // Create post... }

Protecting Routes by Ability

Use the abilities middleware:

Route::middleware(['auth:sanctum', 'abilities:post:create,post:update']) ->post('/posts', [PostController::class, 'store']); // User must have ALL specified abilities Route::middleware(['auth:sanctum', 'ability:post:create,post:read']) ->get('/posts', [PostController::class, 'index']); // User must have AT LEAST ONE of the specified abilities

Token Expiration

Configure token expiration in config/sanctum.php:

'expiration' => 60 * 24, // 24 hours (in minutes) // Or set to null for tokens that never expire 'expiration' => null,

Check if a token has expired:

$token = $request->user()->currentAccessToken(); if ($token->expires_at && $token->expires_at->isPast()) { return response()->json([ 'success' => false, 'message' => 'Token has expired' ], 401); }

Customizing Token Model

Add custom fields to track additional token information:

// Migration Schema::table('personal_access_tokens', function (Blueprint $table) { $table->string('ip_address', 45)->nullable(); $table->text('user_agent')->nullable(); $table->timestamp('last_used_at')->nullable(); }); // When creating token $token = $user->createToken($request->device_name); $token->accessToken->update([ 'ip_address' => $request->ip(), 'user_agent' => $request->userAgent(), ]); return response()->json([ 'token' => $token->plainTextToken ]);

SPA Authentication

For SPAs on the same domain, use cookie-based authentication:

// Configure CORS in config/cors.php 'supports_credentials' => true, // Configure Sanctum stateful domains in config/sanctum.php 'stateful' => explode(',', env( 'SANCTUM_STATEFUL_DOMAINS', 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000' )), // Add to .env SANCTUM_STATEFUL_DOMAINS=localhost:3000,myapp.com SESSION_DOMAIN=.myapp.com

SPA authentication flow:

// 1. Get CSRF cookie await axios.get('/sanctum/csrf-cookie'); // 2. Login await axios.post('/login', { email: 'user@example.com', password: 'password' }); // 3. Make authenticated requests (cookie automatically included) const response = await axios.get('/api/user');

Registration with Token

Create a registration endpoint that returns a token:

public function register(Request $request) { $validated = $request->validate([ 'name' => 'required|string|max:255', 'email' => 'required|email|unique:users,email', 'password' => 'required|string|min:8|confirmed', 'device_name' => 'required', ]); $user = User::create([ 'name' => $validated['name'], 'email' => $validated['email'], 'password' => Hash::make($validated['password']), ]); $token = $user->createToken($validated['device_name'])->plainTextToken; return response()->json([ 'success' => true, 'message' => 'Registration successful', 'data' => [ 'user' => $user, 'token' => $token, ] ], 201); }

Managing User Tokens

Allow users to view and manage their tokens:

public function tokens(Request $request) { $tokens = $request->user()->tokens->map(function ($token) { return [ 'id' => $token->id, 'name' => $token->name, 'abilities' => $token->abilities, 'last_used_at' => $token->last_used_at, 'created_at' => $token->created_at, 'expires_at' => $token->expires_at, ]; }); return response()->json([ 'success' => true, 'data' => $tokens ]); } public function revokeToken(Request $request, $tokenId) { $request->user() ->tokens() ->where('id', $tokenId) ->delete(); return response()->json([ 'success' => true, 'message' => 'Token revoked successfully' ]); }

Testing with Sanctum

Test authenticated endpoints in your tests:

use Laravel\Sanctum\Sanctum; public function test_user_can_view_posts() { $user = User::factory()->create(); // Act as authenticated user Sanctum::actingAs($user); $response = $this->getJson('/api/posts'); $response->assertStatus(200); } public function test_user_can_create_post_with_ability() { $user = User::factory()->create(); // Act as user with specific abilities Sanctum::actingAs($user, ['post:create']); $response = $this->postJson('/api/posts', [ 'title' => 'Test Post', 'content' => 'Test content', ]); $response->assertStatus(201); }
Exercise:
  1. Install and configure Sanctum in your application
  2. Create registration and login endpoints that return tokens
  3. Protect your Post CRUD routes with auth:sanctum middleware
  4. Implement token abilities for read-only and full-access tokens
  5. Create an endpoint that lists all user tokens with their abilities and last used time
  6. Add a logout endpoint that revokes the current token

Security Best Practices

  • Use HTTPS: Always use HTTPS in production to protect tokens in transit
  • Short-Lived Tokens: Set reasonable expiration times (24 hours for mobile apps)
  • Device Tracking: Store device name, IP, and user agent for security auditing
  • Token Rotation: Implement token refresh for long-lived applications
  • Ability Scopes: Grant minimal necessary permissions to tokens
  • Revocation: Provide easy token revocation for users
  • Rate Limiting: Apply rate limiting to authentication endpoints

Summary

Laravel Sanctum provides a simple, secure authentication system perfect for APIs and SPAs. With token abilities, expiration, and easy revocation, it covers most authentication needs without OAuth2 complexity. In the next lesson, we'll explore Laravel Passport for applications requiring full OAuth2 implementation.