
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
| Tag | Publishes |
|---|---|
sanctum-hardening | everything below, in one command |
sanctum-hardening-config | the configuration file |
sanctum-hardening-migrations | the migration |
sanctum-hardening-views | the views of the self-service screen, for overriding |
sanctum-hardening-lang | the translation files |
A setting that reads an environment variable changes without publishing the file:
| Environment variable | Setting | Default |
|---|---|---|
SANCTUM_HARDENING_ENABLED | sanctum-hardening.enabled | true |
SANCTUM_HARDENING_MAX_TOKENS_PER_USER | sanctum-hardening.minting.max_tokens_per_user | 20 |
SANCTUM_HARDENING_MINT_PER_MINUTE | sanctum-hardening.minting.per_minute | 5 |
SANCTUM_HARDENING_DEFAULT_LIFETIME | sanctum-hardening.minting.default_lifetime | 90 days |
SANCTUM_HARDENING_MAX_LIFETIME | sanctum-hardening.minting.max_lifetime | 365 days |
SANCTUM_HARDENING_ALLOW_NO_EXPIRY | sanctum-hardening.minting.allow_no_expiry | false |
SANCTUM_HARDENING_PASSWORD_TIMEOUT | sanctum-hardening.minting.password_timeout | Laravel's auth.password_timeout |
SANCTUM_HARDENING_PER_TOKEN | sanctum-hardening.throttle.per_token | 60 a minute |
SANCTUM_HARDENING_PER_USER | sanctum-hardening.throttle.per_user | 120 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.