Errors
This page describes polyjose 0.1.0 as designed. The library is in development and not published yet.
Every polyjose failure carries exactly one error code from a fixed set. The codes are the same strings on every platform, because every package reports the code the Rust core produced and never adds codes of its own. errorCodes() returns the full list in JavaScript, Kotlin and Swift, and Error::CODES holds it in Rust.
Reading the code
Section titled “Reading the code”| Platform | Error type | The code |
|---|---|---|
| Rust | polyjose::Error | Error::code() |
JavaScript (@polyjose/node, @polyjose/wasm) | PolyjoseError, a subclass of Error whose name is “PolyjoseError”; the rejected Promise carries it | PolyjoseError.code |
| Kotlin | PolyjoseException, a sealed class with one subclass per code | PolyjoseException.code() |
| Swift | PolyjoseError, an enum with one case per code | PolyjoseError.code() |
In JavaScript, passing an argument of the wrong type (a number where a string belongs, for example) throws the host’s own TypeError before polyjose runs. That is a programming error, not one of the codes below, so err.code is always one of the codes whenever err instanceof PolyjoseError. Kotlin and Swift reject the same mistakes at compile time.
Branch on the code, never on the message
Section titled “Branch on the code, never on the message”- Decide what to do from the code. Messages are for people reading logs: their wording can change between versions, and they may name the claim or header involved.
- Messages never contain input. Keys, passwords, tokens, payloads and plaintext stay out of every message and every error’s string form, on every platform, so an error is safe to log.
- New codes may be added in later versions. Keep a default branch for codes you don’t handle.
| Code | Meaning | Typical causes |
|---|---|---|
InvalidBase64 | A part that must be base64url is not valid unpadded base64url (RFC 4648 §5). |
|
InvalidJson | Input that must be JSON is not strict I-JSON, or exceeds the parser's depth or size limits. |
|
InvalidHeader | A JOSE header is well-formed JSON but breaks a header rule. |
|
InvalidFormat | The serialization has the wrong shape, or the input is larger than the configured limit. |
|
UnsupportedAlgorithm | The algorithm, key type, curve, format or option is not implemented. |
|
AlgorithmNotAllowed | The algorithm is valid but not in the allow-list the caller passed. |
|
InvalidKey | The key material is malformed or too weak for the algorithm. |
|
KeyAlgorithmMismatch | The key is valid but does not belong to the algorithm or is not permitted for this use. |
|
InvalidSignature | The signature or MAC does not verify with the given key. |
|
DecryptionFailed | Decryption failed, and the reason is deliberately not reported. |
|
UnknownCritical | The crit header names a parameter that polyjose does not understand. |
|
TokenExpired | The JWT's exp time has passed, after allowing for the leeway. |
|
TokenNotYetValid | The JWT's nbf time is still in the future, after allowing for the leeway. |
|
InvalidClaim | A JWT claim is missing, has the wrong type, or does not match the validation policy. |
|
NoMatchingKey | No key in the JWK Set is a candidate for verifying this token. |
|
RandomnessUnavailable | The operating system's secure random number generator could not be read. |
|
DecryptionFailed is deliberately one code for every failure after key management, whatever the internal cause. Reporting which step failed would give an attacker an oracle to probe ciphertexts with.
The order of JWT checks
Section titled “The order of JWT checks”A JWT can be wrong in several ways at once. polyjose runs its checks in a fixed order, and the first failing check decides the error, so the same token gives the same code on every platform.
- Size limits and the token’s shape:
InvalidFormat. A JSON serialization is never a JWT, so it fails here too. - base64url of every part:
InvalidBase64. - The header:
InvalidJsonif it isn’t valid JSON, thenInvalidHeaderfor structural problems such as a missingalg. critnaming a parameter polyjose doesn’t understand:UnknownCritical.algthat isn’t a known JWS algorithm:UnsupportedAlgorithm.algthat isn’t in your allow-list:AlgorithmNotAllowed.- JWT header rules (
b64: false,cty: JWTon a signed JWT, thetyppolicy):InvalidHeader. - Choosing the key. With a JWK Set, finding no candidate key, or more candidates than the limit, gives
NoMatchingKey. With a single key, a key of the wrong family or use givesKeyAlgorithmMismatch, and weak key material givesInvalidKey. - The signature:
InvalidSignature. When a JWK Set offers several candidates and all fail, the furthest-reaching failure is reported, rankedInvalidSignature, thenInvalidKey, thenKeyAlgorithmMismatch. - Parsing the claims:
InvalidJson(including duplicate claim names), thenInvalidClaimfor a registered claim of the wrong type or a date out of range. - Required claims, in the order
iss,sub,aud,exp,nbf,iat,jti, then any extra names you require:InvalidClaim. exp:TokenExpired.nbf:TokenNotYetValid.iat, when you set a maximum age:InvalidClaim.iss:InvalidClaim.sub:InvalidClaim.aud, including a token that has an audience when you configured none:InvalidClaim.
Claims are read only after the signature verifies, so a tampered payload reports InvalidSignature, never a claim error.
For a nested JWT (signed, then encrypted), decryption runs first and reports its own errors, ending in DecryptionFailed. Then the outer header’s cty and typ are checked (InvalidHeader), then steps 1 to 17 run on the inner signed JWT, and finally any iss, sub or aud copied into the outer header must equal the inner claim (InvalidClaim).