Errors
Every error code, what triggers it, and what to do about it.
JWTShield reports problems through three distinct channels. Read this section before wiring error handling.
- Token-shape errors return HTTP 400 with body
{"error": {"code": "...", "message": "..."}}. The token cannot be parsed at all. - Validation errors return HTTP 200 with body
{"valid": false, "findings": [...], ...}. The token parsed cleanly but failed one or more checks. Always checkvalidbefore trusting the response. - Request-shape errors return HTTP 422 with the FastAPI standard validation envelope. Your request body is malformed.
Token-shape errors (HTTP 400)
MALFORMED_TOKEN
Returned by every endpoint that accepts a token when the value cannot be parsed as a three-segment, base64url-encoded JWT containing JSON header and payload.
| Trigger | Example message |
|---|---|
| Wrong segment count | expected 3 segments, got 1 |
| Invalid base64url | invalid base64url segment: Invalid base64-encoded string |
| Header/payload not JSON | header is not valid JSON: Expecting value |
| Header/payload not a JSON object | payload is not a JSON object |
| Empty token string | token is empty |
Fix: ensure the token is the value of a JWT, not the entire Authorization: Bearer ... header. Strip any whitespace.
Validation findings (HTTP 200, valid: false)
These appear in the findings[] array of /v1/validate/jwt and any endpoint that performs full validation.
SIGNATURE_INVALID
The signature does not verify against the resolved key. For HS* algorithms the policy secret is wrong; for RS/ES/EdDSA the resolved JWKS key does not match the signature.
ALGORITHM_INVALID
The token’s alg header is not in policy.allowed_algs. JWTShield refuses signature work for any algorithm outside the allowlist; this is the primary defence against alg=none and key-confusion attacks.
ISSUER_MISMATCH
The token’s iss claim does not equal policy.issuer exactly. Comparison is case-sensitive and trailing slashes matter.
AUDIENCE_MISMATCH
The token’s aud claim is not in policy.audiences. Both single-string and array aud claims are supported; at least one entry must match.
TOKEN_EXPIRED
Current time is past exp. Honours policy.clock_skew_seconds.
TOKEN_NOT_YET_VALID
Current time is before nbf. Honours policy.clock_skew_seconds.
REQUIRED_CLAIM_MISSING
A claim listed in policy.required_claims is absent from the payload. The evidence object names which claim.
PROFILE_NOT_FOUND
issuer_profile_id was supplied but no profile with that ID is registered. Check the ISSUER_PROFILES_JSON environment variable on the JWTShield service.
Discovery findings (HTTP 200, in findings[])
Returned by /v1/providers/discover.
DISCOVERY_FAILED
{issuer}/.well-known/openid-configuration did not return a 2xx response, or returned non-JSON. The evidence object includes the HTTP status and body excerpt.
JWKS_UNREACHABLE
The JWKS endpoint advertised in the discovery document timed out or returned a non-2xx response. The discovery jwks_reachability field also reflects this.
INVALID_METADATA
The discovery document is JSON but does not match the OpenID Connect Discovery 1.0 schema (e.g. issuer field missing, jwks_uri not a URL).
Config-lint findings (HTTP 200, in findings[])
Returned by /v1/lint/oidc-config.
WEAK_ALGORITHM
alg_policy.allowed_algs contains an algorithm weaker than RS256/ES256/EdDSA. Refusing weak algorithms reduces the attack surface even when stronger ones are also allowed.
INSECURE_URI
A URI in jwks_uri or redirect_uris uses http:// while https_required is true.
JWKS_URI_MISMATCH
The jwks_uri you sent does not match discovered_jwks_uri from the issuer’s /.well-known/openid-configuration. Either the upstream provider rotated their JWKS endpoint or your config drifted.
CI-token findings (HTTP 200, in findings[])
Returned by /v1/validate/ci-oidc in addition to the standard validation findings above.
GITHUB_REPO_MISMATCH
The token’s repository claim does not match expected_repository. Common cause: a fork or unintended branch triggered the workflow.
GITHUB_REF_MISMATCH
The token’s ref claim does not match expected_ref. Use this to refuse tokens issued by feature branches when production deploys must come from refs/heads/main.
GITLAB_PROJECT_MISMATCH
The token’s project_path claim does not match expected_project_path. Mirrors the GitHub repo check.
GITLAB_REF_PROTECTION_MISMATCH
The token’s ref_protected claim does not match expected_ref_protected. Refuse OIDC-issued tokens from unprotected branches if your deploy gate requires protection.
Rotation findings (HTTP 200, in findings[])
Returned by /v1/validate/jwks-rotation.
NO_KEY_OVERLAP
current_jwks shares no keys (by kid) with previous_jwks. In-flight tokens issued under the previous key set will fail verification immediately. Roll back, or accept brief verification failures during the cutover window.
ROTATION_IN_PROGRESS
Both old and new keys are present in current_jwks. This is the safe overlap state; finish the rotation by removing the old key only after the longest in-flight token has expired.
ROTATION_UNCLEAR
The provided JWKS pair lacks the metadata needed to classify rotation (e.g. keys without kid). Call /v1/providers/discover first or attach kid to your keys.
CI-provider error (HTTP 422)
CI_PROVIDER_UNKNOWN
provider is not one of github_actions or gitlab. The 422 body lists supported providers.
Suspicious patterns (HTTP 200, in suspicious_warnings[])
Returned by /v1/inspect/token. These are heuristic warnings about token structure, not validation failures. They flag dangerous shapes early so they can be refused before signature work runs.
ALG_NONE
header.alg is the literal string "none". Any verifier that honours this header trivially accepts forged tokens. JWTShield’s validate path will reject this regardless of header — this warning surfaces it during decode.
JKU_PRESENT
header.jku is set. Naïve verifiers fetch the URL and use it as the key source, allowing the token issuer to point key resolution at attacker-controlled infrastructure. JWTShield ignores jku during validation; this warning is for clients that may not.
KID_MISSING
No kid in the header. JWKS rotation cannot select between overlapping keys without it. Token issuance is acceptable but operationally fragile.
EXP_MISSING
No exp claim in the payload. Such a token never expires — refresh, revocation, and least-privilege windows all rely on exp being present.
Request validation (HTTP 422)
Standard FastAPI envelope. Most common causes:
- Missing required field in the request body.
policyandissuer_profile_idboth supplied (or neither) on/v1/validate/jwt— exactly one is required.providerfield set to a value JWTShield does not recognise.
{
"detail": [
{
"loc": ["body"],
"msg": "Exactly one of 'policy' or 'issuer_profile_id' must be provided.",
"type": "value_error"
}
]
}