PrefectHQ/fastmcp · error · JoseError

Invalid token issuer

Error message

Invalid token issuer

What it means

verify_token() validates the 'iss' (issuer) claim against the issuer value the JWTIssuer was constructed with. A token signed with the correct key but issued by a different issuer (different deployment, tenant, or environment) is rejected with JoseError('Invalid token issuer') to prevent tokens minted by one system being accepted by another.

Source

Thrown at fastmcp_slim/fastmcp/server/auth/jwt_issuer.py:282

                    "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")
                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. Make the JWTIssuer's issuer configuration identical to the value stamped into tokens at mint time (exact string match)
  2. Check env-specific config — trailing slashes, scheme (http/https), and host must match exactly
  3. In multi-tenant setups, ensure the request is routed to the verifier configured for the token's tenant/issuer
  4. Decode the token locally and compare payload['iss'] with your configured issuer to see the exact mismatch

Example fix

// before
JWTIssuer(issuer="https://api.example.com/")  # token has no trailing slash
// after
JWTIssuer(issuer="https://api.example.com")  # match mint-time iss exactly
Defensive patterns

Strategy: validation

Validate before calling

claims = jwt.decode(token, options={"verify_signature": False})
expected_issuer = os.environ["FASTMCP_JWT_ISSUER"]
if claims.get("iss") != expected_issuer:
    raise ValueError(f"Token iss {claims.get('iss')!r} != configured {expected_issuer!r}")

Type guard

def has_expected_issuer(claims: dict, expected: str) -> bool:
    return claims.get("iss") == expected

Try / catch

try:
    payload = issuer.verify_token(token)
except JoseError as e:
    if "issuer" in str(e).lower():
        log_untrusted_token_rejected(token)  # do not retry; investigate config
        raise
    raise

Prevention

When it happens

Trigger: Calling verify_token() on a token whose payload['iss'] != self.issuer (jwt_issuer.py:282): e.g. a token from staging used against production, a misconfigured issuer base URL, or a multi-tenant setup routing tokens to the wrong tenant verifier.

Common situations: Environment variable for the issuer differs between issuer and verifier deployments (trailing slash, http vs https, localhost vs domain); pointing a dev client at prod; copying tokens between environments in tests; tenant ID omitted from the configured issuer URL.

Understand the failure class

Related errors


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