googleapis/mcp-toolbox · error

could not parse audience from token: %w

Error message

could not parse audience from token: %w

What it means

The JWT parsed successfully but its 'aud' (audience) claim could not be read via MapClaims.GetAudience(). The library requires an audience claim to verify the token was issued for this toolbox audience. GetAudience fails when the claim is absent or is not a string or array of strings.

Source

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

	// 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
	for _, audItem := range aud {
		if audItem == a.Audience {
			isAudValid = true
			break
		}
	}

	if !isAudValid {
		return nil, fmt.Errorf("audience validation failed: expected %s, got %v", a.Audience, aud)
	}

	return claims, nil
}

// MCPAuthError represents an error during MCP authentication validation.

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Re-issue the token including an 'aud' claim matching the audience configured on the auth service.
  2. Check the token payload at jwt.io and fix the 'aud' type to a string or array of strings.
  3. If your IdP cannot add 'aud', configure the audience correctly or use a token flow that includes it (e.g. request scope/audience when exchanging credentials).

Example fix

// before
{"iss":"https://idp.example.com","sub":"user1"}
// after
{"iss":"https://idp.example.com","sub":"user1","aud":"toolbox-audience"}
Defensive patterns

Strategy: validation

Validate before calling

payload := decodeJWTPayload(tokenString) // base64 decode part 2
aud, ok := payload["aud"]
if !ok {
    return fmt.Errorf("token has no aud claim; re-request with audience parameter")
}
switch aud.(type) {
case string, []any:
default:
    return fmt.Errorf("aud must be string or array, got %T", aud)
}

Type guard

func hasValidAud(claims map[string]any) bool {
    a, ok := claims["aud"]
    if !ok { return false }
    switch v := a.(type) {
    case string:
        return v != ""
    case []any:
        return len(v) > 0
    default:
        return false
    }
}

Try / catch

claims, err := svc.GetClaimsFromHeader(ctx, header)
if err != nil {
    var audErr *jwt.AudienceError
    if errors.As(err, &audErr) || strings.Contains(err.Error(), "could not parse audience") {
        return http.StatusUnauthorized // token lacks usable aud claim
    }
    return http.StatusInternalServerError
}

Prevention

When it happens

Trigger: GetClaimsFromHeader verifies a token whose claims lack 'aud' or whose 'aud' has an unexpected JSON type (number, object, null), then calls claims.GetAudience() which returns an error that is wrapped into this message.

Common situations: Identity-provider tokens minted without an audience (e.g. service-account tokens), tokens with 'aud' as a non-string JSON value, or tokens intended for a different product that omit the claim.

Related errors


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