Skip to main content

What is Laravel Sanctum?

Laravel Sanctum provides a featherweight authentication system for SPAs, mobile applications, and simple, token-based APIs. It allows each user to generate multiple API tokens for their account, which can be granted specific abilities or scopes.

Installation & Setup

Laravel Sanctum is already installed in this project as specified in composer.json with version ^4.0.
1

Verify Installation

Sanctum is already included in your project dependencies:
composer.json
{
  "require": {
    "laravel/sanctum": "^4.0"
  }
}
If you need to reinstall, run:
composer require laravel/sanctum
2

Publish Configuration (Optional)

If you need to customize Sanctum’s configuration, publish the config file:
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
This creates config/sanctum.php where you can customize:
  • Token expiration
  • Stateful domains
  • Guard configuration
  • Middleware settings
3

Run Migrations

Sanctum requires a database table to store API tokens:
php artisan migrate
This creates the personal_access_tokens table which stores:
  • Token hashes
  • Token names
  • Abilities/scopes
  • Last used timestamps
  • Expiration dates
4

Add Sanctum Trait to User Model

The User model should use the HasApiTokens trait:
app/Models/User.php
use Laravel\Sanctum\HasApiTokens;

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

Protecting Routes with Sanctum

To protect API routes, use the auth:sanctum middleware. Here’s an example from the project:
routes/api.php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

// Protected route - requires authentication
Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:sanctum');

// Public routes - no authentication required
Route::apiResource('institutions', InstitutionsController::class);
Route::apiResource('users', UsersController::class);
Currently, most API routes in the project are public and do not require authentication. Consider adding the auth:sanctum middleware to routes that should be protected.

Protecting Resource Routes

You can protect entire resource controllers:
// Protect all routes in the controller
Route::apiResource('users', UsersController::class)
    ->middleware('auth:sanctum');

// Or protect specific methods in the controller constructor
class UsersController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth:sanctum');
    }
}

Group Protection

Protect multiple routes at once:
Route::middleware('auth:sanctum')->group(function () {
    Route::apiResource('users', UsersController::class);
    Route::apiResource('institutions', InstitutionsController::class);
    Route::apiResource('sections', SectionsController::class);
});

Generating API Tokens

Creating a Login Endpoint

Create a login controller to issue tokens:
app/Http/Controllers/Api/AuthController.php
namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;

class AuthController extends Controller
{
    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.'],
            ]);
        }

        // Generate token
        $token = $user->createToken($request->device_name)->plainTextToken;

        return response()->json([
            'success' => true,
            'message' => 'Login exitoso',
            'data' => [
                'user' => $user,
                'token' => $token,
            ]
        ], 200);
    }

    public function logout(Request $request)
    {
        // Revoke current token
        $request->user()->currentAccessToken()->delete();

        return response()->json([
            'success' => true,
            'message' => 'Logout exitoso'
        ], 200);
    }

    public function tokens(Request $request)
    {
        // List all tokens for the user
        return response()->json([
            'success' => true,
            'data' => $request->user()->tokens
        ], 200);
    }
}

Register Auth Routes

Add authentication routes to routes/api.php:
routes/api.php
use App\Http\Controllers\Api\AuthController;

// Authentication routes
Route::post('/login', [AuthController::class, 'login']);

Route::middleware('auth:sanctum')->group(function () {
    Route::get('/user', function (Request $request) {
        return $request->user();
    });
    Route::post('/logout', [AuthController::class, 'logout']);
    Route::get('/tokens', [AuthController::class, 'tokens']);
});

Using API Tokens

Login Request

curl -X POST https://api.example.com/api/login \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "password123",
    "device_name": "iPhone 15"
  }'

Login Response

{
  "success": true,
  "message": "Login exitoso",
  "data": {
    "user": {
      "id": 1,
      "institutions_id": 5,
      "rol": "admin",
      "nombre": "Juan",
      "apellido": "Pérez",
      "email": "[email protected]",
      "activo": true
    },
    "token": "1|a8K3jD9fM2pL5qR7sT1vW4xY6zB0cE..."
  }
}

