apps/docs/content/guides/integrate/identity-providers/jwt_idp.mdx
A JWT Identity Provider (JWT IdP) allows ZITADEL to accept a JSON Web Token (JWT) issued and signed by an external system as proof that a user has already been authenticated elsewhere. In this flow, ZITADEL does not perform authentication itself, but relies on the trustworthiness and validity of the provided JWT.
Use JWT IdP if:
Do not use JWT IdP if:
Configuring JWT IdP enables ZITADEL to accept a JWT generated by an external authentication system, such as your WAF or a legacy app. The typical setup involves obtaining the JWT from the user's previously established session.
To configure JWT IdP in ZITADEL, you need to provide:
The authentication flow with JWT IdP looks like this:
Step-by-Step Process
User authenticates in the existing app (Server-side & Browser):
User accesses new app (Browser):
ZITADEL redirects to the JWT Endpoint (Browser):
ZITADEL redirects the user's browser to the configured JWT Endpoint (usually behind the WAF).
Purpose: Redirecting the browser ensures that any existing session cookies for the previous app/WAF are available, letting the WAF determine the authenticated user and issue the appropriate JWT.
WAF/session gateway attaches JWT and proxies request to ZITADEL (Server-side):
The JWT Endpoint (behind the WAF) uses the session (from browser cookies) to ascertain the authenticated user.
The WAF/server injects the JWT into a specific HTTP header (never as a URL parameter) and forwards (proxies) the user’s request with all original query parameters intact to ZITADEL’s JWT receiving endpoint.
How to proxy the request (example): The example below shows a simplified "serverless/WAF proxy" approach to:
export default {
async fetch(request, env, ctx) {
// 1. Obtain the JWT for the current session (implementation will vary)
const jwt = await getJwtForCurrentSession(request, env);
// 2. Prepare the ZITADEL endpoint URL and copy all query params
const userUrl = new URL(request.url);
// Important: The ZITADEL_JWT_IDP_ENDPOINT should be your ZITADEL custom domain plus "/idps/jwt"
// For example: https://accounts.test.com/idps/jwt
const zitadelUrl = new URL(env.ZITADEL_JWT_IDP_ENDPOINT);
userUrl.searchParams.forEach((v, k) => zitadelUrl.searchParams.set(k, v));
// 3. Proxy request, attaching JWT in the configured HTTP header
const zitadelReq = new Request(zitadelUrl.toString(), {
method: "GET",
headers: {
"x-custom-tkn": jwt, // Use header name as set in ZITADEL JWT IdP settings
"Accept": request.headers.get("Accept") || "*/*",
},
redirect: "manual",
});
// 4. Send to ZITADEL and relay its response to the browser
const zitadelResp = await fetch(zitadelReq);
return new Response(zitadelResp.body, {
status: zitadelResp.status,
headers: zitadelResp.headers,
});
}
}
(Replace getJwtForCurrentSession with your logic for retrieving/creating a JWT from the user's WAF session.
Note: env.ZITADEL_JWT_IDP_ENDPOINT should be set to the custom domain of your ZITADEL instance
with the /idps/jwt path, e.g. https://accounts.test.com/idps/jwt.)
ZITADEL receives and validates the JWT (Server-side):
issuer (iss claim) matches the configured issuer.Login completes (Browser):
Here’s a reference integration scenario:
apps.test.com/existing/new.test.comaccounts.test.comSample JWT IdP Configuration:
JWT Endpoint:
Where ZITADEL redirects users to retrieve the JWT:
https://apps.test.com/existing/auth-new
Issuer:
The iss claim ZITADEL expects in the JWT:
https://issuer.test.internal
Keys Endpoint:
Where ZITADEL fetches public keys for signature verification:
https://issuer.test.internal/keys
Header Name:
The HTTP header delivering the JWT (defaults to Authorization if not set):
x-custom-tkn
Clarifications and Best Practices:
When ZITADEL performs signature validation, it uses the Keys Endpoint to: