// 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.