Configuration¶
Overview¶
Daycry\JWT\Config\JWT extends CodeIgniter's BaseConfig. After running php spark jwt:publish you get an editable copy at app/Config/JWT.php.
All values are also overridable through .env using the standard CI4 dot-notation convention: jwt.{property}.
The library refuses to operate when required fields are missing — it throws Daycry\JWT\Exceptions\JWTConfigurationException. There are no insecure defaults.
Required fields¶
| Property | Required for | Default |
|---|---|---|
$signer |
symmetric (HMAC) | null |
$signingKey + $verifyingKey |
asymmetric (RSA / ECDSA) | null |
$issuer |
always | null |
$audience |
always | null |
$identifier |
always | null |
The first call to encode() / decode() with any of these unset throws JWTConfigurationException::missingClaim('...') with a message that names the missing field. An empty string is treated exactly like null for $issuer, $audience and $identifier — both throw.
Algorithm selection¶
$algorithmType¶
'symmetric' (default) or 'asymmetric'. Drives which key fields are read.
$algorithm¶
The signer class. It must match $algorithmType: 'symmetric' requires an Lcobucci\JWT\Signer\Hmac\* signer, and 'asymmetric' requires an Rsa\* or Ecdsa\* signer. A mismatch — for example leaving $algorithmType = 'asymmetric' with the default HMAC Sha256, or pairing 'symmetric' with an RSA/ECDSA signer — throws JWTConfigurationException with a clear message instead of a cryptic lcobucci "key" error.
Pick one matching $algorithmType:
| Algorithm | Class |
|---|---|
| HMAC SHA-256 (HS256) | \Lcobucci\JWT\Signer\Hmac\Sha256::class |
| HMAC SHA-384 (HS384) | \Lcobucci\JWT\Signer\Hmac\Sha384::class |
| HMAC SHA-512 (HS512) | \Lcobucci\JWT\Signer\Hmac\Sha512::class |
| RSA SHA-256 (RS256) | \Lcobucci\JWT\Signer\Rsa\Sha256::class |
| RSA SHA-384 (RS384) | \Lcobucci\JWT\Signer\Rsa\Sha384::class |
| RSA SHA-512 (RS512) | \Lcobucci\JWT\Signer\Rsa\Sha512::class |
| ECDSA P-256 (ES256) | \Lcobucci\JWT\Signer\Ecdsa\Sha256::class |
| ECDSA P-384 (ES384) | \Lcobucci\JWT\Signer\Ecdsa\Sha384::class |
| ECDSA P-521 (ES512) | \Lcobucci\JWT\Signer\Ecdsa\Sha512::class |
Symmetric (HMAC) keys¶
$signer¶
Base64-encoded secret. Generate one with:
Rotating the key invalidates all outstanding tokens.
Asymmetric (RSA / ECDSA) keys¶
$signingKey / $verifyingKey¶
Either a filesystem path (preferred) or the raw PEM contents. Generate with:
php spark jwt:keypair --algorithm=rsa --bits=2048
php spark jwt:keypair --algorithm=ecdsa --curve=prime256v1
jwt.algorithmType = "asymmetric"
jwt.signingKey = "/var/www/app/writable/keys/jwt-private.pem"
jwt.verifyingKey = "/var/www/app/writable/keys/jwt-public.pem"
A path under WRITEPATH works out of the box; alternatively prefix with file:// or pass the literal PEM string.
$passphrase¶
Set if the private key is encrypted. Used only when reading $signingKey. Leave null for unencrypted keys.
Key rotation (3.2.0)¶
$keyId¶
Optional key identifier. When set, it is written as the kid header on every issued token so verifiers can pick the matching key during rotation. Defaults to null (no kid header). Override per instance with JWT::withKeyId().
$verifyingKeys¶
A kid => key map consulted on decode(): the token's kid header selects the matching verification key, falling back to the single $verifyingKey / $signer when the kid is absent or unknown. Values are PEM contents or paths for asymmetric algorithms, or base64 secrets for symmetric ones. The configured signer/algorithm is always used, so a token's kid can never downgrade the verifier.
public ?string $keyId = '2026-06';
public array $verifyingKeys = [
'2026-05' => '/path/old-public.pem',
'2026-06' => '/path/new-public.pem',
];
See Advanced → Key rotation for the full workflow.
Claims¶
$issuer (iss)¶
URL of the token-issuing service.
$audience (aud)¶
URL of the consuming service. Tokens whose aud does not include this value are rejected.
$identifier (jti)¶
Application-specific identifier. Tokens with a different jti are rejected.
$uid¶
Default value for the custom uid claim — a string or an integer ID (e.g. a database primary key). Type int|string|null. Set per-call via JWT::encode($data, $uid), which accepts int|string|null. lcobucci/jwt preserves the JSON type, so an integer uid round-trips back as an integer.
$canOnlyBeUsedAfter / $expiresAt¶
DateTimeImmutable::modify() strings. Defaults: +0 minute / +24 hour.
If canOnlyBeUsedAfter resolves to a moment after iat, it is clamped to iat so freshly-issued tokens are immediately usable.
Validation¶
$validate¶
true (default): decode() runs every constraint in $validateClaims.
false: parsing only — never use in production. When decode() runs with $validate = false it writes a warning via log_message() (parallel to extractClaimsUnsafe()); the signature and registered claims are not verified. This is intended for tests / debug only.
$validateClaims¶
Ordered list of constraint names. Defaults: ['SignedWith', 'IssuedBy', 'LooseValidAt', 'IdentifiedBy', 'PermittedFor'].
Allowed values:
| Name | Library class | Notes |
|---|---|---|
SignedWith |
Lcobucci\JWT\Validation\Constraint\SignedWith |
Signature verification |
IssuedBy |
IssuedBy |
iss |
IdentifiedBy |
IdentifiedBy |
jti |
PermittedFor |
PermittedFor |
aud |
LooseValidAt (default) |
LooseValidAt |
iat/nbf/exp with leeway, missing claims tolerated |
StrictValidAt |
StrictValidAt |
iat/nbf/exp all required |
ValidAt |
alias of LooseValidAt |
Legacy name; prefer LooseValidAt |
SignedWithis mandatory when$validate = true. If$validateClaimsdoes not contain'SignedWith',decode()throwsJWTConfigurationExceptionrather than silently skip signature verification. To decode a token without any validation, set$validate = falseinstead of droppingSignedWith.
$leeway¶
Acceptable clock skew in seconds for LooseValidAt / StrictValidAt. 0 (default) means strict; null also disables leeway.
Leeway can also be applied per-call: $jwt->withLeeway(60). Passing null ($jwt->withLeeway(null)) resets it to no leeway; a negative value throws InvalidArgumentException.
Other flags¶
$allowUnsafeExtraction¶
false (default): every call to JWT::extractClaimsUnsafe() writes a warning to the framework logger so accidental production use shows up. Set to true if you intentionally rely on the method (token inspection tools, etc.).
.env reference¶
# Algorithm
jwt.algorithmType = "symmetric"
jwt.algorithm = "Lcobucci\\JWT\\Signer\\Hmac\\Sha256"
# Symmetric secret
jwt.signer = "<base64-secret>"
# Asymmetric (when algorithmType=asymmetric)
jwt.signingKey = "/path/to/private.pem"
jwt.verifyingKey = "/path/to/public.pem"
jwt.passphrase = ""
# Claims
jwt.issuer = "https://api.my-app.com"
jwt.audience = "https://my-app.com"
jwt.identifier = "my-app-v2"
# Lifetime
jwt.canOnlyBeUsedAfter = "+0 minute"
jwt.expiresAt = "+1 hour"
# Validation
jwt.validate = true
jwt.leeway = 30
jwt.allowUnsafeExtraction = false
Programmatic override¶
use Daycry\JWT\JWT;
$config = config('JWT');
$config->uid = $userId; // string or integer ID
$token = (new JWT($config))->encode($payload);
To override the lifetime for a single token without mutating the shared config, prefer the immutable per-instance helper: