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

Laravel Jwt Laravel Package

a2workspace/laravel-jwt

JWT authentication for Laravel: issue and validate JSON Web Tokens for stateless API auth. Plugs into Laravel's auth guards and middleware to protect routes, with configurable signing keys, token lifetimes, and claims for flexible, token-based access control.

View on GitHub
Deep Wiki
Context7

Getting Started

  • composer require a2workspace/laravel-jwt — it pulls in tymon/jwt-auth:^2.2, so your PHP and laravel/framework versions must satisfy that dependency first. On Laravel 9+ this is usually fine; on older 8.x apps check before committing.
  • Publish the upstream config and mint a secret: php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider" then php artisan jwt:secret. The result is a JWT_SECRET in .env and a config/jwt.php you own from now on.
  • Add the guard in config/auth.php: an api guard with 'driver' => 'jwt' and the users provider pointing at App\Models\User. The model must implement Tymon\JWTAuth\Contracts\JWTSubject (getJWTIdentifier() returning $this->getKey(), getJWTCustomClaims() usually returning []).
  • Protect routes with Route::middleware('auth:api')->get('/me', ...).
  • First smoke test — a POST /api/login action returning ['access_token' => auth('api')->attempt($credentials), 'token_type' => 'bearer', 'expires_in' => auth('api')->factory()->getTTL() * 60], then GET /api/me with Authorization: Bearer <token>. If both work, the wiring is correct and you can stop reading setup docs.
  • Where to look first when it 401s: config/auth.php (guard driver, provider, defaults) and config/jwt.php (TTL, required_claims, blacklist). Almost every "valid token rejected" report lives in one of those two files.

Implementation Patterns

One small auth controller, four actions. login, me, refresh, logout, all going through the guard rather than the facade so the right provider resolves: $this->guard()->attempt(...), ->user(), ->refresh(), ->logout(). logout() only actually invalidates anything when jwt.blacklist_enabled is true — if you want real revocation, turn it on.

Mint from a known user when you already have one. After registration, an OAuth callback, or a password reset, you hold a User instance — use JWTAuth::fromUser($user) instead of round-tripping credentials through attempt(). Same call is the cleanest way to make tokens in seeders, factories, and tests.

Refresh is a rotation, not a renewal. With blacklist_enabled = true, refresh() returns a new token and blacklists the old one. Clients must overwrite their stored token on every refresh; anything that treats refresh as advisory will replay the old token and start getting TokenBlacklistedException. Set jwt.refresh_ttl meaningfully higher than jwt.ttl or the refresh window is a fiction.

Handle expiry separately. Catch TokenExpiredException apart from TokenInvalidException/JWTException so clients get a distinct "refresh now" signal instead of a generic 401. In Laravel 9+ register this as a renderable in bootstrap/app.php (or the exception Handler) rather than wrapping every controller action in try/catch.

Don't hit the login endpoint in feature tests. Build the user, get a token via JWTAuth::fromUser($user), and attach it with $this->withHeader('Authorization', "Bearer {$token}")->getJson(...). For expiry behaviour use Carbon::setTestNow() or JWTAuth::setTTL(0) — never sleep().

Reset the guard in queue workers and Octane. The resolved guard survives between jobs, so token/user state leaks. Call auth('api')->setUser($user) or JWTAuth::setToken(...) explicitly at the top of each job, or forget the guard.

Per-guard lifespans. Once ttl/refresh_ttl exist in the guard's config/auth.php entry, the guard reads them from there rather than config/jwt.php. Use this for separate mobile (30 days) and admin (2 hours) token lifetimes instead of mutating global config at runtime.

Gotchas and Tips

  • This package is a thin wrapper, not the auth engine. Everything that actually matters is tymon/jwt-auth 2.x; when you hit real behaviour questions, read upstream docs and source, not this repo. With 4 stars and a single-line 2.2.0 release ("bump the dependency"), treat it as a convenience layer with a low bus factor — if you only need the dependency plus config, depending on tymon/jwt-auth directly removes a maintenance risk with zero functional loss.
  • Blacklist storage is load-bearing when enabled. It lives in your cache store: a cache flush silently un-blacklists logged-out tokens, and the file driver makes invalidation unreliable across multiple app servers. Use Redis or the database store in production.
  • The iat grace period bites immediate callers. Refresh or invalidate right after issuance and you can get a blacklist error because iat equals "now". jwt.blacklist_grace_period exists for exactly this; the default of 0 is too tight for some clients.
  • Config caching hides .env edits. php artisan config:cache bakes JWT_SECRET and TTLs into compiled config — a "missing secret" after deploy is nearly always stale cached config. php artisan config:clear after any env change.
  • Never use the default web guard. Auth::attempt() hits the session guard and will not issue a JWT, and auth:api returning 401 with a visibly correct token usually means defaults.guard or a provider mismatch in config/auth.php.
  • Custom claims are sent on every request. Large payloads blow past proxy header limits (~8 KB) and produce intermittent 400s that look nothing like auth failures. Keep roles/permissions out of the token and resolve them from the DB on me.
  • Clock skew shows up as a one-environment 401. Set jwt.leeway when the issuer and verifier are different hosts; nbf/iat drift is the usual cause of "works locally, fails in staging".
  • Redact the Authorization header before request logging reaches production. Default middleware in several packages will happily write tokens to disk.
  • Upgrading: don't let Composer overwrite your published config/jwt.php. Publish --force to a scratch directory and diff — the 2.x line reworked keys (providers.*.storage, providers.jwt.lock_subject, required_claims), and a stale config can boot fine and then fail at validation time.
  • Extension points worth knowing: swap blacklist storage via jwt.providers.storage, override the claim factory for custom iss/aud, and use JWTSubject::getJWTCustomClaims() for anything you need before a DB hit. Resist wrapping the guard in your own abstraction — it generally breaks refresh() semantics for very little gain.
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.
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk