diff --git a/docs/docs/guides/integrate/identity-providers/jwt_idp.md b/docs/docs/guides/integrate/identity-providers/jwt_idp.md index 52324ad5c..372b0e464 100644 --- a/docs/docs/guides/integrate/identity-providers/jwt_idp.md +++ b/docs/docs/guides/integrate/identity-providers/jwt_idp.md @@ -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: ![JWT IdP Architecture](/img/guides/jwt_idp.png) -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):
`https://apps.test.com/existing/auth-new` - - **Issuer** (of the JWT):
`https://issuer.test.internal` - - **Keys Endpoint** (where keys of the JWT Signature can be gathered):
`https://issuer.test.internal/keys` - - **Header Name** (of the JWT, Authorization if omitted):
`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