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

Jwt Laravel Package

lcobucci/jwt

A simple, RFC 7519-compliant PHP library for creating, signing, and verifying JSON Web Tokens and JSON Web Signatures, with support for multiple signing algorithms and a clean, well-documented API.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is a standalone JWT implementation — it does not ship Laravel service providers, facades, or a guard. You wire it into Laravel yourself. That is the whole integration surface, and it is small.

composer require lcobucci/jwt:^5.6

Requires PHP 8.1+ (5.x dropped PHP 7 support). You will also want lcobucci/clock (pulled in transitively) for testable time handling.

Look here first: Lcobucci\JWT\Configuration. Every 5.x workflow starts from one of three factories — forSymmetricSigner(), forAsymmetricSigner(), forUnsecuredSigner() (tests only). It bundles the parser, signer, keys, and validator so you never pass keys around by hand.

Issue a token:

use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InsecureKey;

$config = Configuration::forSymmetricSigner(
    new Sha256(),
    InsecureKey::plainText(config('jwt.secret')) // InsecureKey = "I know this is a shared secret"
);

$now = new DateTimeImmutable();

$token = $config->builder()
    ->issuedBy(config('app.url'))
    ->permittedFor('mobile-app')
    ->identifiedBy((string) Str::uuid())
    ->issuedAt($now)
    ->expiresAt($now->modify('+15 minutes'))
    ->withClaim('uid', $user->id)
    ->getToken($config->signer(), $config->signingKey());

return $token->toString();

Parse and assert:

use Lcobucci\JWT\Token\UnencryptedToken;
use Lcobucci\JWT\Validation\Constraint;
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
use Lcobucci\Clock\SystemClock;

$token = $config->parser()->parse($jwt);

assert($token instanceof UnencryptedToken); // gives you claims()

try {
    $config->validator()->assert(
        $token,
        new Constraint\SignedWith($config->signer(), $config->verificationKey()),
        new Constraint\IssuedBy(config('app.url')),
        new Constraint\PermittedFor('mobile-app'),
        new Constraint\StrictValidAt(SystemClock::fromUTC()),
    );
} catch (RequiredConstraintsViolated $e) {
    // $e->violations() => array of ConstraintViolation
    abort(401);
}

$uid = $token->claims()->get('uid');

First use case: replace an ad-hoc signed string or a session lookup for a stateless API. Issue short-lived access tokens for your SPA/mobile client, validate in middleware, keep uid/role as claims.

Implementation Patterns

  • Bind Configuration as a singleton. Register it in a service provider and inject it — rebuilding it per request re-reads and re-parses key files. Choose the factory from config:

    $this->app->singleton(Configuration::class, fn () => config('jwt.algorithm') === 'RS256'
        ? Configuration::forAsymmetricSigner(
              new Signer\Rsa\Sha256(),
              InMemory::file(config('jwt.private_key_path')),
              InMemory::file(config('jwt.public_key_path')),
          )
        : Configuration::forSymmetricSigner(new Signer\Hmac\Sha256(), InsecureKey::plainText(config('jwt.secret')))
    );
    
  • A TokenIssuer service, not inline builder calls. Inject Configuration plus a TTL config; expose issue(User $user, array $extraClaims = []). Controllers and jobs depend on this, never on Builder directly.

  • A TokenValidator service + middleware. One class owns parse → assert → return a small DTO (userId, jti, claims). Middleware catches the three exception families (decode, structure, constraint) and maps them all to a single opaque 401 — do not leak why validation failed.

  • Compose constraints rather than writing checks. SignedWith, IssuedBy, PermittedFor, IdentifiedBy, RelatedTo, HasClaim, StrictValidAt/LooseValidAt — pass them as variadic args to assert(). Build your own by implementing Lcobucci\JWT\Validation\Constraint (one method: assert(Token $token): void), e.g. a RequiresScope('invoices:read') constraint. This is the cleanest extension point the package offers.

  • Asymmetric keys for anything crossing a trust boundary. RS256/ES256/EdDSA so resource servers verify with a public key only. Load PEMs from a path outside the repo or a secrets manager; never commit private_key_path contents into config/.

  • jti + cache for revocation. On logout/rotation, Cache::put("revoked:{$jti}", true, $token->claims()->get('exp') - now()). Add a TokenNotRevoked constraint so revocation is enforced by the same assert() call as everything else.

  • Test with forUnsecuredSigner() and a frozen clock. lcobucci/clock's FrozenClock lets you assert expiry/nbf behaviour deterministically:

    $config = Configuration::forUnsecuredSigner();
    $clock  = new FrozenClock(new DateTimeImmutable('2025-01-01 00:00:00'));
    

    Remember forUnsecuredSigner() produces genuinely unsigned tokens — guard against it leaking into a production binding (e.g. an if (app()->isProduction()) throw in the provider).

  • Refresh tokens over long-lived access tokens. Keep access TTLs in minutes, pair with a revocable refresh token stored server-side (DB or Redis) referenced by jti.

  • Version-pin deliberately. "lcobucci/jwt": "^5.6.1" — 5.6.1 is a hardening patch (see Gotchas); anything below it leaves you with a 500-on-bad-input path when a hostile or buggy client sends a malformed token.

Gotchas and Tips

  • The 3.x/4.x API is gone. Builder::sign(), Parser, ValidationData, $token->validate(), $token->verify() all no longer exist. If you are porting an older integration: signing happens inside getToken($signer, $key), verification is the SignedWith constraint, and standard-claim checking is StrictValidAt/LooseValidAt. Most stale tutorials you will find online are written against 3.x.

  • parser()->parse() returns Token, not UnencryptedToken. claims() only exists on UnencryptedToken, so assert($token instanceof UnencryptedToken) (or an explicit instanceof check in production code) before touching claims.

  • Parse-time errors vs. validation errors are different exception families. InvalidTokenStructure, CannotDecodeContent, and UnsupportedHeaderFound are thrown by the parser; RequiredConstraintsViolated by the validator. Catch both — a single catch (Throwable) around the whole block is fine only if it maps to 401 and logs, never re-throws as 500.

  • 5.6.1 changes exactly two of those paths — review your error handling. Non-numeric date claims (exp: "soon") now throw InvalidTokenStructure at parse time instead of failing later or surfacing an odd exception, and malformed ECDSA/EdDSA signatures (wrong-length, garbage bytes) now fail verification cleanly instead of throwing an unhandled low-level error. Both are correctness/DoS fixes: if your monitoring shows 500s from token endpoints, those were attacker- or bug-triggered, and they become clean 401s on 5.6.1. Practical consequence: make sure InvalidTokenStructure and RequiredConstraintsViolated are both mapped, and that you never treat "parse threw" as "server error".

  • Do validation in UTC, always. SystemClock::fromUTC() (or SystemClock::fromSystemTimezone() if you really know what you are doing). Claims are numeric timestamps; passing Carbon::now() with a non-UTC timezone into issuedAt()/expiresAt() shifts every window. Never build timestamps with strtotime()/mktime() and no timezone.

  • Clock skew is real — use leeway, not longer TTLs. new LooseValidAt($clock, new DateInterval('PT30S')) in place of StrictValidAt absorbs a few seconds of drift between issuers; StrictValidAt is zero-tolerance and will reject perfectly good tokens from a slightly fast client clock.

  • claims()->get() returns mixed and throws when the claim is absent. Do not assume uid exists because you issued it — tokens from older deploys, or an Unsecured/re-signed token, may not carry it. Check $token->claims()->has('uid') or wrap, and cast explicitly ((int)) so a string claim cannot smuggle into a typed constructor.

  • Symmetric keys: InsecureKey, not InMemory. The InsecureKey type exists to make shared-secret usage obvious in review. For asymmetric keys load PEM contents via a Key implementation reading from a path — and pass a Key object, never a raw string, so a truncated/misread file fails loudly instead of producing a subtly wrong signature.

  • JWTs are stateless; "logout" is a server-side concept. Without a jti denylist (or short TTLs + refresh token rotation) a stolen token stays valid until exp. Also: revocation lookups must be TTL'd to exp, or your cache grows forever.

  • Never log the raw token or key material. Log jti, sub/uid, iss, and the constraint violation — not the compact string. Add authorization to whatever request-logging scrubber you use; Laravel's default does not redact it.

  • Rate-limit issuance and validation endpoints. Signing is CPU work (especially RSA/ECDSA) and an unauthenticated token endpoint is a free oracle. RateLimiter + throttle middleware on issue/refresh routes.

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