PrefectHQ/fastmcp · error · JoseError
Invalid token audience
Error message
Invalid token audience
What it means
verify_token() validates the 'aud' (audience) claim against the audience the JWTIssuer was configured with. The audience names the intended recipient of the token; a token signed correctly but addressed to a different audience (different API/resource) is rejected with JoseError('Invalid token audience') so tokens meant for service A cannot be replayed at service B.
Source
Thrown at fastmcp_slim/fastmcp/server/auth/jwt_issuer.py:287
f"Token type mismatch: expected {expected_token_use}, "
f"got {token_use}"
)
# Validate expiration
exp = payload.get("exp")
if exp is not None and exp < time.time():
logger.debug("Token expired")
raise JoseError("Token has expired")
# Validate issuer
if payload.get("iss") != self.issuer:
logger.debug("Token has invalid issuer")
raise JoseError("Invalid token issuer")
# Validate audience
if payload.get("aud") != self.audience:
logger.debug("Token has invalid audience")
raise JoseError("Invalid token audience")
logger.debug(
"Token verified successfully for subject=%s", payload.get("sub")
)
return payload
except JoseError as e:
logger.debug("Token validation failed: %s", e)
raise
View on GitHub (pinned to 1f02114297)
Solutions
- Set the audience on both the issuer (token minting) and the verifier (JWTIssuer) to the exact same string
- Check the OAuth resource indicator / audience parameter the client requests matches this server's configured audience
- Decode the token locally and diff payload['aud'] against your configured audience to spot case/slash/whitespace differences
- If this server legitimately accepts multiple audiences, configure the verifier to accept the full set rather than one
Example fix
// before JWTIssuer(audience="mcp-server") # tokens minted with aud="mcp-api" // after JWTIssuer(audience="mcp-api") # match the aud stamped at mint time
Defensive patterns
Strategy: validation
Validate before calling
claims = jwt.decode(token, options={"verify_signature": False})
expected_aud = "mcp-api"
if claims.get("aud") != expected_aud:
raise ValueError(f"Token aud {claims.get('aud')!r} != expected {expected_aud!r}") Type guard
def has_expected_audience(claims: dict, expected: str) -> bool:
return claims.get("aud") == expected Try / catch
try:
payload = issuer.verify_token(token)
except JoseError as e:
if "audience" in str(e).lower():
log_untrusted_token_rejected(token) # client requested wrong resource
raise
raise Prevention
- Define the audience string once and share it between client resource requests and server config
- Verify the OAuth client requests a token for the correct resource indicator
- Check case, whitespace, and scheme differences when audiences look similar
When it happens
Trigger: Calling verify_token() on a token whose payload['aud'] != self.audience (jwt_issuer.py:287): e.g. a token minted for resource 'api://other-service' being verified by a server configured with audience 'mcp-server', or an audience case/whitespace mismatch.
Common situations: Resource-server audience configured with a different string than the minting code uses (typo, case, trailing slash); reusing one client's tokens against another API; multi-resource deployments where the client picked the wrong resource indicator during OAuth; audience defaulted but never set on one side.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Invalid token issuer
- JWKS URI not configured
- No keys found in JWKS
- OAuth server rejected the static client credentials. Verify
- MultiAuth requires at least a server or one verifier
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/baf55957c998d7b2.
Report an issue: GitHub.