承接 d076/sanctum-refresh-tokens 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

d076/sanctum-refresh-tokens

Composer 安装命令:

composer require d076/sanctum-refresh-tokens

包简介

Refresh token realization for laravel sanctum

README 文档

README

Tests Latest Version License

Refresh tokens on top of Laravel Sanctum. Sanctum issues long-lived personal access tokens; this package adds a short-lived access token paired with a longer-lived, single-use refresh token, so a client can silently obtain a fresh pair without re-authenticating — the standard pattern for SPAs and mobile apps.

Features

  • Access + refresh token pairs issued together, each with its own TTL.
  • Single-use rotation — exchanging a refresh token deletes it and its bound access token, then issues a brand-new pair. Replaying a used token fails.
  • Hashed at rest — refresh tokens are stored as SHA-256 hashes and compared in constant time (hash_equals); the plaintext is only ever returned to the client.
  • TTL enforced on lookup — expired refresh tokens are never matched.
  • Credential login, logout and password reset helpers that revoke the right tokens.
  • Override-friendly — no routes or controllers are shipped; you wire your own. Services are bound behind interfaces, and the user model's email/password fields are configurable.
  • Prune command for housekeeping expired tokens.

Requirements

Version
PHP ^8.3
Laravel 12, 13
Sanctum ^4.0

Tested against PHP 8.3 / 8.4 / 8.5 and Laravel 12 / 13 on SQLite, PostgreSQL and MySQL.

Installation

composer require d076/sanctum-refresh-tokens

Publish and run the migration (creates the personal_refresh_tokens table):

php artisan vendor:publish --tag=sanctum-refresh-tokens
php artisan migrate

This package builds on Sanctum's personal_access_tokens table, so make sure Sanctum itself is installed and migrated (php artisan install:api on a fresh app).

Upgrading from 3.x

