Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Sanctum Refresh Tokens Laravel Package

d076/sanctum-refresh-tokens

Add refresh token support to Laravel Sanctum. Issue short-lived access tokens alongside revocable refresh tokens, rotate them on use, and keep API authentication secure without long-lived tokens.

View on GitHub
Deep Wiki
Context7

Getting Started

Requirements first — this is a hard gate. v4.0.0 requires PHP 8.3+ and Laravel ^12.0|^13.0. Laravel 11 and PHP 8.2 are no longer supported. If you are on Laravel 11, stay on 3.1.x or plan the framework upgrade as part of adopting this release.

  1. Install via Composer (composer require d076/sanctum-refresh-tokens — confirm the exact vendor/name against the README install block) and let Laravel package discovery register the service provider.
  2. Publish and run the migrations so you get the personal_refresh_tokens table, including the new abilities column added in 4.0. Run the migration before deploying the new code — the ability carry-over logic depends on the column existing.
  3. Read the rewritten README end-to-end, then open AuthService — specifically refresh() and getUserFromCredentials(). Those two methods are the whole contract you will build against.
  4. Check your User model: if you use the package's AuthenticatableUser base class, it extends Illuminate\Foundation\Auth\User; if you have your own model, make sure it satisfies the same contract.
  5. Skim the test suite (Pest + Orchestra Testbench) — it is the best executable documentation of rotation, replay protection and TTL semantics, and it runs against SQLite, PostgreSQL and MySQL.

First use case: wire up login → refresh → logout. On login, authenticate credentials and return a short-lived access token plus a long-lived refresh token. On refresh, hand the refresh token to AuthService::refresh(), get a new pair back, and replace what the client stores. Protect your normal API routes with Sanctum's auth:sanctum; protect the refresh endpoint with throttling instead (the client's access token is expired by definition).

Implementation Patterns

Token pair on login. Issue the access token with the narrowest abilities the client needs. Because abilities are now persisted on the refresh token, whatever you pass at login is what you get back forever after — this is the intended design, so decide scopes at issuance rather than trying to widen them later.

Refresh with rotation. Treat every refresh as a swap: persist the newly returned refresh token and discard the old one. The old token is invalidated by rotation, so a client that keeps using the stale one will get rejected. Store refresh tokens in an HttpOnly, Secure, SameSite cookie where possible; keep access tokens in memory on the client.

Handle replay as a security event, not a retry. If a refresh token that has already been rotated is presented again, the package's replay protection will reject it. Map that failure to a 401, clear the client's cookie, and force a full re-login. Log the event — repeated replays usually mean a leaked token.

Logout and password reset = revocation. On logout, revoke the refresh token (and ideally the current access token). On password reset, revoke all refresh tokens for the user so existing sessions cannot silently extend themselves. The 4.0 test suite covers both flows, so mirror its expectations in your own feature tests.

Custom email columns now work. getUserFromCredentials() resolves getEmailField() on a model instance in 4.0. If you authenticate by something other than email, this is the release that stops fataling — add a regression test for your column.

Test with time travel. TTL enforcement is best tested with Laravel's travel()/Carbon::setTestNow(): issue a pair, travel past the access TTL, assert refresh still works, travel past the refresh TTL, assert it fails. Also assert the DB stores a hash, not the raw token.

Upgrade pattern from 3.x: bump PHP and Laravel constraints, run the new migration, then decide what to do with refresh tokens issued under 3.x. Their abilities were previously not persisted, so treat pre-upgrade tokens as untrusted — the cleanest path is to invalidate them and require one re-login. A backfill is possible but you would be guessing at the original scope, which is exactly the bug 4.0 fixes.

Gotchas and Tips

  • The abilities bug is a real security issue, not just a bug. Before 4.0, every refresh reset the access token's abilities to ['*']. If you shipped scoped tokens on 3.x, assume they were silently widened and audit any sensitive action those tokens could reach. Revoke refresh tokens issued before the upgrade.
  • laravel/framework is now a direct dependency. This makes installs more honest but can conflict with apps that pin the framework tightly, and it means the package can no longer be used in an illuminate-only, framework-less setup. Resolve conflicts at the root composer.json.
  • Concurrent refreshes cause false replay positives. Multiple browser tabs refreshing at once will rotate the token twice, and the second request looks like a replay. Serialize refreshes client-side (single-flight / mutex) or add a short grace window server-side. This is the single most common production symptom with rotating refresh tokens.
  • You cannot look up a token by its raw value. Refresh tokens are hashed at rest; hash the incoming value the same way before querying. Never log raw tokens — your logs would become a credential store.
  • Deploy ordering matters. Migration first, then code. The inverse gives you a window where refresh() writes to a column that does not exist.
  • Cross-database coverage is a promise, not a guarantee for your schema. The suite passes on SQLite, PostgreSQL and MySQL, but if you add your own indexes or query the abilities column directly, verify the column type on your engine (JSON vs. text) before writing whereJsonContains-style queries.
  • PHPStan level 6 is good, not exhaustive. Do not assume generics on your User model are fully validated; run Larastan in your own app against your subclass.
  • Extension points: subclass AuthenticatableUser for custom user behaviour, and use the notifications dependency (illuminate/notifications) to brand password-reset emails. Publish any package config before overriding, so future upgrades do not clobber your changes.
  • Debugging a failing refresh: check, in order — is the token expired, already rotated, or being sent through the wrong guard? Inspect the personal_refresh_tokens rows for the user; the stored abilities and revocation state usually explain the 401 immediately.
  • Pin the major version. 4.0 is a BC break on two axes (PHP and Laravel); ^4.0 in composer.json keeps you from being surprised by a future major.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo