Appendix: Authentication¶
The contracts behind Authentication and JWT Authentication: the accepted
Authorization header, the JWT key and algorithm rules, every check a
token passes before it authenticates, JWK Set validation, and the
exceptions kinetis/auth-jwt throws. The guides cover setup; this page
is the reference they link to.
JWT keys and algorithms¶
Algorithm |
Signing ( |
Verifying ( |
Minimum key |
|---|---|---|---|
|
|
|
32-byte secret |
|
|
|
48-byte secret |
|
|
|
64-byte secret |
|
|
|
2048-bit RSA key |
hmacSecret() defaults to HS256 and the RSA constructors to RS256;
the second argument names another algorithm from the same family. The
algorithms firebase/php-jwt implements beyond these six (ES256,
ES256K, ES384, PS256, EdDSA) are refused.
A single configured key pins the algorithm. firebase/php-jwt rejects a
token whose header alg differs from the key’s own algorithm, so a token
cannot move an RS256 verifier onto HS256 by naming it, and none is
not an accepted alg at all.
Validation at construction¶
JwtSigningKey and JwtVerificationKeys validate when they are built,
never on the first issue() or request, and throw
Exception\JwtConfigurationException for:
an algorithm outside the six, or from the other family than the constructor —
RS256handed tohmacSecret(),HS256handed torsaPublicKey();an HMAC secret shorter than the table’s minimum, the digest length RFC 7518 §3.2 requires;
RSA material that does not parse as an RSA key, is under 2048 bits, or is the wrong half:
rsaPrivateKey()refuses a public key andrsaPublicKey()refuses a private key. A shared HMAC secret is not PEM and fails here too;a
kidthat breaks the rule below.
JwtAuthenticator and JwtIssuer validate their claim constraints the
same way: an empty issuer, an empty audience string, and an audience
array that is empty, not a list (sequential integer keys from 0), or
holds anything but non-empty strings. A non-list array would encode as a
JSON object rather than the JWT array-of-strings form.
Validating a key parsed out of a JWK Set raises no PHP warning, so an
error handler that converts warnings to exceptions still sees this
package’s own exception. Each message names the rule, never the secret or
key material, and chains no OpenSSL or firebase/php-jwt exception.
Key ids¶
A kid is a non-blank string of at most 256 bytes that is valid UTF-8
(JwtKeyValidator::isUsableKid()). The rule applies to
JwtSigningKey’s $kid, to PublishedRsaKey, to every kid in a JWK
Set document, and to the kid in a token’s header, so no side of a
rotation can name a key another side refuses. UTF-8 is required because a
kid travels as JSON in both directions.
A kid is matched as the exact string published: "0", "00" and
"zero" select three different keys. PublishedRsaKey carries its kid
as a value rather than an array key because PHP turns the array key '0'
into the integer 0.
Token acceptance¶
JwtAuthenticator::authenticate() returns a JwtUser only when every
check passes, in this order. Any failure returns null, which
JwtAuthMiddleware answers with the generic 401; nothing in the
response names the failed check.
The JOSE header is acceptable (The JOSE header).
A key is selected: the single configured key, or the key a
jwks()set publishes under the token’skid.firebase/php-jwtverifies the token: the headeralgequals the key’s algorithm, the signature verifies, the payload is a JSON object, andiat,nbfandexpare numbers when present. A token is rejected beforenbf, beforeiatwhen it has nonbf, and from the secondexpis reached. No clock-skew leeway applies:Firebase\JWT\JWT::$leewaydefaults to0and this package does not set it, so an issuer whose clock runs ahead of the verifier produces tokens that fail until the verifier’s clock reachesiat.subis a non-empty string. A JSON number is refused.With
expectedIssuer,issis a string equal to it.With
acceptedAudiences,audis either a string in the list or a non-empty array of non-empty strings, at least one of them in the list.With a revocation store,
jtiis a non-empty string and the store does not report it revoked. A lookup that throws propagates instead of returningnull.
The JOSE header¶
The header arrives unsigned. JwtAuthenticator validates it through
Kinetis\AuthJwt\JoseHeader before any part of the token reaches
JWT::decode(), because firebase/php-jwt raises a TypeError, not a
decode failure, for an alg or kid written as a JSON array or object.
A token reaches verification only when:
it is non-empty and at most 16,384 bytes;
it has exactly three segments, the header segment at most 4,096 characters;
every segment is non-empty and is the one canonical unpadded base64url spelling of its bytes — no
=padding, no+or/, no unused pad bits set;the header decodes to a JSON object of at most 2,048 bytes and nesting depth 8 that names no member twice at any depth, with names compared after unescaping;
algis a string among the six supported algorithms;the header has no
critorb64member;kid, when present, follows the kid rule whether or not the configured key reads it. Ajwks()set requires one.
Duplicate members and non-canonical spellings are refused because a JWS
signs the encoded text of its header and payload: what the verifier acts
on has to be the one document the sender signed, not whichever value
json_decode() kept.
crit and b64 are refused because each changes what verification
means and firebase/php-jwt reads neither:
crit(RFC 7515 §4.1.11) lists header members a verifier must understand or reject the token over. This package implements no critical extension.b64(RFC 7797) signs the payload unencoded. Verification here signs the compact encoded form.
Every other member, typ included, is ignored.
JWK Sets¶
Publishing: JwkSet::fromRsaPublicKeys()¶
JwkSet::fromRsaPublicKeys(array $keys, string $algorithm = 'RS256')
returns an RFC 7517 {"keys": [...]} array. Each entry carries kty
RSA, the key’s kid, use sig, alg, and the base64url n and
e. Every entry shares one $algorithm.
It validates everything before producing output and throws
Exception\JwtConfigurationException for an $algorithm outside
RS256/RS384/RS512, an empty or non-list $keys, an entry that is
not a PublishedRsaKey, two entries under one kid, and a key that is not
a PEM RSA public key of at least 2048 bits. A message about one key names
its kid, since on this side a kid is the application’s own
configuration. A published document therefore never advertises a key
the verifier would refuse.
Parsing: JwtVerificationKeys::jwks()¶
jwks() parses a JWK Set JSON string once, when it is called. It does
not fetch a URL or refresh; an application that needs a changed document
builds a new JwtAuthenticator.
It returns a set whose every key is usable, or throws
Exception\JwtConfigurationException — never a partial set. It refuses:
a document that is not a JSON object, exceeds 65,536 bytes or nesting depth 8, or names a member twice at any depth;
a missing
keysmember, or one that is not a non-empty array, or more than 32 keys;a key that is not a non-empty object;
a
ktyother thanRSA(octkeys are symmetric secrets and never belong in a published set);the RSA private members
d,p,q,dp,dq,qi,oth, or a symmetrick;a missing
kid, one outside the kid rule, or one an earlier key already claims;a missing
alg, or one outsideRS256/RS384/RS512;a
useother thansig, orkey_opsother than exactly["verify"];an
norethat is not a string holding the canonical unpadded base64url spelling of an unsigned integer with no leading zero byte, within 2,048 characters, 1,024 bytes fornand 8 bytes fore;an even
e, oreequal to 1;a key that does not compose into an RSA public key of at least 2048 bits.
Members outside that list are ignored at the root and inside a key, as
RFC 7517 §5 requires,
so a provider’s x5c, x5t, x5t#S256 or x5u changes nothing. A
message names the rule and the zero-based index of the offending key,
never the document, a kid, or key material, and chains no OpenSSL or
firebase/php-jwt exception.
Exceptions¶
All four live in Kinetis\AuthJwt\Exception. No message carries a
token, jti, subject, secret, or key material.
Exception |
Thrown by |
When |
|---|---|---|
|
|
A key, algorithm, kid, JWK Set, or issuer/audience constraint breaks the rules above. |
|
|
An empty subject; |
|
|
Construction over |
|
|
Construction over |
JwtAuthenticator::authenticate() turns every rejected token into
null and throws nothing for one. A JwtConfigurationException or a
cache failure is a server-side fault and surfaces as one.
Credentials in stack traces¶
A stack frame carries the arguments it was called with, so a backtrace
renders them. kinetis/auth-jwt marks key material, issued claims, the
request carrying a bearer token, the token string, a refresh token, and a
jti with #[\SensitiveParameter] where it passes them, so a trace
through its own frames shows a redacted placeholder. Frames owned by
firebase/php-jwt, PSR-7, or the application are outside its reach.