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 Framework Laravel Package

web-token/jwt-framework

A comprehensive PHP library for JSON Web Tokens, implementing JWS, JWE, JWK, JWT, and related RFCs. It offers signing, encryption, key management, and token validation with a clean, standards-compliant API suitable for any PHP project.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a JWT framework, not a one-call JWT helper. You compose four things: an AlgorithmManager (the allow-list), a key (JWK/JWKSet), a builder/verifier, and a serializer. Budget an hour to understand those four pieces; after that, day-to-day use is mostly configuration and policy code.

Install (library only):

composer require web-token/jwt-library

For Symfony, add the bundle instead (web-token/jwt-bundle) and let it wire the factories.

Where to look first:

  1. The official docs at web-token.spomky-labs.com — read JWS, Key Management, Header Checker, Claim Checker, then the Symfony bundle page.
  2. In the code: AlgorithmManager, JWSBuilder, JWSVerifier, CompactSerializer, JWKFactory, ClaimCheckerManager, HeaderCheckerManager.
  3. For Symfony: php bin/console config:dump-reference jose (or jwt on 3.x) before writing any YAML.
  4. If a class is missing after upgrading from 3.x: 4.0 merged many small packages into jwt-core/jwt-library, so namespaces moved. Check the migration guide before assuming a bug.

First use case — issue and verify an HS256 API token:

use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Core\Util\JWKFactory;
use Jose\Component\Signature\Algorithm\HS256;
use Jose\Component\Signature\JWSBuilder;
use Jose\Component\Signature\JWSVerifier;
use Jose\Component\Signature\Serializer\CompactSerializer;

$algorithms = new AlgorithmManager([new HS256()]);          // allow-list, not a registry
$key = JWKFactory::createFromSecret(
    $_ENV['JWT_SECRET'],
    ['alg' => 'HS256', 'use' => 'sig', 'kid' => 'app-2026-01']
);

$jws = (new JWSBuilder($algorithms))
    ->create()
    ->withPayload(json_encode([
        'iss' => 'https://app.example.com',
        'sub' => (string) $user->id,
        'aud' => 'https://api.example.com',
        'iat' => time(),
        'exp' => time() + 900,
    ]))
    ->addSignature($key, ['alg' => 'HS256', 'typ' => 'JWT', 'kid' => 'app-2026-01'])
    ->build();

$token = (new CompactSerializer())->serialize($jws, 0);

Verification is the mirror image, plus the two steps everyone forgets — header checks and claim checks:

$jws = (new CompactSerializer())->unserialize($token);

if (! (new JWSVerifier($algorithms))->verifyWithKey($jws, $key, 0)) {
    throw new RuntimeException('Invalid signature');
}

$claims = json_decode($jws->getPayload(), true, flags: JSON_THROW_ON_ERROR);

// Signature verified. Now enforce policy:
$claimChecker = new ClaimCheckerManager([
    new ExpirationTimeChecker(),
    new IssuedAtChecker(),
    new NotBeforeChecker(),
    new AudienceChecker('https://api.example.com'),
    new IssuerChecker('https://app.example.com'),
]);
$claimChecker->check($claims, ['exp', 'iat', 'iss', 'aud', 'sub']);

Two rules for day one: never put None in the AlgorithmManager, and always call ClaimCheckerManager::check() with mandatory claims — signature verification alone is not authentication.

Implementation Patterns

Wrap the primitives in two services: an issuer and a verifier. The library has no opinion about TTLs, claims, or key lookup. Put that policy in one place. An TokenIssuer with issue(User $user, array $scopes = []): string and a TokenVerifier with verify(string $token): VerifiedToken (or a thrown exception) keeps cryptographic details out of controllers. All the assembling of AlgorithmManager, JWSBuilder, and serializers happens once in a service provider / container definition, not per request.

