NovinkaZverejnili sme 50+ Laravel balíkov ako open source
Custom AI apps, agents and automation — Roundly ConsultingRoundly
Všetky balíky
JWT for Laravel

Testovanie

Jwt::fake() nahradí manager zaznamenávajúcim fakom nad párom RSA kľúčov v pamäti — súbory s kľúčmi netreba a prázdne jwt.issuer, jwt.audience či servisné tajomstvo doplní testovacími hodnotami. Denylist balíka presunie do súkromného cache store v pamäti, takže Redis (ani nič, čo uvádza jwt.denylist.store) netreba; vašu vlastnú väzbu Denylist nechá tak. Tokeny sa naďalej skutočne podpisujú aj overujú a každé vydanie, servisný token aj zakázanie sa zaznamená — cez fasádu, vložený JwtManager, guard(), services() aj denylist():

use App\Models\User;
use RoundlyConsulting\Jwt\Facades\Jwt;
use RoundlyConsulting\Jwt\Jose\Claims;
use RoundlyConsulting\Jwt\UserTokens\AccessTokenRequest;

it('mints, calls billing and logs out', function (): void {
    $fake = Jwt::fake();   // in-memory RSA keys — no key files

    $issued = Jwt::mintAccessToken(AccessTokenRequest::for('42'));
    Jwt::services()->issue('billing', ['job' => 'sync']);
    Jwt::logout($issued);

    $fake->assertMinted();                                                  // any user token
    $fake->assertMinted(fn (Claims $claims): bool => $claims->string('sub') === '42');
    $fake->assertServiceTokenIssued('billing');
    $fake->assertServiceTokenIssued('billing', fn (Claims $claims): bool => $claims->get('job') === 'sync');
    $fake->assertDenied($issued->jti);
    $fake->assertDenied();                                                  // any jti

    expect($fake->minted())->toHaveCount(1);   // list<RecordedToken> — ->token, ->claims
});

it('serves the profile to an authenticated user', function (): void {
    $fake = Jwt::fake();
    $user = User::factory()->create();

    // Authenticate a jwt guard for the rest of the test — no hand-built token:
    $fake->actingAs(['sub' => (string) $user->id, 'permissions' => ['posts.edit']], 'api');

    $this->getJson('/me')->assertOk();

    $fake->assertNothingMinted();            // actingAs() is not recorded as a mint
    $fake->assertNothingIssuedToServices();
    $fake->assertNothingDenied();
});

Asercie

AserciaNegatívnaPrejde, keď
assertMinted(?Closure $where = null)assertNothingMinted()Vydal sa používateľský token — pri callbacku taký, ktorého Claims mu vyhovujú — cez fasádu, vložený JwtManager alebo guard().
assertServiceTokenIssued(?string $audience = null, ?Closure $where = null)assertNothingIssuedToServices()Vydal sa servisný token — pre dané audience a vyhovujúci callbacku, ak ich zadáte — vrátane request() a authenticate().
assertDenied(?string $jti = null)assertNothingDenied()Do denylistu sa dostal nejaký jti (alebo práve tento) — cez denylist()->deny() / denyToken(), logout() či denyClaims().

$fake->minted(), serviceTokens() a denied() vrátia zaznamenané údaje — objekty RecordedToken s token a claims a zakázané jti.

Prihlásenie guardu

actingAs($claims, $guard) vydá skutočný token pre audience a scope guardu — nezaznamená sa ako vydanie — a pošle ho ako bearer každého ďalšieho requestu, ktorý nemá vlastnú hlavičku Authorization. Vráti IssuedToken, napríklad pre $this->withToken(). Pri Eloquent provideri musí byť sub kľúč skutočného používateľa; guard s token_version potrebuje zodpovedajúci tv.

actingAs() z Laravelu

Aj actingAs() z Laravelu funguje na oboch guardoch, úplne bez tokenu — guard si nastaveného používateľa podrží až do Auth::guard('api')->forgetUser():

$this->actingAs($user, 'api')->getJson('/me')->assertOk();          // Eloquent or TokenUser
$this->actingAs($serviceIdentity, 'service')->postJson('/internal/sync')->assertOk();

Jwt::guard('api')->claims() vráti claimy nastaveného TokenUser (a Jwt::services()->claims() claimy nastavenej ServiceIdentity); iný takto nastavený používateľ claimy nemá. Jwt::fake() volajte pred actingAs() z Laravelu — fake guardy znova zostaví a skôr nastaveného používateľa zahodí — a neskoršie $fake->actingAs() ho nahradí.

