Skip to main content

Sanctum Hardening for Laravel

A token only narrows what its user may do, and never widens it. Every part of this package follows from that sentence. Sanctum gives a token the wildcard unless it is told otherwise, mints as many tokens as a session asks for, and writes to the token's row on every request. This package puts a rule in front of each of those:

  • Abilities: one catalog names every ability a token can carry. There is no wildcard, and a reserved ability belongs to the application's own code alone.
  • Minting: a token is minted behind a password confirmed a moment ago, under a limit per user and per minute, with a lifetime from a list. A token of a workspace follows the same rules, where the application lets the user manage its tokens.
  • Throttling and the switch: the API runs behind a limit per token and one per user over all of their tokens, and one setting removes its routes altogether.
  • The screen: an optional Livewire screen where a person mints, sees and revokes their own tokens.

Installation​

composer require pushery/sanctum-hardening-for-laravel
php artisan migrate

The service provider is registered automatically through package discovery. The migration creates sanctum_hardening_token_prefixes, where the package keeps the first characters of each token's secret so a person can tell their tokens apart. Where sanctum.token_prefix puts a constant in front of every secret, the prefix keeps the constant and the twelve characters after it. Sanctum's own personal_access_tokens table stays as php artisan install:api created it.

Configuration​

Publish the configuration file, where every option is documented inline:

php artisan vendor:publish --tag=sanctum-hardening-config
TagPublishes
sanctum-hardeningeverything below, in one command
sanctum-hardening-configthe configuration file
sanctum-hardening-migrationsthe migration
sanctum-hardening-viewsthe views of the self-service screen, for overriding
sanctum-hardening-langthe translation files

A setting that reads an environment variable changes without publishing the file:

Environment variableSettingDefault
SANCTUM_HARDENING_ENABLEDsanctum-hardening.enabledtrue
SANCTUM_HARDENING_MAX_TOKENS_PER_USERsanctum-hardening.minting.max_tokens_per_user20
SANCTUM_HARDENING_MINT_PER_MINUTEsanctum-hardening.minting.per_minute5
SANCTUM_HARDENING_DEFAULT_LIFETIMEsanctum-hardening.minting.default_lifetime90 days
SANCTUM_HARDENING_MAX_LIFETIMEsanctum-hardening.minting.max_lifetime365 days
SANCTUM_HARDENING_ALLOW_NO_EXPIRYsanctum-hardening.minting.allow_no_expiryfalse
SANCTUM_HARDENING_PASSWORD_TIMEOUTsanctum-hardening.minting.password_timeoutLaravel's auth.password_timeout
SANCTUM_HARDENING_PER_TOKENsanctum-hardening.throttle.per_token60 a minute
SANCTUM_HARDENING_PER_USERsanctum-hardening.throttle.per_user120 a minute

SANCTUM_HARDENING_ENABLED is off for false, 0, no, off and an empty value, written in any case and with spaces around it, and any other value keeps it on. SANCTUM_HARDENING_ALLOW_NO_EXPIRY loosens a mint, so it is the other way round: it is on only for true, 1, yes and on, and any other value keeps it off. A number that is no number, or one below one, keeps the default. The password timeout also takes 0, which asks for the password before every mint.

The migrations run from the package as they are. To keep them in your application instead, call \Pushery\SanctumHardening\SanctumHardeningServiceProvider::ignoreMigrations() in the register method of one of your service providers before you publish them.

Checking existing tokens​

Tokens minted before the package, or around it with createToken(), may carry the wildcard or an ability the catalog does not know:

php artisan sanctum-hardening:doctor

It names each such token by its id and ends with exit code 1, so a deploy step can stop on it.

Pruning the prefixes of deleted tokens​

A token deleted through its model takes its prefix along. Sanctum's sanctum:prune-expired and $user->tokens()->delete() delete with a query instead, which fires no model event, so their prefixes stay behind. Schedule the package's prune beside Sanctum's:

use Illuminate\Support\Facades\Schedule;

Schedule::command('sanctum:prune-expired --hours=24')->daily();
Schedule::command('sanctum-hardening:prune-prefixes')->daily();

It reports how many prefixes it forgot, and runs once at a time with --isolated.