Verification middleware/authenticator pipeline. Order matters and should be explicit:

  1. Extract the bearer token; reject malformed compact serialization with a 401 (catch the serializer's InvalidArgumentException).
  2. Parse without trusting anything (unserialize gives you headers and payload only).
  3. Resolve the key: use the kid header against your key set. Reject unknown or missing kid unless you have exactly one expected key.
  4. Run header checks (HeaderCheckerManager with AlgorithmChecker bound to your allow-list, and any crit handling).
  5. Verify the signature with JWSVerifier and the resolved key.
  6. Run ClaimCheckerManager with exp/nbf/iat/aud/iss mandatory.
  7. Only then decode user identity and attach it to the request.

Do steps 4–6 even when the signature verifies. In Symfony, put this in a custom Authenticator/listener using JWSVerifierFactory; in Laravel, a middleware resolved from a service provider. Keep the failure response uniform — never leak which check failed.

OIDC / third-party IdP verification (JWKS). This is the highest-leverage use: verifying tokens from Auth0/Keycloak/Entra/etc.

  • Fetch the provider's JWKS once, cache it (PSR-6/PSR-16) keyed by issuer, and honour cache TTLs — do not fetch per request.
  • Select the key by kid, filter by use: sig and key_ops, and ignore keys whose alg conflicts with your allow-list.
  • Build the AlgorithmManager from the issuers you actually trust (RS256, ES256, EdDSA), not from whatever alg the token claims.
  • Accept key rotation: a cached JWKS will eventually miss a new kid. On miss, refetch once (rate-limited), then fail closed.
  • Never fetch jku or x5u URLs from the token header. That is an SSRF and trust-anchor bypass waiting to happen.

Key rotation and publishing a JWKS endpoint. Keep a current signing key and the previous public key(s). Sign with kid in the protected header; expose {"keys":[...]} at /.well-known/jwks.json. JWKSet::createFromJson()/JWKSet plus a loop over all() matching get('kid') is enough for selection. For asymmetric algorithms, JWKFactory can create RSA (createRSAKey), EC (createECKey), and OKP/Ed25519 (createOKPKey) keys; for production, generate private keys offline and load them from a secret store rather than generating at boot.

Use JWE when the payload is sensitive or must be hidden. A JWS is signed, not encrypted — anyone holding the token can read the claims. For PII, use JWE (or nested JWT: sign then encrypt). You need two algorithm managers for JWE: key-encryption (alg, e.g. RSA-OAEP-256, A256KW, ECDH-ES) and content-encryption (enc, e.g. A256GCM). The compact serializer only supports one recipient; multi-recipient or multi-signature needs the JSON serializers.

Separate internal and external token profiles. Internal service-to-service tokens: short TTL, aud set to the target service, Ed25519 or ES256, verified locally against a cached public key. User-facing access tokens: sub, scope, jti, 5–15 minute TTL, refresh handled by an opaque server-side token — not by a long-lived JWT. Different AlgorithmManager instances per profile make accidental cross-acceptance much harder.

Testing pattern. Use fixed test keys (never production keys) and assert negative cases, not just the happy path: tampered payload, wrong aud, expired token, alg: none, unknown kid, HS256 token presented to an RS256 verifier. For time-sensitive claims, generate tokens with explicit iat/exp and either inject a clock into custom checkers or freeze time in the test; avoid sleep().

When not to reach for this package. If you only need "sign an HS256 token and check exp", firebase/php-jwt or lcobucci/jwt will be less code. Choose web-token when you need JWK/JWKS, multiple algorithm families, JWE, standards-complete header/claim checkers, or the Symfony integration. That trade-off is worth stating explicitly in your team's ADR.

Gotchas and Tips

  • Signature valid ≠ token valid. JWSVerifier::verifyWithKey() returns a boolean and does not check exp, nbf, aud, or iss. A token with a valid signature but the wrong audience is still valid cryptographically. Always run ClaimCheckerManager and pass mandatory claims explicitly.
  • Algorithm confusion. The alg header is attacker-controlled. Bind verification to a fixed allow-list (AlgorithmManager + AlgorithmChecker) and, where possible, to the key's own alg. Never include None in a production manager, and never let the token choose the key type.
  • kid handling is your job. The framework does not automatically pick a key from a JWKSet based on the header. Resolve it yourself; reject missing/unknown kid when more than one key is trusted, and reject duplicate kids in a set.
  • A false return tells you nothing. verifyWithKey() returning false is intentional API design, not a debugging aid. Wrap it and log the protected header ($jws->getSignature(0)->getProtectedHeader()), the kid, and the algorithm — never the token or claims.
  • Malformed input throws; invalid crypto returns false. Catch serializer/InvalidArgumentException separately from verification failure, and map both to the same 401 externally.
  • Compact serializer limits. One signature, one recipient. If you need multiple signatures or recipients, use JsonGeneralSerializer/JsonFlattenedSerializer. Mixing serializers between issue and verify is a common source of "valid token rejected" bugs.
  • JWE needs both managers. Forgetting the content-encryption (enc) manager, or omitting the key-wrapping algorithm from the alg manager, produces confusing "algorithm not supported" errors at build time, not at configuration time.
  • Compression is deprecated. Don't design new tokens around zip/DEFLATE; it has been deprecated and creates decompression-bomb risk on verification.
  • Upgrade for the security fixes. Recent 4.x patches bound the PBES2 p2c iteration count and fixed ECDH-ES key derivation. If you accept password-based or ECDH-encrypted tokens from untrusted parties, stay on a patched 4.x release and read the security notes; pinning an old 3.x "because it works" is a liability.
  • Symfony bundle config is version-sensitive. The root key differs between 3.x (jwt) and 4.x (jose in current releases), and the factories only accept algorithms declared in config — calling JWSBuilderFactory::create(['RS512']) when config only enabled RS256 fails at runtime. Run config:dump-reference and cache:clear after changes. Also remember that inline JWKs in YAML end up in the compiled config cache: use env vars/secrets for private material and mark public keys with is_public: true.
  • Clock skew is real. Default claim checkers are strict about exp/nbf. In distributed systems, allow a small tolerance (a few seconds) via custom checker logic or by setting nbf slightly in the past — otherwise you will see intermittent 401s across nodes.
  • Don't put secrets in a JWS. Payloads are base64url, not encrypted. If the claim must not be readable by the client, use JWE or keep it server-side.
  • Extension points are clean — use them. Custom claims belong in a ClaimChecker implementation (one checker per claim, returning the claim names it supports); custom headers in a HeaderChecker. Registering these keeps all policy in one pipeline instead of scattered if statements in controllers.
  • Watch token size. Headers and claims inflate every request. Keep kid, typ, and alg minimal, avoid embedding roles/permissions lists that can grow unbounded, and prefer scope strings plus a server-side lookup for large authorization data.
  • Performance is fine if you cache. Algorithm managers and keys are cheap to reuse; remote JWKS fetching is not. Construct services once, cache the key set, and reuse the JWSVerifier — do not rebuild the manager or refetch keys per request.
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.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels