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

  1. Send the token whose token_use matches what the endpoint expects — check which stored token you attach to the request
  2. Fix the issuer/minting code to stamp token_use correctly for each token type
  3. Ensure verify_token is called with the expected_token_use value that matches the tokens your issuer emits
  4. 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

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


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