mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-03 21:02:15 +00:00
docs: update JWT IDP doc (#11230)
This PR provides additional clarification to the steps needed to configure a JWT IDP in Zitadel Fixes #7646
This commit is contained in:
@@ -3,54 +3,160 @@ title: JWT IdP
|
||||
sidebar_label: JWT IdP
|
||||
---
|
||||
|
||||
## In simple terms
|
||||
|
||||
## Configure a JWT as an Identity Provider in ZITADEL
|
||||
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.
|
||||
|
||||
JSON Web Token Identity Provider (JWT IdP) gives you the possibility to use an (existing) JWT as federated identity.
|
||||
Imagine you have a Web Application Firewall (WAF) which handles your session for an existing application.
|
||||
You're now creating a new application which uses ZITADEL as its IDP / Authentication Server.
|
||||
The new app might even be opened from within the existing application and you want to reuse the session information for the new application.
|
||||
This is where JWT IdP comes into place.
|
||||
## When should I use JWT IdP?
|
||||
|
||||
All you need to provide is an endpoint where ZITADEL can receive a JWT and some information for its signature verification.
|
||||
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 process then might look like the following:
|
||||
The authentication flow with JWT IdP looks like this:
|
||||
|
||||

|
||||
|
||||
1. The user is logged into the existing application and the WAF holds the session information. It might even send a JWT to the application.
|
||||
The new application is opened by clicking on a link in the existing application.
|
||||
2. The application bootstraps and since it cannot find a session, it will create an OIDC Authorization Request to ZITADEL.
|
||||
In this request it provides a scope to directly request the JWT IdP.
|
||||
3. ZITADEL will do so and redirect to the preconfigured JWT Endpoint. While the endpoint is behind the WAF, ZITADEL is able to receive a JWT from a defined HTTP header.
|
||||
It will then validate its signature, which might require to call the configured Keys Endpoint.
|
||||
If the signature is valid and token is not expired, ZITADEL will then use the token and the enclosed `sub` claim as if it was an id_token returned from an OIDC IDP:
|
||||
It will try to match a user's external identity and if not possible, create a new user with the information of the provided token.
|
||||
Prerequisite for this is that the IDP setting `autoregister` is set to `true`.
|
||||
4. ZITADEL will then redirect to its main instance and the login flow will proceed.
|
||||
5. The user will be redirected to the Callback Endpoint of the new Application, where the application will exchange the code for tokens.
|
||||
The user is finally logged in the new application, without any direct interaction.
|
||||
**Step-by-Step Process**
|
||||
|
||||
### Terms and example values
|
||||
1. **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.
|
||||
|
||||
To further explain and illustrate how a JWT IdP works, we will assume the following:
|
||||
2. **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](/docs/apis/openidoauth/scopes#reserved-scopes) to pre-select the JWT IdP in the OIDC Authorization Request.
|
||||
|
||||
- the **Existing Application** is deployed under `apps.test.com/existing/`
|
||||
- the **New Application** is deployed under `new.test.com`
|
||||
- the **Login UI of ZITADEL** is deployed under `accounts.test.com`
|
||||
3. **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.
|
||||
|
||||
The **JWT IdP Configuration** might then be:
|
||||
- **JWT Endpoint** (Endpoint where ZITADEL will redirect to):<br/>`https://apps.test.com/existing/auth-new`
|
||||
- **Issuer** (of the JWT):<br/>`https://issuer.test.internal`
|
||||
- **Keys Endpoint** (where keys of the JWT Signature can be gathered):<br/>`https://issuer.test.internal/keys`
|
||||
- **Header Name** (of the JWT, Authorization if omitted):<br/>`x-custom-tkn`
|
||||
4. **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.
|
||||
|
||||
Therefore, if the user is redirected from ZITADEL to the JWT Endpoint on the WAF (`https://apps.test.com/existing/auth-new`),
|
||||
the session cookies previously issued by the WAF, will be sent along by the browser due to the path being on the same domain as the exiting application.
|
||||
The WAF will reuse the session and send the JWT in the HTTP header `x-custom-tkn` to its upstream, the ZITADEL JWT Endpoint (`https://accounts.test.com/ipds/jwt`).
|
||||
- **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.
|
||||
|
||||
For the signature validation, ZITADEL must be able to connect to Keys Endpoint (`https://issuer.test.internal/keys`)
|
||||
and it will check if the token was signed (claim `iss`) by the defined Issuer (`https://issuer.test.internal`).
|
||||
```js
|
||||
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`.)*
|
||||
|
||||
5. **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` (`iss` claim) 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.
|
||||
|
||||
6. **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:**
|
||||
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:**
|
||||
- **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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user