dgraph-io/dgraph · error
unable to parse jwt token:%v
Error message
unable to parse jwt token:%v
What it means
Generic failure wrapper: any error produced while parsing/validating the JWT (signature invalid, token expired, malformed token, keyfunc errors like kid/alg failures) is re-wrapped as "unable to parse jwt token:%v". The underlying cause in %v is what actually matters for debugging. Note the JWK-URL loop swallows per-URL errors and only the last one is wrapped here.
Source
Thrown at graphql/authorization/auth.go:412
algo, _ := token.Header["alg"].(string)
if algo != a.Algo {
return nil, errors.Errorf("unexpected signing method: Expected %s Found %s",
a.Algo, algo)
}
switch a.SigningMethod.(type) {
case *jwt.SigningMethodHMAC:
return []byte(a.VerificationKey), nil
case *jwt.SigningMethodRSA:
return a.RSAPublicKey, nil
}
return nil, errors.Errorf("couldn't parse signing method from token header: %s", algo)
})
}
if err != nil {
return nil, errors.Errorf("unable to parse jwt token:%v", err)
}
claims, ok := token.Claims.(*CustomClaims)
if !ok || !token.Valid {
return nil, errors.Errorf("claims in jwt token is not map claims")
}
if err := claims.validateAudience(); err != nil {
return nil, err
}
return claims, nil
}
// FetchJWKs fetches the JSON Web Key sets for the JWKUrls. It returns an error if
// the fetching of key is failed even for one of the JWKUrl.
func (a *AuthMeta) FetchJWKs() error {
if len(a.JWKUrls) == 0 {
return errors.Errorf("No JWKUrl supplied")View on GitHub (pinned to 759e242be6)
Solutions
- Read the wrapped cause after "unable to parse jwt token:" to identify the real problem.
- Decode the token locally (jwt.io or jwt.Parse) to check expiry, signature, and header.
- Sync clocks (NTP) if the cause is token expired with skew.
- Confirm the verification key/JWK set matches the issuer's current signing key.
- Return structured errors to clients (401 with reason) instead of the raw wrapped message.
Example fix
// before
return nil, errors.Errorf("unable to parse jwt token:%v", err)
// after
if errors.Is(err, jwt.ErrTokenExpired) {
return nil, status.Error(codes.Unauthenticated, "token expired")
}
return nil, errors.Wrap(err, "unable to parse jwt token") Defensive patterns
Strategy: try-catch
Validate before calling
func precheckToken(jwtStr string) error {
parts := strings.Split(jwtStr, ".")
if len(parts) != 3 { return errors.New("malformed JWT: expected 3 segments") }
return nil
} Try / catch
claims, err := auth.ExtractCustomClaims(ctx, jwtStr)
if err != nil {
var cause string
if pkgErr, ok := err.(interface{ Cause() error }); ok { cause = pkgErr.Cause().Error() }
log.Printf("JWT rejected: %v (cause: %s)", err, cause)
return nil, status.Error(codes.Unauthenticated, "invalid or expired token")
} Prevention
- Always log the wrapped cause, not just the outer message
- Check token expiry client-side before sending requests
- Sync server clocks with NTP
- Return 401 with a stable reason code to callers
When it happens
Trigger: After validateThroughJWKUrl or the static-key ParseWithClaims returns err != nil, validateJWTCustomClaims wraps it. Any prior error in this list (351–355) or jwt library errors (signature invalid, token expired, malformed) surface through here when ExtractCustomClaims is called.
Common situations: Client sends expired/tampered tokens; clock skew between services; token truncated by proxy or header size limit; all configured JWK URLs failed validation; key mismatch after rotation.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- expecting either JWKUrl or JWKUrls, both were given
- expecting either JWKUrl/JWKUrls or (VerificationKey, Algo),
- required field missing in Dgraph.Authorization:%s
- invalid Bearer-formatted header value for JWT (%s)
- audience value was expected but not provided
AI-assisted analysis of dgraph-io/dgraph@759e242be6 (2026-09-01).
Data as JSON: /api/errors/366a990ded7a0f36.
Report an issue: GitHub.