定制 sopheak/sp-jwt-auth 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

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

sopheak/sp-jwt-auth

Composer 安装命令:

composer require sopheak/sp-jwt-auth

包简介

First-party JWT access and rotating refresh token authentication for Laravel apps.

README 文档

README

Latest Stable Version Total Downloads PHP Version License Security

sopheak/sp-jwt-auth is a modular Laravel authentication package for first-party JWT APIs, rotating opaque refresh tokens, account security workflows, API keys, external identity links, and optional OAuth server mode.

The package owns authentication infrastructure. Your application still owns password login, registration, user creation, tenants, roles, UI, response shape, delivery templates, and business authorization policy.

Features

Module What it provides Default
Core JWT sp-jwt guard, signed JWT access tokens, persisted jti, opaque rotating refresh tokens, scopes, claims, revocation, key rotation, JWKS, events, hooks Enabled
Account Security MFA challenge broker, hashed OTP codes, email verification tokens, password reset tokens, app-owned sender contracts Disabled
API Keys Scoped integration keys with public-id lookup, HMAC secret validation, rotation, revocation, IP restrictions, middleware Disabled
External Identity Normalized Socialite/OIDC-style identity DTO, provider contract, external identity storage Disabled
OAuth Server Separate sp_oauth_* storage, clients, consents, authorization-code + PKCE, refresh tokens, client credentials, revocation, introspection, resource middleware Disabled

Requirements

  • PHP ^8.3|^8.4|^8.5
  • Laravel ^12.0|^13.0
  • firebase/php-jwt
  • RSA signing keys for the default RS256 setup

Optional integrations are kept in Composer suggest:

  • laravel/socialite
  • socialiteproviders/manager
  • league/oauth2-client
  • league/oauth2-server

Stability

This package is pre-1.0. APIs, config keys, and optional module behavior may change before v1.0.0. Pin a tagged version in production and review the changelog before upgrading.

Installation

The package is public on Packagist: sopheak/sp-jwt-auth.

Install it with Composer:

composer require sopheak/sp-jwt-auth
php artisan sp-jwt-auth:setup --keys
php artisan migrate
php artisan sp-jwt-auth:validate

The setup command publishes config and migrations, attempts to add the Laravel api guard, generates local PEM signing keys with --keys, and writes the related JWT key paths and refresh hash secret to .env. If your config/auth.php is custom, add the guard manually:

'guards' => [
    'api' => [
        'driver' => 'sp-jwt',
        'provider' => 'users',
    ],
],

Keep Laravel's normal web guard for Blade, Livewire, Inertia, and session pages.

For local path testing while developing the package:

composer config repositories.sp-jwt-auth '{"type":"path","url":"/absolute/path/to/sp-jwt-auth","options":{"versions":{"sopheak/sp-jwt-auth":"0.1.0"}}}'
composer require sopheak/sp-jwt-auth:^0.1

Configuration

Publish the config when needed:

php artisan vendor:publish --tag=sp-jwt-auth-config

Common environment keys:

SP_JWT_GUARD=api
SP_JWT_USER_PROVIDER=users
SP_JWT_ISSUER=https://app.example.com
SP_JWT_AUDIENCE=app-api
SP_JWT_ALGORITHM=RS256
SP_JWT_ACCESS_TTL_MINUTES=15
SP_JWT_REFRESH_TTL_DAYS=60
SP_JWT_REUSE_DETECTION=revoke_session
SP_JWT_ACTIVE_KID=2026-06-primary
SP_JWT_PRIVATE_KEY_PATH=storage/jwt-private-2026-06-primary.pem
SP_JWT_PUBLIC_KEY_PATH=storage/jwt-public-2026-06-primary.pem
SP_JWT_HASH_KEY_ID=default
SP_JWT_REFRESH_HASH_KEY=your-random-refresh-hash-secret

Optional modules have their own config sections:

  • mfa
  • email_verification
  • password_reset
  • api_keys
  • external_identities
  • oauth_server

Quick Start

Create login and refresh endpoints in your Laravel app. Your app owns credential validation; the package owns token issuing, refresh rotation, and token response formatting.

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Route;
use Illuminate\Validation\ValidationException;
use Sopheak\JwtAuth\DTO\TokenContext;
use Sopheak\JwtAuth\Services\JwtTokenService;
use Sopheak\JwtAuth\Support\TokenResponse;

Route::post('/login', function (Request $request, JwtTokenService $jwt) {
    $credentials = $request->validate([
        'email' => ['required', 'email'],
        'password' => ['required', 'string'],
    ]);

    $user = User::query()->where('email', $credentials['email'])->first();

    if (! $user || ! Hash::check($credentials['password'], $user->password)) {
        throw ValidationException::withMessages([
            'email' => ['The provided credentials are incorrect.'],
        ]);
    }

    $pair = $jwt->issueTokenPair(
        $user,
        TokenContext::make()->scopes(['profile.read']),
    );

    return TokenResponse::passportCompatible($pair);
});