Testy so skutočnými kľúčmi

Ak chcete otestovať vlastnú konfiguráciu kľúčov, testujte so skutočnými tokenmi. Raz vygenerujte dočasný pár kľúčov pre testy — relatívne cesty sa odvodzujú od koreňa aplikácie:

JWT_PRIVATE_KEY_PATH=tests/keys/jwt-private.pem \
JWT_PUBLIC_KEY_PATH=tests/keys/jwt-public.pem \
php artisan jwt:generate-keys

Používateľské tokeny a guard jwt

Piny a kľúče nastavte v každom teste, denylist držte v pamäti cez cache store array a overujte cez HTTP vrstvu:

use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Route;
use RoundlyConsulting\Jwt\Events\UserTokenIssued;
use RoundlyConsulting\Jwt\Facades\Jwt;
use RoundlyConsulting\Jwt\Jose\Exceptions\TokenExpired;
use RoundlyConsulting\Jwt\UserTokens\AccessTokenRequest;

beforeEach(function (): void {
    config([
        'jwt.issuer' => 'jwt-issuer',
        'jwt.audience' => 'web',
        'jwt.private_key_path' => base_path('tests/keys/jwt-private.pem'),
        'jwt.public_key_path' => base_path('tests/keys/jwt-public.pem'),
        'jwt.denylist.store' => 'array',
        'auth.guards.api' => ['driver' => 'jwt'],
    ]);

    Route::middleware('auth:api')->get('/me', fn () => ['id' => auth()->id()]);
});

afterEach(function (): void {
    CarbonImmutable::setTestNow();
});

it('authenticates a bearer token and rejects it after logout', function (): void {
    $issued = Jwt::mintAccessToken(AccessTokenRequest::for('user-1'));

    $this->withToken($issued->token)->getJson('/me')
        ->assertOk()
        ->assertJson(['id' => 'user-1']);

    Jwt::logout($issued);

    $this->withToken($issued->token)->getJson('/me')->assertUnauthorized();
});

it('rejects an expired token', function (): void {
    $issued = Jwt::mintAccessToken(AccessTokenRequest::for('user-1')->ttl(60));

    CarbonImmutable::setTestNow(CarbonImmutable::now()->addMinutes(5));

    expect(fn () => Jwt::verify($issued->token))->toThrow(TokenExpired::class);
});

it('dispatches UserTokenIssued', function (): void {
    Event::fake();

    $issued = Jwt::mintAccessToken(AccessTokenRequest::for('user-1'));

    Event::assertDispatched(UserTokenIssued::class, fn (UserTokenIssued $e): bool => $e->jti === $issued->jti);
});

Časové kontroly čítajú CarbonImmutable::now(), takže CarbonImmutable::setTestNow() posunie token za expiráciu — po každom teste čas vráťte späť.

Servisné tokeny

Nastavte tajomstvo s aspoň 32 bajtmi, názov tejto služby a guard service-jwt, potom overujte prichádzajúce volania aj odchádzajúce hlavičky cez Http::fake():

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route;
use RoundlyConsulting\Jwt\Facades\Jwt;

beforeEach(function (): void {
    config([
        'jwt.service.name' => 'notifications',
        'jwt.service.secret' => 'a-very-secret-service-key-0123456789',
        'jwt.service.issuer' => 'billing',
        'auth.guards.service' => ['driver' => 'service-jwt'],
    ]);
});

it('accepts a signed internal call', function (): void {
    Route::middleware('auth:service')->get('/internal', fn () => ['iss' => auth()->id()]);

    $token = Jwt::services()->issue('notifications')->token;

    $this->withToken($token)->getJson('/internal')
        ->assertOk()
        ->assertJson(['iss' => 'billing']);
});

it('signs outbound internal calls', function (): void {
    Http::fake();

    Jwt::services()->request('notifications')->post('https://notifications.internal/send', ['id' => 1]);

    Http::assertSent(fn ($request) => $request->hasHeader('Authorization'));
});

Vlastná sada testov balíka sa spúšťa cez composer test:

composer test

Prejavte lásku k open source

Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.

Ďalšie spôsoby podpory vrátane kryptomien

Odoslaním daru súhlasíte s našimi podmienkami prijímania darov.

Chcete to zabudovať do svojho produktu?

Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.