mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
# Which Problems Are Solved naming inconsistencies: Configuration <-> Settings # How the Problems Are Solved # Additional Changes # Additional Context - Closes #11282
162 lines
7.3 KiB
Plaintext
162 lines
7.3 KiB
Plaintext
---
|
||
title: JWT IdP
|
||
sidebar_label: 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**
|
||
|
||
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.
|
||
|
||
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](/apis/openidoauth/scopes#reserved-scopes) to pre-select the JWT IdP in the OIDC Authorization Request.
|
||
|
||
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.
|
||
|
||
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.
|
||
|
||
- **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.
|
||
|
||
```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 settings
|
||
- Ensure the JWT is not expired
|