Route::post('/refresh', function (Request $request, JwtTokenService $jwt) {
    $data = $request->validate([
        'refresh_token' => ['required', 'string'],
    ]);

    return TokenResponse::passportCompatible(
        $jwt->rotateRefreshToken($data['refresh_token']),
    );
});

Route::middleware('auth:api')->get('/me', function (Request $request) {
    return $request->user();
});

Call protected routes with the returned access token:

Authorization: Bearer <access-token>

Core JWT Usage

Your app validates credentials, resolves a user, builds a TokenContext, then asks the package to issue tokens.

use Sopheak\JwtAuth\DTO\TokenContext;
use Sopheak\JwtAuth\Services\JwtTokenService;
use Sopheak\JwtAuth\Support\TokenResponse;

$pair = app(JwtTokenService::class)->issueTokenPair(
    $user,
    TokenContext::make()
        ->companyId(42)
        ->companyIds([42, 84])
        ->scopes(['invoices.read', 'invoices.write'])
        ->impersonated(false),
);

return TokenResponse::passportCompatible($pair);

Read claims from the authenticated token:

$token = $request->user()?->token();

$companyId = $token?->claim('company_id');
$claims = $token?->claims ?? [];

For response fields owned by the app, pass extra data to the response helper or register a response extension:

return TokenResponse::passportCompatible($pair, [
    'company_id' => $pair->accessTokenRecord->companyId(),
]);

TokenResponse::extend(function (array $response, TokenPair $pair): array {
    $response['company_id'] = $pair->accessTokenRecord->companyId();
    $response['impersonated'] = $pair->accessTokenRecord->isImpersonated();

    return $response;
});

Protect routes with Laravel auth middleware:

Route::middleware(['auth:api'])->get('/me', MeController::class);

Route::middleware(['auth:api', 'sp.jwt.scope:invoices.read'])
    ->get('/invoices', InvoiceIndexController::class);

Add Passport-like helpers to user models:

use Sopheak\JwtAuth\Traits\HasJwtTokens;

class User extends Authenticatable
{
    use HasJwtTokens;
}
$request->user()->token();
$request->user()->tokenCan('invoices.read');

Refresh and Revocation

Refresh tokens are returned as id.secret. Only the HMAC hash of the secret is stored.

$pair = app(JwtTokenService::class)->rotateRefreshToken(
    $request->input('refresh_token'),
);

Revoke one access token, one session, or all sessions for a user:

$token = $request->user()->token();

app(JwtTokenService::class)->revokeAccessToken($token->id);
app(JwtTokenService::class)->revokeSession($token->session_id);
app(JwtTokenService::class)->revokeAllForUser($request->user());

Account Security

Account security brokers can be called from controllers, Livewire actions, queued jobs, or service classes. Delivery is app-owned through sender contracts.

use Sopheak\JwtAuth\DTO\OtpDestination;
use Sopheak\JwtAuth\Services\EmailVerificationBroker;
use Sopheak\JwtAuth\Services\MfaChallengeBroker;
use Sopheak\JwtAuth\Services\OtpChallengeBroker;
use Sopheak\JwtAuth\Services\PasswordResetBroker;

$challenge = app(MfaChallengeBroker::class)->create($user, TokenContext::make());

$otp = app(OtpChallengeBroker::class)->createOtp(
    $challenge,
    new OtpDestination('email', 'user@example.com', 'u***@example.com'),
);

$context = app(OtpChallengeBroker::class)->verifyOtp($challenge->id, $otp->plaintextCode);

$verification = app(EmailVerificationBroker::class)
    ->createVerificationToken($user, $user->email);

$verified = app(EmailVerificationBroker::class)
    ->verifyEmailToken($verification->token);

$reset = app(PasswordResetBroker::class)->createResetToken($user, $user->email);
$result = app(PasswordResetBroker::class)->consumeResetToken($reset->token);

Available sender contracts:

  • OtpChannelSender
  • EmailVerificationSender
  • PasswordResetSender

API Keys

API keys are for third-party integrations and machine clients. The full plaintext key is returned only at creation or rotation time.

use Sopheak\JwtAuth\DTO\ApiKeyContext;
use Sopheak\JwtAuth\Services\ApiKeyService;

$key = app(ApiKeyService::class)->createApiKey(ApiKeyContext::forCompany(
    companyId: 42,
    name: 'QuickBooks sync worker',
    scopes: ['qbo.sync', 'invoices.write'],
));

Protect integration routes:

Route::middleware(['sp.api_key', 'sp.api_key.scope:invoices.write'])
    ->post('/integrations/invoices', IntegrationInvoiceController::class);

Rotate or revoke:

$rotated = app(ApiKeyService::class)->rotateApiKey($apiKeyId);
app(ApiKeyService::class)->revokeApiKey($apiKeyId);
app(ApiKeyService::class)->revokeApiKeysForOwner('tenant', '42');

