JWT IdP
In simple terms​
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.
When should I use JWT IdP?​
Use JWT IdP if:
- You have an existing application or Web Application Firewall (WAF) that authenticates users and is able to generate a JWT for them.
- You want users to access new applications via ZITADEL without re-authentication, effectively reusing an existing session.
- You want to enable silent single sign-on (SSO) between legacy and new apps.
- You wish to federate authentication with a system that can't act as a full OpenID Connect provider, but can issue JWTs.
Do not use JWT IdP if:
- You need ZITADEL to interactively authenticate users (e.g., password-based logins).
- There is no secure way to transfer or validate the JWT.
Configuring a JWT as an Identity Provider in ZITADEL​
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 endpoint to obtain the JWT.
- Information ZITADEL requires to validate the JWT, including issuer, signature keys, and which HTTP header carries the JWT.
Authentication using JWT IdP​
The authentication flow with JWT IdP looks like this:

Step-by-Step Process
-
User authenticates in the existing app (Server-side & Browser):
- The user logs in to the current application, with authentication managed (often) by a WAF or legacy system.
- The WAF or app creates a session and can issue a JWT representing that session.
-
User accesses new app (Browser):
- The user visits the new application, possibly via a link from the existing app.
- If no session is found, the new app initiates an OIDC login via ZITADEL.
- The user either selects the JWT IdP in the ZITADEL UI,
- or the application uses a custom scope to pre-select the JWT IdP in the OIDC Authorization Request.
-
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.
- This preserves critical OIDC flow state.
-
How to proxy the request (example):
The example below shows a simplified "serverless/WAF proxy" approach to:- obtain/generate the JWT,
- forward all the user's query params,
- and place the JWT in the appropriate HTTP header when proxying to ZITADEL.
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
getJwtForCurrentSessionwith your logic for retrieving/creating a JWT from the user's WAF session. Note:env.ZITADEL_JWT_IDP_ENDPOINTshould be set to the custom domain of your ZITADEL instance with the/idps/jwtpath, e.g.https://accounts.test.com/idps/jwt.)
-
-
ZITADEL receives and validates the JWT (Server-side):
- ZITADEL extracts the JWT from the configured HTTP header.
- ZITADEL verifies that:
- The signature matches using keys from the Keys Endpoint.
- The
issuer(issclaim) matches the configured issuer. - The JWT is not expired.
- Note:
- ZITADEL does not re-authenticate the user.
- ZITADEL does not issue this JWT.
- ZITADEL only verifies authenticity and validity.
- The JWT is treated as an external ID token, similar to third-party IdPs.
-
Login completes (Browser):
- ZITADEL finishes the OIDC flow.
- The browser is redirected to the application’s callback endpoint.
- The app exchanges the code for tokens, and the user is now logged in—without extra authentication steps.
Terms and Example Values​
Here’s a reference integration scenario:
- Existing Application:
apps.test.com/existing/ - New Application:
new.test.com - ZITADEL Login UI:
accounts.test.com
Sample JWT IdP Configuration:
-
JWT Endpoint:
Where ZITADEL redirects users to retrieve the JWT:
https://apps.test.com/existing/auth-new -
Issuer:
Theissclaim 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 toAuthorizationif not set):
x-custom-tkn
Clarifications and Best Practices:
- Why must the JWT Endpoint be on the same domain as the existing app?
This ensures browser sessions (cookies) are correctly sent and recognized by the WAF or app, so the right JWT can be generated. - Why are cookies sent automatically?
Browsers automatically include relevant cookies when redirecting within the same domain, enabling server-side authentication seamlessly. - Why send the JWT in a header, not as a parameter?
HTTP headers are more secure for transmitting sensitive tokens, prevent them from being exposed in URLs or logs, and avoid user tampering.
When ZITADEL performs signature validation, it uses the Keys Endpoint to:
- Verify the JWT’s signature
- Check the issuer matches the JWT IdP configuration
- Ensure the JWT is not expired