keycloak/keycloak · error · VerificationException

Failed to parse JWT

Error message

Failed to parse JWT

What it means

Thrown by TokenVerifier.parse() when constructing new JWSInput(tokenString) raises a JWSInputException. This means the string could not even be interpreted as a JWS compact serialization — typically malformed base64url, wrong segment count, or an unparseable header. The original JWSInputException is chained as the cause. This fires before any JSON deserialization of the payload (a separate, later check handles payload reading).

Source

Thrown at core/src/main/java/org/keycloak/TokenVerifier.java:408

     * Add check for verifying that token issuedFor (azp claim) is the expected value
     *
     * @param expectedIssuedFor issuedFor, which needs to be in the target token. Can't be null
     * @return This token verifier
     */
    public TokenVerifier<T> issuedFor(String expectedIssuedFor) {
        return this.replaceCheck(IssuedForCheck.class, true, new IssuedForCheck(expectedIssuedFor));
    }

    public TokenVerifier<T> parse() throws VerificationException {
        if (jws == null) {
            if (tokenString == null) {
                throw new VerificationException("Token not set");
            }

            try {
                jws = new JWSInput(tokenString);
            } catch (JWSInputException e) {
                throw new VerificationException("Failed to parse JWT", e);
            }


            try {
                token = jws.readJsonContent(clazz);
            } catch (JWSInputException e) {
                throw new VerificationException("Failed to read access token from JWT", e);
            }
        }
        return this;
    }

    public T getToken() throws VerificationException {
        if (token == null) {
            parse();
        }
        return token;
    }

View on GitHub (pinned to 66c7e15a37)

Solutions

  1. Inspect the chained JWSInputException cause for the precise parse stage that failed.
  2. Verify the token has exactly two '.' separators and uses base64url alphabet (A–Z, a–z, 0–9, -, _) with no padding issues.
  3. Ensure the token is not URL-encoded when passed to the verifier; strip 'Bearer ' prefix and any surrounding whitespace.
  4. If the value is not a JWT at all, route it to the appropriate validator instead of TokenVerifier.

Example fix

// before: header includes 'Bearer ' prefix or is URL-encoded
TokenVerifier.create(rawHeader, AccessToken.class).verify();

// after: normalize the token string first
String token = rawHeader;
if (token.startsWith("Bearer ")) token = token.substring(7);
token = URLDecoder.decode(token, StandardCharsets.UTF_8);
TokenVerifier.create(token, AccessToken.class).verify();
Defensive patterns

Strategy: validation

Validate before calling

// Normalize the token string before parsing
static String normalize(String raw) {
  if (raw == null) return null;
  if (raw.startsWith("Bearer ")) raw = raw.substring(7);
  return raw.trim();
}
String token = normalize(header);
if (token == null || token.split("\\.").length != 3) {
  // not a compact JWS — reject before parsing
}
TokenVerifier.create(token, AccessToken.class).parse();

Type guard

static boolean isCompactJws(String s) {
  return s != null && s.split("\\.").length == 3
      && s.chars().allMatch(c -> isBase64UrlChar((char) c) || c == '.');
}

Try / catch

try {
  TokenVerifier.create(token, AccessToken.class).parse();
} catch (VerificationException e) {
  if (e.getMessage().equals("Failed to parse JWT")) {
    // inspect e.getCause() (JWSInputException); normalize encoding/structure and retry
  } else throw e;
}

Prevention

When it happens

Trigger: Passing a token string that is not a valid compact JWS (not three base64url segments), is URL-encoded instead of base64url-encoded, has been truncated, or contains illegal characters. JWSInput's constructor performs structural + base64 + header-JSON parsing and throws on any failure.

Common situations: Double URL-encoding of the token in transit, a proxy stripping/replacing '+' or '_' characters, copy-paste truncation, or passing a raw SAML/opaque token to a JWT verifier.

Understand the failure class

Related errors


AI-assisted analysis of keycloak/keycloak@66c7e15a37 (2026-08-14). Data as JSON: /api/errors/b895a4f8a49b56ad. Report an issue: GitHub.