Authorization

Decision Cache

Keep the decisions of chosen policy abilities between requests, key them per user and tenant, and forget them when rights change.

Within one request, each distinct check runs the policy once. Between requests, the package keeps only the decisions that a resource declares. A resource that declares nothing keeps nothing.

Declaring the cached abilities

List in authorizationCache() the abilities to keep, with the number of seconds to keep each one:

public function authorizationCache(): array
{
    return ['viewAny' => 300, 'view' => 60];
}
  • The other abilities run the policy in each request.
  • A duration that is not an integer of 1 or more throws Lomkit\Rest\Exceptions\InvalidResourceException.
  • The resource of the check declares the ability: the resource of the endpoint or of the reached relation for viewAny and create, the resource of the row for its gates and its row checks, and the parent resource for the link abilities of a relation.
  • A declared ability is read from the cache wherever it is checked: the gates of a response, the checks before validation, the row checks of mutate, destroy, restore and forceDestroy, and the row checks of actions.
  • Denials are kept like the other decisions, with their message and status.
A cached delete on a row lets destroy delete that row without calling the policy while the row keeps its updated_at. Cache the write abilities only when their policy reads nothing that changes outside the row.

What is never kept

  • The check of create on each row that mutate creates, and the update checks of mutate, which carry the changes of their entry. The create check on the model class can be kept.
  • A decision on a row without updated_at. Add timestamps to a model to keep the decisions on its rows.
  • A decision on a row whose updated_at is not before the second in which the request started.
  • A check on a row that the request wrote.
  • A check on a model that is not saved yet.

The key

The key holds the guard, the user class and key, the resource class, the ability, the class, key and updated_at of each model, the values of authorizationCacheContext(), and the stamps that Rest::forgetAuthorizationsFor() and Rest::forgetAuthorizations() renew.

A decision on a row follows the row's updated_at, so a change made through Eloquent is checked again in the next request. A change made without Eloquent that leaves updated_at alone, such as DB::table()->update(), shows once the entry expires or the user is forgotten.

Adding values to the key

authorizationCacheContext() adds values of your own to the key of every decision that the resource keeps, for example the team of the user:

public function authorizationCacheContext(): array
{
    return ['team' => $this->request()->user()?->current_team_id];
}

A value is an integer, a string, a boolean or null. Another value throws InvalidResourceException.

Tenants

The key holds the user class and key, not the tenant. With several tenants whose users share identifiers, add the tenant in the base resource of your application:

public function authorizationCacheContext(): array
{
    return ['tenant' => tenant()?->getTenantKey()];
}

Or name in authorizations.cache.store a store that is already per tenant. The context also keys the metric results.

Forgetting decisions

use Lomkit\Rest\Facades\Rest;

Rest::forgetAuthorizationsFor($user);
Rest::forgetAuthorizations();
  • forgetAuthorizationsFor($user) drops the kept decisions and metric results of a user, on every guard. Call it where your application changes what a policy or a Gate::before() callback reads about the user, such as a role or a team.
  • forgetAuthorizations() drops the kept decisions and metric results of every user and of guests, for example after a deploy that changes a policy.
  • Both work on every cache store, and write even when the cache is off.

Otherwise a kept decision stays for the seconds of its ability: a change to a policy shows once the entry expires.

Configuration

'authorizations' => [
    'cache' => [
        'enabled' => env('REST_AUTHORIZATIONS_CACHE_ENABLED', true),
        'store' => env('REST_AUTHORIZATIONS_CACHE_STORE'),
    ],
],
  • enabled set to false turns the cache off for every resource. Set REST_AUTHORIZATIONS_CACHE_ENABLED=false while you debug policies.
  • store names the cache store, the default store when null. A store that is not defined throws Cache store [name] is not defined. on the first check of a declared ability.
  • A decision is stored as an array of scalars, so any store works, file and database included.
  • When no resource declares an ability, the package calls no cache store.

Grouping your own checks

The package reads the kept decisions of a group of checks in one call, and writes the decisions it evaluated in one call per duration. authorizer()->batch() groups checks of your own the same way:

use Lomkit\Rest\Authorization\Check;

$this->request()->context()->authorizer()->batch(
    [Check::onClass($resource, 'viewAny')],
    fn () => $resource->authorizedTo('viewAny', Post::class),
);