Building a REST API in Laravel with Sanctum Authentication

A complete walkthrough of building a token-authenticated REST API in Laravel using Sanctum, with full working code.

Why Sanctum for API Auth

Sanctum provides lightweight token-based authentication ideal for SPAs, mobile apps, and simple token-based APIs, without the complexity of a full OAuth2 server that Passport requires.

Installation

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

Configuring the User Model

use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;
}

Login Endpoint Issuing Tokens

Route::post('/login', function (Request $request) {
    $request->validate([
        'email' => 'required|email',
        'password' => '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.'],
        ]);
    }

    $token = $user->createToken('api-token', ['orders:read'])->plainTextToken;

    return response()->json(['token' => $token]);
});

Protecting Routes

Route::middleware('auth:sanctum')->group(function () {
    Route::get('/user', fn (Request $request) => $request->user());
    Route::apiResource('orders', OrderController::class);
});

Scoping Tokens with Abilities

// Restrict a token to read-only order access
$token = $user->createToken('mobile-app', ['orders:read'])->plainTextToken;

// Check ability inside a controller
public function destroy(Request $request, Order $order)
{
    if (!$request->user()->tokenCan('orders:delete')) {
        abort(403);
    }
    $order->delete();
}

API Resource Formatting

class OrderResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'total' => $this->total,
            'status' => $this->status,
            'items' => OrderItemResource::collection($this->whenLoaded('items')),
        ];
    }
}

Revoking Tokens on Logout

Route::post('/logout', function (Request $request) {
    $request->user()->currentAccessToken()->delete();
    return response()->json(['message' => 'Logged out']);
})->middleware('auth:sanctum');

Conclusion

Sanctum strikes a good balance between simplicity and real security for most API use cases, especially first-party SPAs and mobile apps. Reach for Passport only if you actually need full OAuth2 flows for third-party integrations.