crewAIInc/crewAI · error · HTTPException
Invalid token issuer
Error message
Invalid token issuer
What it means
Raised by OIDCAuth.authenticate() when jwt.decode() throws InvalidIssuerError: the JWT's `iss` claim does not equal the scheme's configured issuer URL. This guards against tokens signed by a trusted key but minted by a different authority (e.g. a sibling tenant). It maps to HTTP 401 and logs reason='invalid_issuer'.
Source
Thrown at lib/crewai/src/crewai/a2a/auth/server_schemes.py:324
raise HTTPException(
status_code=HTTP_401_UNAUTHORIZED,
detail="Token has expired",
) from None
except jwt.InvalidAudienceError:
logger.debug(
"OIDC authentication failed",
extra={"reason": "invalid_audience", "scheme": "oidc"},
)
raise HTTPException(
status_code=HTTP_401_UNAUTHORIZED,
detail="Invalid token audience",
) from None
except jwt.InvalidIssuerError:
logger.debug(
"OIDC authentication failed",
extra={"reason": "invalid_issuer", "scheme": "oidc"},
)
raise HTTPException(
status_code=HTTP_401_UNAUTHORIZED,
detail="Invalid token issuer",
) from None
except jwt.MissingRequiredClaimError as e:
logger.debug(
"OIDC authentication failed",
extra={"reason": "missing_claim", "claim": e.claim, "scheme": "oidc"},
)
raise HTTPException(
status_code=HTTP_401_UNAUTHORIZED,
detail=f"Missing required claim: {e.claim}",
) from None
except jwt.PyJWKClientError as e:
logger.error(
"OIDC authentication failed",
extra={
"reason": "jwks_client_error",
"error": str(e),View on GitHub (pinned to 754d7323be)
Solutions
- Decode the token and read its iss claim verbatim, then set the scheme's issuer to exactly that string (including scheme, host, path, trailing slash).
- For Azure AD, use the tenant-specific issuer (https://login.microsoftonline.com/<tenant-id>/v2.0) matching the token.
- Treat issuer/audience/jwks_url as one unit per environment and validate them together at startup.
- Never disable issuer validation to 'make it work'; fix the configured value instead.
Example fix
# before auth = OIDCAuth(jwks_url=jwks, issuer="https://idp.example.com", ...) # token has "https://idp.example.com/" # after auth = OIDCAuth(jwks_url=jwks, issuer="https://idp.example.com/", ...) # exact match with `iss` claim
Defensive patterns
Strategy: validation
Validate before calling
import jwt
claims = jwt.decode(access_token, options={"verify_signature": False})
assert claims.get("iss") == configured_issuer, (
f"iss mismatch: token={claims.get('iss')!r} scheme={configured_issuer!r}"
) Prevention
- Copy the issuer from a real token's iss claim, not from IdP marketing URLs.
- Watch for trailing-slash mismatches.
- For Azure AD, use the tenant-specific v2.0 issuer URL.
When it happens
Trigger: Scheme configured with issuer='https://idp.example.com' while the token's iss is 'https://tenant.idp.example.com/' (or differs only by trailing slash); multi-tenant IdPs where each tenant has a distinct issuer; mixing issuer URLs between prod and staging tenants.
Common situations: Trailing-slash mismatch between the configured issuer and the actual iss claim; Azure AD where the issuer includes a {tenantid} GUID; environment promotion carrying a staging issuer into a prod config.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Token has expired
- Invalid token audience
- Missing required claim: {e.claim}
- Error: {e}
- Project name cannot be empty or contain only whitespace
AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15).
Data as JSON: /api/errors/94928a27a74315ed.
Report an issue: GitHub.