Skip to main content
Laravel, shipping fast.
// routes/api.php
Route::middleware('auth:sanctum')->group(function () {
    Route::apiResource('licenses', LicenseController::class)
        ->only(['index', 'store', 'destroy']);
});

A request without a valid token never reaches the controller. It gets a 401:

{
    "message": "Unauthenticated."
}

That response is JSON because of the one line Chapter 2 added to bootstrap/app.php. And inside the application, the consumer is where Laravel always puts the authenticated party:

$consumer = $request->user();

No custom request attribute, no helper of your own. Policies, rate limiters, and logging all find it there.

Authorization: What a Token May Do

Authentication answers “who is this?” Authorization answers “may they do that?” A common way for an API to leak is an endpoint that checked the first and forgot the second.

Sanctum ships a middleware that checks a token’s abilities. Give it an alias:

// bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->alias([
        'ability' => CheckForAnyAbility::class,
    ]);
})

alias() takes the complete list. A second call replaces the first, so later chapters add their aliases to this array and never call it again.

Then say, on the controller, what each action needs:

// app/Http/Controllers/LicenseController.php
#[Middleware('ability:licenses:read', only: ['index'])]
#[Middleware('ability:licenses:write', except: ['index'])]
class LicenseController
{
    // ...
}

A token without the ability gets a 403. Laravel’s documentation usually shows these attributes on individual methods, and that reads well. I put them on the class here for the sake of except: a new action added to this controller needs licenses:write unless somebody deliberately says otherwise. Inside this controller, the default is closed. (Laravel documentation: Sanctum › Token Abilities and Controllers › Controller Middleware.)

That default stops at the controller’s edge. A new controller starts with no ability at all, protected only by auth:sanctum, and that is exactly how the worst holes in an API are made. Chapter 5 adds two controllers and shows what each one needs, and Chapter 19 turns “every controller declares who may call it” into something you check.

For an action with a FormRequest, the check can live in authorize() instead, the method Chapter 2 left returning true:

// app/Http/Requests/StoreLicenseRequest.php
public function authorize(): bool
{
    return $this->user()->tokenCan('licenses:write');
}

Use one or the other for a given action, not both.

When Abilities Aren’t Enough

Abilities say what a token may do. They don’t say which records it may do it to. That second question needs a Policy, and Chapter 5 writes one, for the first resource in this API where it matters. Licenses don’t need one, for the reason the next section explains.

The audio could not be loaded. Try again in a moment.