hashicorp/nomad · error
unable to verify signature of JWT token: %v
Error message
unable to verify signature of JWT token: %v
What it means
Validate wraps any failure from the underlying JWT validator (bad signature, expired token, malformed token, wrong audience) with this message. It means the token could not be cryptographically/structurally verified against the auth method's configuration.
Source
Thrown at lib/auth/jwt/validator.go:69
toAlgFn := func(m string) jwt.Alg { return jwt.Alg(m) }
algorithms := helper.ConvertSlice(methodConf.SigningAlgs, toAlgFn)
expected := jwt.Expected{
Audiences: methodConf.BoundAudiences,
SigningAlgorithms: algorithms,
NotBeforeLeeway: methodConf.NotBeforeLeeway,
ExpirationLeeway: methodConf.ExpirationLeeway,
ClockSkewLeeway: methodConf.ClockSkewLeeway,
}
validator, err := jwt.NewValidator(keySet)
if err != nil {
return nil, err
}
claims, err := validator.Validate(ctx, token, expected)
if err != nil {
return nil, fmt.Errorf("unable to verify signature of JWT token: %v", err)
}
// validate issuer manually, because we allow users to specify an array
if len(methodConf.BoundIssuer) > 0 {
if _, ok := claims["iss"]; !ok {
return nil, fmt.Errorf(
"auth method specifies BoundIssuers but the provided token does not contain issuer information",
)
}
if iss, ok := claims["iss"].(string); !ok {
return nil, fmt.Errorf("unable to read iss property of provided token")
} else if !slices.Contains(methodConf.BoundIssuer, iss) {
return nil, fmt.Errorf("invalid JWT issuer: %v", claims["iss"])
}
}
return claims, nil
}View on GitHub (pinned to 482b49bf1a)
Solutions
- Read the wrapped %v cause to distinguish expired vs bad-signature vs malformed
- Re-obtain a fresh token and retry immediately
- Verify the auth method's JWKSURL/KeySource/OIDCDiscoveryURL and BoundAudiences match your IdP
- Check clock skew between client and server; ensure IdP keys haven't rotated without cache refresh
Example fix
// before: expired token reused from cache token := cachedToken // after: fetch/refresh token before login token := idp.GetFreshToken()
Defensive patterns
Strategy: try-catch
Validate before calling
// verify token structurally and check expiry before calling Validate
token, _, err := new(jwt.Parser).ParseUnverified(rawToken)
if err != nil { return ErrMalformedToken }
var claims jwt.MapClaims
token.Claims = &claims
if exp, err := claims.GetExpirationTime(); err == nil && exp != nil && exp.Time.Before(time.Now()) {
return ErrTokenExpired
} Try / catch
claims, err := validator.Validate(ctx, token, expected)
if err != nil {
if strings.Contains(err.Error(), "token is expired") {
return refreshTokenAndRetry()
}
return fmt.Errorf("jwt validation failed: %w", err)
} Prevention
- Refresh tokens proactively before expiry instead of caching indefinitely
- Keep the auth method's JWKS URL / OIDC discovery config in sync with your IdP
- Set BoundAudiences to the exact aud value your IdP emits
- Monitor NTP/clock skew on servers validating exp/iat
When it happens
Trigger: Login with a JWT whose signature doesn't verify against the configured JWKS/public keys, an expired token, wrong aud claim, or a token signed by an unexpected key.
Common situations: IdP rotated signing keys but cached JWKS is stale; testing a dev token against prod config; clock skew causing expiry; wrong key source (JWKS URL, PEM, or OIDC discovery URL) configured on the auth method.
Related errors
- no signed workload identity available
- JWT login returned an empty secret
- JWT login did not return a token
- auth method specifies BoundIssuers but the provided token do
- unable to read iss property of provided token
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/41c2f49f7d0a8cfd.
Report an issue: GitHub.