External Identity

External identity support normalizes provider profiles. The app decides whether to link, create, or deny a local user.

use Sopheak\JwtAuth\DTO\ExternalIdentity;
use Sopheak\JwtAuth\Services\ExternalIdentityStore;

app(ExternalIdentityStore::class)->store(new ExternalIdentity(
    provider: 'google',
    providerUserId: $providerUser->getId(),
    email: $providerUser->getEmail(),
    emailVerified: true,
    name: $providerUser->getName(),
    rawProfile: $providerUser->user,
), $user);

Provider adapters can implement Sopheak\JwtAuth\Contracts\ExternalIdentityProvider.

OAuth Server Mode

OAuth server mode is disabled by default and uses separate sp_oauth_* tables. It is for third-party clients, not normal first-party SPA/mobile login.

SP_JWT_OAUTH_SERVER_ENABLED=true

Create a client:

use Sopheak\JwtAuth\DTO\OAuthClientData;
use Sopheak\JwtAuth\Services\OAuthClientRepository;

$client = app(OAuthClientRepository::class)->createClient(new OAuthClientData(
    name: 'ERP Connector',
    redirectUris: ['https://client.example/callback'],
    allowedGrants: ['authorization_code', 'refresh_token'],
    allowedScopes: ['invoices.read'],
));

Protect OAuth resource routes:

Route::middleware(['sp.oauth', 'sp.oauth.scope:invoices.read'])
    ->get('/partner/invoices', PartnerInvoiceController::class);

OAuth client-credentials tokens authenticate as clients, not users.

Middleware

Middleware Purpose
sp.jwt Authenticate with the configured first-party JWT guard
sp.jwt.scope:<scope> Require every listed JWT scope
sp.jwt.any_scope:<scope1>,<scope2> Require any listed JWT scope
sp.api_key Authenticate an API key bearer token
sp.api_key.scope:<scope> Require every listed API key scope
sp.api_key.any_scope:<scope1>,<scope2> Require any listed API key scope
sp.oauth Authenticate an OAuth resource token
sp.oauth.scope:<scope> Require every listed OAuth scope
sp.oauth.any_scope:<scope1>,<scope2> Require any listed OAuth scope
sp.oauth.client:<client_id> Restrict OAuth access to a client id

Commands

php artisan sp-jwt-auth:install --keys
php artisan sp-jwt-auth:setup --keys
php artisan sp-jwt-auth:validate
php artisan sp-jwt-auth:keys --generate --kid=2026-06-primary
php artisan sp-jwt-auth:jwks --pretty
php artisan sp-jwt-auth:prune --expired-days=30 --revoked-days=30

sp-jwt-auth:keys --generate and --rotate update .env by default with SP_JWT_ACTIVE_KID, SP_JWT_PRIVATE_KEY_PATH, and SP_JWT_PUBLIC_KEY_PATH. They also create SP_JWT_REFRESH_HASH_KEY when it is missing, without replacing an existing refresh hash secret. Use --no-write-env when your deployment manages environment values outside Artisan.

Events and Hooks

The package emits lifecycle events for:

  • Token issue, refresh, revocation, sessions, and refresh reuse detection.
  • MFA, OTP, email verification, and password reset.
  • API key creation, use, revocation, and rotation.
  • External identity resolution.
  • OAuth clients, consents, authorization approval, token issue, and token revocation.

HookRegistry supports token-context validation, token-context mutation, and after-issue hooks for app-owned policy.

Security Notes

  • JWTs are signed with package signing keys, never APP_KEY.
  • JWKS exposes public keys only.
  • Refresh tokens, OTP codes, verification tokens, reset tokens, API keys, OAuth client secrets, and OAuth opaque tokens are stored as HMAC hashes.
  • Refresh rotation runs in a transaction and detects reuse.
  • OAuth tokens use separate storage and middleware from first-party JWT tokens.
  • Optional modules are disabled by default and can be enabled incrementally.

Documentation

Development

composer install
composer quality

composer quality runs Rector dry-run, PHPStan, and PHPUnit.

Release

Packagist versions are created from Git tags:

git tag v1.0.0
git push origin v1.0.0

Packagist is already configured at packagist.org/packages/sopheak/sp-jwt-auth. After pushing a new tag, Packagist makes the release available to Composer.

Community

  • Use GitHub Issues for reproducible bugs and focused feature requests.
  • Use GitHub Discussions for questions, roadmap ideas, and integration help.
  • See SUPPORT.md for support channels and security boundaries.
  • Report vulnerabilities through GitHub Security Advisories or the process in SECURITY.md.
  • Contributions are welcome. See CONTRIBUTING.md and CODE_OF_CONDUCT.md.

License

This package is open-source software licensed under the MIT license.

统计信息

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

GitHub 信息

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

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-06-15

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固