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

  1. Set the audience on both the issuer (token minting) and the verifier (JWTIssuer) to the exact same string
  2. Check the OAuth resource indicator / audience parameter the client requests matches this server's configured audience
  3. Decode the token locally and diff payload['aud'] against your configured audience to spot case/slash/whitespace differences
  4. 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

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

Related errors


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/baf55957c998d7b2. Report an issue: GitHub.