roundly-consulting / refresh-tokens-for-laravel
Opaque, rotating refresh tokens and device sessions for Laravel — SHA-256 at rest, atomic anti-double-spend rotation, family revocation on reuse. Zero third-party runtime deps.
Package info
github.com/roundly-consulting/refresh-tokens-for-laravel
pkg:composer/roundly-consulting/refresh-tokens-for-laravel
Fund package maintenance!
Requires
- php: ^8.4
- illuminate/console: ^12.0|^13.0
- illuminate/contracts: ^12.0|^13.0
- illuminate/database: ^12.0|^13.0
- illuminate/http: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- roundly-consulting/crypto-for-laravel: ^1.0
- roundly-consulting/enums-for-laravel: ^1.0
- roundly-consulting/package-toolkit-for-laravel: ^1.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.0
- nunomaduro/collision: ^8.0
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^4.0
- phpstan/extension-installer: ^1.4
- roundly-consulting/testing-for-laravel: ^1.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Refresh Tokens for Laravel
Opaque, rotating refresh tokens and device sessions for Laravel — SHA-256 at rest, atomic
anti-double-spend rotation, and whole-family revocation the moment a spent token is replayed.
Tokens hang off a polymorphic owner, so any Authenticatable model can hold sessions; JWT minting,
user-agent parsing and routes stay in your app.
Installation
Requires PHP 8.4 and Laravel 12 or 13.
composer require roundly-consulting/refresh-tokens-for-laravel
php artisan vendor:publish --tag="refresh-tokens-migrations"
php artisan migrate
If your owner models have UUID/ULID keys, set REFRESH_TOKENS_KEY_TYPE=uuid (or ulid)
before migrating.
Usage
Add the trait to every model that holds sessions:
use RoundlyConsulting\RefreshTokens\Traits\HasRefreshTokens; final class User extends Authenticatable { use HasRefreshTokens; }
At login, mint your access token, then issue a refresh token linked to it. On refresh the owner is
known only once rotate() returns, so pick the new access token's id first:
use Illuminate\Support\Str; use RoundlyConsulting\RefreshTokens\DataTransferObjects\RotationContext; use RoundlyConsulting\RefreshTokens\Facades\RefreshTokens; $new = RefreshTokens::for($user)->fromRequest($request)->linkedTo($access->jti)->issue(); $new->plainText; // hand to the client ONCE — never stored $jti = (string) Str::uuid(); // the next access token's id $rotation = RefreshTokens::rotate($new->plainText, new RotationContext(accessReference: $jti)); // null means "log in again"; otherwise mint the access token for $rotation->user with $jti $rotation->newRefreshToken->plainText; // the replacement refresh token RefreshTokens::rotate($new->plainText); // null — a replayed token revokes the whole session
Minting the access token after the redeem instead? Use redeem(), then issue() into the same
family — see the docs.
Manage device sessions:
RefreshTokens::sessions($user)->all(); // active sessions, newest first RefreshTokens::sessions($user)->revokeAllExcept($currentFamilyId); // "log out my other devices" RefreshTokens::revoke($plainFromClient); // logout with the token in hand
Documentation
The full documentation — configuration, every feature and its API, and testing — lives on our website: roundly-consulting.com/open-source/docs/refresh-tokens-for-laravel
Release notes are in CHANGELOG.md. To contribute, see the contributing guide.
Support our work
This package is free and open source, built and maintained by Roundly Consulting. If it saves you time, please consider supporting our open-source work — a one-time donation, a monthly pledge on Patreon or a crypto donation helps fund maintenance, new features and new packages.
License
The MIT License (MIT). Copyright (c) roundly-consulting. See LICENSE.md.