googleapis/mcp-toolbox · error

failed to parse and verify JWT token: %w

Error message

failed to parse and verify JWT token: %w

What it means

GetClaimsFromHeader could not parse or cryptographically verify the JWT found in the request header. jwt.Parse with the JWKS-backed keyfunc failed — this covers malformed token structure, unsupported algorithms, missing/expired claims, or signature verification failure against the fetched JWKS. The underlying jwt/v5 error is wrapped for inspection.

Source

Thrown at internal/auth/generic/generic.go:233

func (a AuthService) GetAuthorizationServer() string {
	return a.AuthorizationServer
}

// Verifies generic JWT access token inside the Authorization header
func (a AuthService) GetClaimsFromHeader(ctx context.Context, h http.Header) (map[string]any, error) {
	if a.McpEnabled {
		return nil, nil
	}

	tokenString := h.Get(a.Name + "_token")
	if tokenString == "" {
		return nil, nil
	}

	// Parse and verify the token signature
	token, err := jwt.Parse(tokenString, a.kf.Keyfunc)
	if err != nil {
		return nil, fmt.Errorf("failed to parse and verify JWT token: %w", err)
	}

	if !token.Valid {
		return nil, fmt.Errorf("invalid JWT token")
	}

	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		return nil, fmt.Errorf("invalid JWT claims format")
	}

	// Validate 'aud' (audience) claim
	aud, err := claims.GetAudience()
	if err != nil {
		return nil, fmt.Errorf("could not parse audience from token: %w", err)
	}

	isAudValid := false

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Decode the token at jwt.io and check header/alg, exp, and signature match the provider's JWKS
  2. Confirm the client fetches its token from the same authorizationServer configured in toolbox
  3. Request a fresh token — expired tokens fail verification
  4. Check for clock skew between toolbox host and auth server
  5. Trim whitespace and ensure the full three-part token is sent in the header

Example fix

// before (client sends wrong token type)
http.setRequestHeader("myauth_token", opaqueSessionId)
// after
http.setRequestHeader("myauth_token", jwtAccessToken)
Defensive patterns

Strategy: try-catch

Validate before calling

parts := strings.Split(tokenString, ".")
if len(parts) != 3 {
    return fmt.Errorf("token is not a three-part JWT")
}
// client-side: also check exp before sending
claims := decodePayload(parts[1])
if exp, ok := claims["exp"].(float64); ok && time.Now().Unix64() > int64(exp) {
    return fmt.Errorf("token already expired; fetch a new one")
}

Try / catch

claims, err := authSvc.GetClaimsFromHeader(ctx, header)
if err != nil {
    if strings.Contains(err.Error(), "failed to parse and verify JWT token") {
        // reject request with 401; optionally check errors.As for jwt/v5 error types:
        var vErr *jwt.ValidationError
        if errors.As(err, &vErr) && vErr.Errors&jwt.ValidationErrorExpired != 0 {
            // hint client to refresh token
        }
        return nil, http.StatusUnauthorized
    }
    return nil, http.StatusInternalServerError
}

Prevention

When it happens

Trigger: A request contains a non-empty <name>_token header whose value fails jwt.Parse: truncated/garbled token, signed with a key not in the JWKS, expired (exp in the past), used before nbf, or wrong algorithm in the header.

Common situations: Client sending an opaque token or a token from a different issuer into the <name>_token header; keys rotated on the auth server before JWKS refresh; clock skew making tokens appear expired; client pasting the token with extra whitespace.

Understand the failure class

Related errors


AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05). Data as JSON: /api/errors/7e101421a81ce4c6. Report an issue: GitHub.