4.0 adds an abilities column to personal_refresh_tokens (so a token's scope is preserved across refreshes). Re-publish and migrate to pick it up:

php artisan vendor:publish --tag=sanctum-refresh-tokens
php artisan migrate

Refresh tokens issued before the upgrade have no stored scope and fall back to ['*'] on their next refresh.

Setup

Extend your authenticatable model from AuthenticatableUser:

use D076\SanctumRefreshTokens\Models\AuthenticatableUser;

class User extends AuthenticatableUser
{
    // ...
}

AuthenticatableUser already pulls in Sanctum's HasApiTokens plus this package's refresh-token behaviour. If you can't change your base class, use the trait directly and implement the contract instead:

use D076\SanctumRefreshTokens\HasApiTokensInterface;
use D076\SanctumRefreshTokens\Traits\HasApiTokens;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements HasApiTokensInterface
{
    use HasApiTokens;
}

Configuration

Token lifetimes are read from Sanctum's config (config/sanctum.php). Add the keys this package uses alongside Sanctum's own:

// config/sanctum.php
'expiration' => env('SANCTUM_ACCESS_TOKEN_EXPIRATION', 60),                          // access token, minutes
'refresh_token_expiration' => env('SANCTUM_REFRESH_TOKEN_EXPIRATION', 43200),        // refresh "remember me", minutes (30 days)
'refresh_token_expiration_no_remember' => env('SANCTUM_REFRESH_TOKEN_EXPIRATION_NO_REMEMBER', 1440), // refresh without remember, minutes (1 day)

If a key is absent the built-in defaults above are used, so the package works out of the box. The optional sanctum.token_prefix is honoured for refresh tokens too (useful for secret-scanning).

Usage

The package ships no routes or controllers — you stay in control of your API surface. Inject the services where you need them.

Issuing tokens

use D076\SanctumRefreshTokens\Services\TokenService;

$tokens = (new TokenService($user))->createTokens();

createTokens() accepts optional overrides:

$tokens = (new TokenService($user))->createTokens(
    accessTokenExpiresAt: now()->addMinutes(15),
    refreshTokenExpiresAt: now()->addDays(7),
    abilities: ['orders:read', 'orders:write'],
);

It returns a TokensDTO:

$tokens->access_token;              // plain-text bearer token
$tokens->refresh_token;             // plain-text refresh token ("{id}|{token}")
$tokens->token_type;                // "Bearer"
$tokens->access_token_expires_at;   // Carbon|null
$tokens->refresh_token_expires_at;  // Carbon|null
$tokens->model;                     // morph class of the user
$tokens->user;                      // the user model

return response()->json($tokens);   // implements Arrayable

Logging in with credentials

use D076\SanctumRefreshTokens\DTOs\LoginDTO;
use D076\SanctumRefreshTokens\Services\IAuthService;

public function login(Request $request, IAuthService $auth)
{
    $credentials = new LoginDTO(
        email: $request->input('email'),
        password: $request->input('password'),
        model: User::class,
        remember: $request->boolean('remember'),
    );

    // throws Illuminate\Auth\AuthenticationException on bad credentials
    $tokens = $auth->setCredentials($credentials)->login();

    return response()->json($tokens);
}

remember selects between the two refresh-token TTLs (refresh_token_expiration vs refresh_token_expiration_no_remember).

Already have the user (e.g. social login)? Skip credentials:

$tokens = $auth->setUser($user)->login();

Refreshing

public function refresh(Request $request, IAuthService $auth)
{
    // throws AuthenticationException if the token is unknown, expired or already used
    $tokens = $auth->refresh($request->input('refresh_token'));

    return response()->json($tokens);
}

The old refresh token and its bound access token are deleted before the new pair is issued, so a stolen-and-replayed token is rejected on the second use.

Logout

Behind the auth:sanctum guard, the authenticated request carries the current access token, so logout can revoke exactly that pair:

public function logout(Request $request, IAuthService $auth)
{
    $auth->setUser($request->user())->logout();

    return response()->noContent();
}

Password reset

Hashes the new password, saves it, and revokes all of the user's access and refresh tokens:

$auth->setUser($user)->resetPassword($newPassword);

Revoking tokens directly

use D076\SanctumRefreshTokens\Services\TokenService;

(new TokenService($user))->deleteCurrentTokens(); // current pair only
(new TokenService($user))->deleteAllTokens();      // every pair

Pruning expired tokens

A console command removes refresh tokens that expired more than --hours ago (default 24):

php artisan sanctum:prune-refresh-expired --hours=0

Schedule it next to Sanctum's own pruning:

use Illuminate\Support\Facades\Schedule;

Schedule::command('sanctum:prune-expired --hours=0')->hourly();
Schedule::command('sanctum:prune-refresh-expired --hours=0')->daily();

Customisation

Custom email / password columns

If your model doesn't use email / password, expose the column names and the package will pick them up:

class User extends AuthenticatableUser
{
    public function getEmailField(): string
    {
        return 'login';
    }

    public function getPasswordField(): string
    {
        return 'pass_hash';
    }
}

Swapping the service implementations

Both services are bound behind interfaces, so you can rebind your own in a service provider:

use D076\SanctumRefreshTokens\Services\IAuthService;
use D076\SanctumRefreshTokens\Services\ITokenService;

$this->app->bind(IAuthService::class, MyAuthService::class);
$this->app->bind(ITokenService::class, MyTokenService::class);

How it works

  • A refresh token is {id}|{token}, where {token} is 40 random characters plus a CRC32b checksum (and the optional sanctum.token_prefix).
  • Only SHA-256({token}) is stored in personal_refresh_tokens.token; the column is hidden from serialization.
  • PersonalRefreshToken::findToken() looks up the row by id, verifies the hash with hash_equals(), and only matches rows whose expires_at is in the future.
  • Each refresh token is linked to the access token it was issued with (access_token_id); deleting the refresh token cascades to that access token via a model observer.
  • The token's abilities (scope) are stored on the refresh token row, so refreshing preserves the scope even after the short-lived access token has been pruned.

Testing

composer install
composer test      # Pest
composer analyse   # PHPStan (level 6)

A Docker setup is included to run the suite (including the PostgreSQL/MySQL matrix):

docker compose run --rm test composer install
docker compose run --rm test vendor/bin/pest

# cross-database
docker compose up -d --wait pgsql mysql
docker compose run --rm -e DB_DRIVER=pgsql test vendor/bin/pest --group=cross-db
docker compose run --rm -e DB_DRIVER=mysql test vendor/bin/pest --group=cross-db

Changelog & License

See CHANGELOG.md. Released under the MIT license.

d076/sanctum-refresh-tokens 适用场景与选型建议

d076/sanctum-refresh-tokens 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 4.23k 次下载、GitHub Stars 达 6, 最近一次更新时间为 2024 年 02 月 29 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「auth」 「laravel」 「sanctum」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 d076/sanctum-refresh-tokens 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 d076/sanctum-refresh-tokens 我们能提供哪些服务?
定制开发 / 二次开发

基于 d076/sanctum-refresh-tokens 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 4.23k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 6
  • 点击次数: 2
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 6
  • Watchers: 1
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2024-02-29