googleapis/mcp-toolbox · error

audience validation failed: expected %s, got %v

Error message

audience validation failed: expected %s, got %v

What it means

The token's audience claim parsed fine, but none of its audience values matches the Audience configured on this generic auth service. The library compares each aud entry against a.Audience and rejects tokens not issued for this deployment, preventing token confusion between services.

Source

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

		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.
type MCPAuthError = auth.MCPAuthError

// ValidateMCPAuth handles MCP auth token validation
func (a AuthService) ValidateMCPAuth(ctx context.Context, h http.Header) (map[string]any, error) {
	tokenString := h.Get("Authorization")
	if tokenString == "" {
		return nil, &MCPAuthError{Code: http.StatusUnauthorized, Message: "missing access token", ScopesRequired: a.ScopesRequired}
	}

	headerParts := strings.Split(tokenString, " ")
	if len(headerParts) != 2 || strings.ToLower(headerParts[0]) != "bearer" {
		return nil, &MCPAuthError{Code: http.StatusUnauthorized, Message: "authorization header must be in the format 'Bearer <token>'", ScopesRequired: a.ScopesRequired}

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Compare the 'expected' value in the error with the token's aud claim and set the auth service's audience config to the exact value the IdP emits (or vice versa).
  2. Re-request the token with the correct audience/resource parameter from your authorization server.
  3. Verify you are not using a token issued for a different environment or service.

Example fix

// before: server expects audience "my-toolbox" but token has aud "other-api"
{"aud":"other-api"}
// after: request token with audience=my-toolbox
{"aud":"my-toolbox"}
Defensive patterns

Strategy: validation

Validate before calling

claims := decodeJWTPayload(tokenString)
expected := cfg.Audience // value from your auth config
audList, _ := claims["aud"].([]any)
found := false
for _, a := range audList {
    if a == expected { found = true }
}
if s, ok := claims["aud"].(string); ok && s == expected { found = true }
if !found { return fmt.Errorf("token aud %v does not include configured audience %q", claims["aud"], expected) }

Type guard

func audienceMatches(claims map[string]any, want string) bool {
    switch a := claims["aud"].(type) {
    case string:
        return a == want
    case []any:
        for _, v := range range a { if v == want { return true } }
    }
    return false
}

Try / catch

claims, err := svc.GetClaimsFromHeader(ctx, header)
if err != nil {
    if strings.Contains(err.Error(), "audience validation failed") {
        // compare err's expected vs got values with your IdP's aud and fix config or token request
        return http.StatusForbidden
    }
    return http.StatusUnauthorized
}

Prevention

When it happens

Trigger: GetClaimsFromHeader receives a validly-signed token whose aud values (e.g. ['api://other-service']) do not include the configured Audience string, so isAudValid stays false.

Common situations: Audience mismatch after renaming or reconfiguring the server, reusing tokens minted for another API, copying config from one environment to another, or an IdP defaulting aud to a client-id different from the value set in the toolbox auth config.

Related errors


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