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

Simplejwt Laravel Package

kelvinmo/simplejwt

SimpleJWT is a PHP library for JSON Web Tokens, signatures, and encryption. It supports JWK and COSE keys, HMAC/RSA/ECDSA/EdDSA signatures, key management, and AES encryption. Requires PHP 8+ with gmp, hash, openssl, and sodium for EdDSA/X25519.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require kelvinmo/simplejwt
    

    Ensure your composer.json includes PHP 8.0+ and extensions: gmp, hash, openssl, and sodium (for EdDSA/X25519).

    Pin to ^1.1.3 or later. v1.1.3 adds validation of the decompressed length for tokens using zip: DEF (JWE compression), closing a decompression-bomb / memory-exhaustion vector. Treat anything below 1.1.3 as unsafe for any endpoint that accepts JWEs from untrusted clients.

  2. First Use Case: Generate a JWT for API authentication:

    use SimpleJWT\Keys\KeySet;
    use SimpleJWT\JWT;
    
    $keySet = KeySet::createFromSecret('your-secret-key');
    $headers = ['alg' => 'HS256', 'typ' => 'JWT'];
    $claims = ['sub' => 'user123', 'exp' => time() + 3600];
    $jwt = new JWT($headers, $claims);
    $token = $jwt->encode($keySet);
    
  3. Where to Look First:

    • README.md for core workflows.
    • SimpleJWT\Keys\KeySet for key management.
    • SimpleJWT\JWT and SimpleJWT\JWE for token operations.
    • CHANGELOG — security-relevant fixes (like the v1.1.3 zip:DEF decompression limit) land in patch releases, so read it before and after every composer update.

Implementation Patterns

Key Management Workflows

  1. HMAC Secrets:

    $keySet = KeySet::createFromSecret('secret-key');
    

    Use for stateless APIs (e.g., API gateways).

  2. Asymmetric Keys (RSA/ECDSA):

    $privateKey = new \SimpleJWT\Keys\RSAKey(file_get_contents('private.pem'), 'pem');
    $publicKey = new \SimpleJWT\Keys\RSAKey(file_get_contents('public.pem'), 'pem');
    $keySet->add($privateKey, true); // Auto-generate kid
    $keySet->add($publicKey);
    

    Ideal for server-to-server or user authentication with public/private pairs.

  3. Key Rotation:

    $keySet->add($newPrivateKey, true); // Add new key with kid
    $keySet->remove($oldPrivateKey);   // Remove old key
    

    Store keys in environment variables or a secrets manager (e.g., AWS Secrets Manager).

Token Creation & Validation

  1. JWT Creation:

    $jwt = new JWT(['alg' => 'RS256'], ['sub' => 'user123', 'iat' => time()]);
    $token = $jwt->encode($keySet);
    
    • Use HS256 for simplicity, RS256/ES256 for security.
    • Disable auto-kid/iat with $jwt->encode($keySet, false).
  2. JWT Validation:

    try {
        $decoded = JWT::decode($token, $keySet, 'RS256');
        $claims = $decoded->getClaims();
    } catch (\SimpleJWT\InvalidTokenException $e) {
        // Handle invalid token (expired, tampered, etc.)
    }
    
    • Always validate alg against a whitelist (e.g., ['HS256', 'RS256']).
    • Use JWT::deserialise() for debugging (no validation).
  3. JWE (Encrypted Tokens):

    $jwe = new \SimpleJWT\JWE(['alg' => 'PBES2-HS256+A128KW', 'enc' => 'A128CBC-HS256'], 'secret-payload');
    $encrypted = $jwe->encrypt($keySet);
    $decrypted = \SimpleJWT\JWE::decrypt($encrypted, $keySet, 'PBES2-HS256+A128KW');
    

    Use for confidential claims (e.g., PII in tokens).

  4. Compressed JWEs (zip: DEF):

    $jwe = new \SimpleJWT\JWE(
        ['alg' => 'A256KW', 'enc' => 'A256GCM', 'zip' => 'DEF'],
        json_encode($largeClaims)
    );
    $encrypted = $jwe->encrypt($keySet);
    
    • On PHP >= kelvinmo/simplejwt 1.1.3, decompression validates the inflated length before handing data back, so a hostile token can no longer expand into gigabytes of memory.
    • Only accept zip on endpoints where you control (or trust) the issuer — and keep the library patched, since the protection lives in the library, not your code.

Integration with Laravel

  1. Middleware for JWT Validation:

    use SimpleJWT\JWT;
    
    class AuthenticateJWT
    {
        public function handle($request, Closure $next)
        {
            $token = $request->bearerToken();
            if (!$token) abort(401);
    
            try {
                $decoded = JWT::decode($token, $this->keySet, 'HS256');
                $request->merge(['user' => $decoded->getClaims()]);
            } catch (\Exception $e) {
                abort(401);
            }
            return $next($request);
        }
    }
    
  2. Service Provider for Key Management:

    class JWTServiceProvider extends ServiceProvider
    {
        public function register()
        {
            $this->app->singleton('jwt.keySet', function () {
                return KeySet::createFromSecret(config('jwt.secret'));
            });
        }
    }
    
  3. API Resource Responses:

    return response()->json([
        'data' => $user,
        'access_token' => (new JWT(['alg' => 'HS256'], [
            'sub' => $user->id,
            'exp' => time() + 3600
        ])->encode($this->keySet)),
    ]);
    
  4. Keep the Library Current in CI:

    # composer.json
    "require": { "kelvinmo/simplejwt": "^1.1.3" }
    

    Add composer audit (or Dependabot/Renovate) to your pipeline — patch releases of this library have carried security fixes.


Gotchas and Tips

Pitfalls

  1. Algorithm Mismatch:

    • Issue: InvalidTokenException with SIGNATURE_VERIFICATION_ERROR.
    • Fix: Ensure the alg in the token matches the one passed to decode(). Example: JWT::decode($token, $keySet, 'HS256') must match the token’s alg.
  2. Key ID (kid) Handling:

    • Issue: Tokens fail validation if kid is missing or mismatched.
    • Fix: Always include kid in keys or use KeySet::add($key, true) to auto-generate it.
    • Debug: Check SimpleJWT\Keys\Key::getKeyId() to verify kid values.
  3. Clock Skew:

    • Issue: Tokens expire prematurely due to server time differences.
    • Fix: Use nbf (Not Before) claims and adjust server time or add a leeway buffer:
      $claims['exp'] = time() + 3600;
      $claims['nbf'] = time() - 60; // Allow 1-minute leeway
      
  4. PEM vs. JWK:

    • Issue: RSAKey fails to load from PEM files.
    • Fix: Ensure PEM files contain only the key (no X.509 certificates). Use:
      $key = new \SimpleJWT\Keys\RSAKey(file_get_contents('private.pem'), 'pem');
      
  5. PHP Extensions:

    • Issue: SodiumException or RuntimeException during EdDSA/X25519 operations.
    • Fix: Enable the sodium extension in php.ini or Dockerfile:
      extension=sodium
      
  6. Compressed Tokens (zip: DEF) — Decompression Bombs:

    • Issue: A JWE with the zip header set to DEF can be a few hundred bytes on the wire but inflate to hundreds of MB, exhausting memory_limit and taking the worker down. Before v1.1.3, the library did not bound the decompressed length.
    • Fix: Upgrade to kelvinmo/simplejwt ^1.1.3, which validates the length of the decompressed data. Verify with:
      composer show kelvinmo/simplejwt | grep versions
      
    • Defence in depth: Reject unexpected zip headers on inbound tokens if your issuer never sets them:
      $parsed = \SimpleJWT\JWE::deserialise($token); // inspect header only
      if (($parsed['protected']['zip'] ?? null) !== null) {
          abort(400);
      }
      
    • Also bound the payload size at the HTTP layer (post_max_size, middleware max: validation rule) so oversized ciphertexts never reach the library.
    • Regression test: add a test that feeds a crafted high-compression-ratio JWE to your decrypt path and asserts a clean rejection rather than an OOM.

Debugging Tips

  1. Deserialize Without Validation:

    $result = JWT::deserialise($token);
    print_r($result['claims']); // Inspect claims
    print_r($result['signatures']); // Inspect signatures
    
  2. Key Validation:

    try {
        $keySet->validateKey($token, 'HS256');
    } catch (\SimpleJWT\KeyException $e) {
        // Key is invalid or unsupported
    }
    
  3. Algorithm Support:

    • Check supported algorithms in SimpleJWT\Algorithms.
    • Example: PBES2-HS256+A128KW requires openssl and gmp.
  4. Compression Failures:

    • Malformed or oversized zip: DEF payloads surface as decode/decrypt exceptions — wrap decrypt in try/catch (\Throwable $e) and log the exception class, since a decompression-limit rejection looks like any other invalid-token error to the caller.
    • Never log the raw token; log a hash of it plus the header (alg, enc, zip) for triage.

Performance Tips

  1. KeySet Caching:

    $keySet = Cache::remember('jwt.keys', 3600, function () {
        return KeySet::createFromSecret(config('jwt.secret'));
    });
    
  2. Avoid Re-encoding:

    • Reuse JWT objects for multiple encodes if claims/headers are static.
  3. JWE Optimization:

    • Prefer symmetric algorithms (e.g., A256GCM) for performance-critical paths.
    • zip: DEF costs CPU on both ends; use it only for genuinely large, repetitive payloads — and only on ^1.1.3.

Extension Points

  1. Custom Claims Validation:

    $decoded = JWT::decode($token, $keySet, 'HS256');
    if ($decoded->getClaim('role') !== 'admin') {
        abort(403);
    }
    
  2. Multi-Recipient JWE:

    $jwe = new \SimpleJWT\JWE(['alg' => 'ECDH-ES+A128KW', 'enc' => 'A128GCM'], 'secret');
    $encrypted = $jwe->encrypt($keySet);
    

    Add one recipient key per party; each recipient decrypts with their own private key. Keep recipient keys in a rotating key set so a compromised recipient can be dropped without reissuing every token.

  3. Pre-flight Header Inspection:

    // Gate on header values (alg, enc, zip) before doing any crypto work.
    $header = \SimpleJWT\JWE::deserialise($token)['protected'];
    abort_unless($header['alg'] === 'A256KW' && $header['enc'] === 'A256GCM', 400);
    

    Rejecting unsupported or unexpected alg/enc/zip values up front is the cheapest defence you can add — it also lets you enforce the v1.1.3 protections on your own terms.

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.
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc