Skip to content

Daycry JWT

JWT (JSON Web Token) for CodeIgniter 4, built on lcobucci/jwt 5HMAC, RSA and ECDSA, an immutable façade, and key rotation. Secure by default: it fails loudly instead of signing with a weak or missing key.

Get started Key rotation GitHub

Features

  • HMAC, RSA & ECDSA


    One config toggle (algorithmType) switches between symmetric HMAC and asymmetric RSA/ECDSA. The signer and algorithmType must agree — a mismatch fails fast with a clear message, not a cryptic key error.

    Configuration

  • Immutable façade


    Every with*() customiser returns a new instancewithSplitData, withExpiresAt, withLeeway, withIssuer, withAudience, withIdentifier, withHeader, withClaims — so the shared config is never mutated across requests.

    Usage

  • Key rotation (kid)


    Stamp a kid header and verify against a per-kid key map, so you can roll keys with zero downtime. An attacker-chosen kid can never downgrade the verifier.

    Key rotation

  • Fail-closed by default


    Missing signer/issuer/audience/identifier throws (null and "" rejected). decode() refuses to skip signature verification, and jwt:key enforces a 256-bit minimum.

    Threat model

  • Validated reads


    decode(), getPayload(), getClaims() and getClaim() all validate first. The parse-only helpers (isExpired, extractClaimsUnsafe) are clearly marked and log a warning so misuse is visible.

    Advanced

  • Spark commands


    jwt:key (HMAC secret to .env), jwt:keypair (RSA/ECDSA PEM pair, curve-aware), and jwt:publish (config to your app) — auto-registered with Spark.

    CLI Commands

Quick start

composer require daycry/jwt
php spark jwt:publish     # write app/Config/JWT.php
php spark jwt:key         # generate jwt.signer in .env (>= 32 bytes)
use Daycry\JWT\JWT;

$jwt = JWT::for();                 // pulls config('JWT')

// The uid may be a string or an integer ID (e.g. a DB primary key).
$token = $jwt->encode(['user_id' => 42, 'role' => 'admin'], 'user-42');
// Throws on failure…
$claims = $jwt->decode($token);                 // Lcobucci\JWT\Token\Plain
echo $claims->claims()->get('uid');             // "user-42"

// …or get the original payload back (auto-decodes compact JSON):
$payload = $jwt->getPayload($token);            // ['user_id' => 42, 'role' => 'admin']

// …or the non-throwing flow:
if ($jwt->tryDecode($maybeBad) === null) {
    return $this->response->setStatusCode(401);
}

Full getting-started guide

Security, by default

  • Signature always verified


    If validate = true but validateClaims omits SignedWith, decode() throws instead of silently skipping verification. Tampered tokens (signature, header or payload) are rejected.

  • No stale clocks


    The time constraints (LooseValidAt / StrictValidAt) are rebuilt per call and always use the current clock — never a frozen one. Only the stateless signer/key configuration is memoized.

    Configuration

  • Tested & analysed


    A focused PHPUnit suite (expiry, leeway, nbf, tampering, encrypted keys, key rotation) plus PHPStan level 8, Psalm and Rector keep the library correct and clean.

    Testing