PrefectHQ/fastmcp · error · JoseError
Token type mismatch: expected {expected_token_use}, got {tok
Error message
Token type mismatch: expected {expected_token_use}, got {token_use} What it means
JWTIssuer.verify_token() distinguishes token purposes via the 'token_use' claim (e.g. access vs refresh/id). If the token decodes and signature-verifies but its token_use differs from the expected_token_use argument, it is rejected with JoseError — the token is cryptographically valid but is the wrong kind of token for this endpoint.
Source
Thrown at fastmcp_slim/fastmcp/server/auth/jwt_issuer.py:268
JoseError: If token is invalid, expired, or has wrong claims
"""
try:
# Decode and verify signature
payload = jwt.decode(
token,
self._jwt_key,
algorithms=["HS256"],
).claims
# Validate token type
token_use = payload.get("token_use", "access")
if token_use != expected_token_use:
logger.debug(
"Token type mismatch: expected %s, got %s",
expected_token_use,
token_use,
)
raise JoseError(
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")View on GitHub (pinned to 1f02114297)
Solutions
- Send the token whose token_use matches what the endpoint expects — check which stored token you attach to the request
- Fix the issuer/minting code to stamp token_use correctly for each token type
- Ensure verify_token is called with the expected_token_use value that matches the tokens your issuer emits
- If your flow doesn't use token-type separation, omit/align the token_use claim consistently between issuer and verifier
Example fix
// before
headers = {"Authorization": f"Bearer {refresh_token}"} # wrong token
// after
headers = {"Authorization": f"Bearer {access_token}"} Defensive patterns
Strategy: try-catch
Validate before calling
claims = jwt.decode(token, options={"verify_signature": False})
if claims.get("token_use") != "access":
raise ValueError("Wrong token selected; send the access token, not refresh/ID token") Type guard
def is_access_token(claims: dict) -> bool:
return claims.get("token_use") == "access" Try / catch
try:
payload = issuer.verify_token(token, expected_token_use="access")
except JoseError as e:
if "Token type mismatch" in str(e):
token = select_token_for_use("access")
payload = issuer.verify_token(token, expected_token_use="access")
else:
raise Prevention
- Store access and refresh tokens under distinct keys client-side
- Stamp token_use correctly at mint time and test it
- Never paste tokens across purposes in tests or docs examples
When it happens
Trigger: Calling verify_token(token, expected_token_use='access') on a JWT whose payload token_use is 'refresh' (or vice versa), mismatch at jwt_issuer.py:268; also happens when the wrong client passes an ID token where an access token is expected.
Common situations: Client stores multiple tokens and sends the wrong one in the Authorization header; a token-minting bug sets token_use incorrectly; API surface changed to require a token_use claim the issuer never set; copy-pasting a refresh token into an access-token slot in tests.
Related errors
- Assertion must include exp claim
- Assertion is not yet valid (nbf in future)
- Assertion iat is in the future
- Assertion lifetime too long (max {self.MAX_ASSERTION_LIFETIM
- Assertion exp too far in future (max {self.MAX_ASSERTION_LIF
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/b779535557891a65.
Report an issue: GitHub.