Making Authenticated Requests

Include the token in the Authorization header with the Bearer prefix:
curl -X GET https://api.example.com/api/user \
  -H "Authorization: Bearer 1|a8K3jD9fM2pL5qR7sT1vW4xY6zB0cE..." \
  -H "Accept: application/json"

Token Abilities & Scopes

You can assign specific abilities to tokens for fine-grained access control:
// Create token with specific abilities
$token = $user->createToken('mobile-app', ['read', 'write'])->plainTextToken;

// Create token with all abilities
$token = $user->createToken('admin-token', ['*'])->plainTextToken;

// Check abilities in controller
public function update(Request $request, User $user)
{
    if ($request->user()->tokenCan('write')) {
        // User has write permission
        $user->update($request->validated());
    }
}

// Protect routes by ability
Route::put('/users/{user}', [UsersController::class, 'update'])
    ->middleware(['auth:sanctum', 'ability:write']);

Token Management

Revoking Tokens

// Revoke current token (logout)
$request->user()->currentAccessToken()->delete();

// Revoke all tokens
$request->user()->tokens()->delete();

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

Listing User Tokens

// Get all tokens for the authenticated user
$tokens = $request->user()->tokens;

// Get token information
foreach ($tokens as $token) {
    echo $token->name;           // Device/app name
    echo $token->abilities;      // Token abilities
    echo $token->last_used_at;   // Last usage timestamp
    echo $token->created_at;     // Creation date
}
Display a list of active tokens in your application so users can manage and revoke access from old devices or apps.

Error Handling

Unauthenticated Responses

When a request is made without a valid token:
{
  "message": "Unauthenticated."
}
HTTP Status Code: 401 Unauthorized

Invalid Credentials

When login credentials are incorrect:
{
  "message": "The provided credentials are incorrect.",
  "errors": {
    "email": [
      "The provided credentials are incorrect."
    ]
  }
}
HTTP Status Code: 422 Unprocessable Entity

Security Best Practices

Never expose API tokens in client-side code, logs, or version control. Store tokens securely in environment variables or secure storage mechanisms.

Token Storage

Store tokens in:
  • localStorage or sessionStorage for SPAs
  • HttpOnly cookies for added security
  • Secure, encrypted storage
// Store token
localStorage.setItem('api_token', token);

// Retrieve token
const token = localStorage.getItem('api_token');

// Remove token (logout)
localStorage.removeItem('api_token');

HTTPS Only

Always use HTTPS in production. Tokens sent over HTTP can be intercepted.

Token Expiration

Consider implementing token expiration in config/sanctum.php:
config/sanctum.php
'expiration' => 60 * 24, // 24 hours

Rate Limiting

Implement rate limiting on authentication endpoints to prevent brute-force attacks:
Route::post('/login', [AuthController::class, 'login'])
    ->middleware('throttle:5,1'); // 5 attempts per minute

Testing Authentication

Test authentication in your test suite:
tests/Feature/AuthenticationTest.php
use App\Models\User;
use Laravel\Sanctum\Sanctum;

public function test_user_can_authenticate()
{
    $user = User::factory()->create([
        'email' => '[email protected]',
        'password' => Hash::make('password'),
    ]);

    $response = $this->postJson('/api/login', [
        'email' => '[email protected]',
        'password' => 'password',
        'device_name' => 'test-device',
    ]);

    $response->assertOk();
    $response->assertJsonStructure(['data' => ['token']]);
}

public function test_authenticated_user_can_access_protected_route()
{
    Sanctum::actingAs(
        User::factory()->create(),
        ['*']
    );

    $response = $this->getJson('/api/user');

    $response->assertOk();
}

Next Steps

API Reference

Explore available API endpoints

User Management

Learn about user operations

Build docs developers (and LLMs) love