docs: introduce versioned docs and migrate to fuma (#11166)

## Todos for release

- [x] Configure Env in docs project on vercel
- [x] Configure Root Path in the docs project on vercel
- [ ] Remove old CSP https://github.com/zitadel/website/pull/1592

## What we did

This pull request migrates the project documentation from the old
`docs/` directory to the new `apps/docs/` directory, introduces a new
documentation system built with Next.js and Fumadocs, and updates all
relevant references, configuration files, and documentation to reflect
this change. It also adds new configuration and ignore files for the new
documentation app, updates CI and linting to exclude the new docs from
certain checks, and revises the contributing guidelines accordingly.

**Documentation System Migration and New Docs App**

* Migrated all documentation from `docs/` to `apps/docs/`, and updated
all references in `README.md`, `CONTRIBUTING.md`, and other files to
point to the new location.
[[1]](diffhunk://#diff-eca12c0a30e25b4b46522ebf89465a03ba72a03f540796c979137931d8f92055L585-L640)
[[2]](diffhunk://#diff-eca12c0a30e25b4b46522ebf89465a03ba72a03f540796c979137931d8f92055L660-R607)
[[3]](diffhunk://#diff-eca12c0a30e25b4b46522ebf89465a03ba72a03f540796c979137931d8f92055L740-R687)
[[4]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L2-R3)
[[5]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L30-R30)
* Added a new Next.js/Fumadocs-based documentation app under
`apps/docs/`, including core app files, layouts, routing, search API,
and a comprehensive `README.md` with development and contribution
instructions.
[[1]](diffhunk://#diff-5a1b07344a2c1b4d3f37b23ff1388b62cd9f57dea3c1cd23d8a0412b7602b132R1-R74)
[[2]](diffhunk://#diff-462b9ad1eabbb7d1c29bb9c36e4931eb180fd7190900e6d2babf8f4d66ad1c28R1-R39)
[[3]](diffhunk://#diff-e16ae25660ded787b10ac35dea96d5ecaacf895dae0afc8a9bd4382dc79a8c87R1-R7)
[apps/docs/app/[[...slug]]/layout.tsxR1-R81](diffhunk://#diff-59e08acde4e805b7aeccef1dcf98f1d71dfc550777e6b9402085cee0e9fa4e0aR1-R81),
[apps/docs/app/[[...slug]]/page.tsxR1-R79](diffhunk://#diff-e5df3f80d0fa01e12d63d81f29c57a9d14e846c78fd3beabb2ef768e38fd9580R1-R79),
[[4]](diffhunk://#diff-389b34918e040cacaa87cd7201ffa462cc2b0b716736f537e3d3c660ac69353bR1-R7)
[[5]](diffhunk://#diff-d3b03416d1c457b19f1c27b26f2db741412df841b875c26ccadc36e4522247f4R1-R29)
[[6]](diffhunk://#diff-c8fb8339570a5305809be7c618e14705fd86390dc278e6ce8ba224a7bc8b0c3cR1-R25)

**Configuration and Tooling Updates**

* Updated `.github/workflows/codeql.yml`, `.golangci.yaml`, and
`.github/dependabot.yml` to properly handle the new docs app: excluded
`apps/docs` from certain checks, added npm dependency updates for the
docs app, and excluded generated content.
[[1]](diffhunk://#diff-12783128521e452af0cfac94b99b8d250413c516ec71fe6d97dbea666ff7ba27L8-R14)
[[2]](diffhunk://#diff-9917ddc9f1c3304218f7269265b746d997c5c0615478177b5fceecd33ef47cb5R5-R6)
[[3]](diffhunk://#diff-9917ddc9f1c3304218f7269265b746d997c5c0615478177b5fceecd33ef47cb5R126-R129)
[[4]](diffhunk://#diff-dd4fbda47e51f1e35defb9275a9cd9c212ecde0b870cba89ddaaae65c5f3cd28R89-R106)
* Updated `.devcontainer/devcontainer.json` to use the latest Go 1.25.3
version for consistency.

**Licensing and Miscellaneous**

* Added `apps/docs/` to the list of licensed directories in
`LICENSING.md`.

These changes ensure the documentation is now maintained in a modern,
scalable system and all project tooling is updated to support the new
structure.

---------

Co-authored-by: Federico Coppede <[email protected]>
This commit is contained in:
Florian Forster
2026-01-29 08:38:36 -08:00
committed by GitHub
co-authored by Federico Coppede
parent 7b6395c348
commit d8ea041c56
2826 changed files with 78739 additions and 23571 deletions
@@ -0,0 +1,41 @@
//go:build ignore
package main
import (
"fmt"
"io"
"net/http"
"github.com/zitadel/zitadel-go/v3/pkg/actions"
)
const signingKey = "somekey" // signing key received after creating the target
// webhook HandleFunc to read the request body and then print out the contents
func webhook(w http.ResponseWriter, req *http.Request) {
// read the body content
sentBody, err := io.ReadAll(req.Body)
if err != nil {
// if there was an error while reading the body return an error
http.Error(w, "error", http.StatusInternalServerError)
return
}
defer req.Body.Close()
// validate signature
if err := actions.ValidateRequestPayload(sentBody, &req.Header, signingKey); err != nil {
// if the signed content is not equal the sent content return an error
http.Error(w, "error", http.StatusInternalServerError)
return
}
// print out the read content
fmt.Println(string(sentBody))
}
func main() {
// handle the HTTP call under "/webhook"
http.HandleFunc("/webhook", webhook)
// start an HTTP server with the before defined function to handle the endpoint under "http://localhost:8090"
http.ListenAndServe(":8090", nil)
}
@@ -0,0 +1,109 @@
//go:build ignore
package main
import (
"context"
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"log"
"net/http"
"os"
"github.com/go-jose/go-jose/v4"
"github.com/zitadel/oidc/v3/pkg/client"
"github.com/zitadel/oidc/v3/pkg/client/rp"
"github.com/zitadel/oidc/v3/pkg/oidc"
)
// webhook HandleFunc to read the request body and then print out the contents
func webhook(signatureAlgorithms []jose.SignatureAlgorithm, keySet oidc.KeySet, privateKey any) func(w http.ResponseWriter, req *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
// read the body content
sentBody, err := io.ReadAll(req.Body)
if err != nil {
// if there was an error while reading the body return an error
http.Error(w, "error", http.StatusInternalServerError)
return
}
defer req.Body.Close()
// decrypt the JWE, validate the JWT and extract the payload from it
payload, err := validateJWE(req.Context(), string(sentBody), signatureAlgorithms, keySet, privateKey)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// print out the payload
fmt.Println(string(payload))
}
}
func validateJWE(ctx context.Context, jweString string, signatureAlgorithms []jose.SignatureAlgorithm, keySet oidc.KeySet, privateKey any) ([]byte, error) {
// For parsing the JWE, we need to specify which algorithms we expect.
// You can specify either `RSA_OAEP_256` (rsa.PrivateKey) or `ECDH_ES_A256KW` (ecdsa.PrivateKey) or both, depending on which private key(s) you have.
// The content encryption algorithm will always be `A256GCM` as used by Zitadel.
parsedJWE, err := jose.ParseEncrypted(jweString, []jose.KeyAlgorithm{jose.RSA_OAEP_256, jose.ECDH_ES_A256KW}, []jose.ContentEncryption{jose.A256GCM})
if err != nil {
return nil, err
}
// In this example we only use a single key and loaded it on start, but you can manage and rotate the key used for the encryption through the API.
// You might also have a key management system in your application.
// Using the KeyID from the JWE header (parsedJWE.Header.KeyID), we could load the correct private key for decryption.
// Decrypt the JWE using the private key to get the inner JWT
decryptedJWT, err := parsedJWE.Decrypt(privateKey)
if err != nil {
return nil, err
}
// Now validate the inner JWT and return the payload
return validateJWT(ctx, string(decryptedJWT), signatureAlgorithms, keySet)
}
func validateJWT(ctx context.Context, jwtString string, algorithms []jose.SignatureAlgorithm, keySet oidc.KeySet) ([]byte, error) { //nolint:typecheck
// Parse the signed JWT to get the JWS object (which contains the signatures and unverified payload)
parsedJWS, err := jose.ParseSigned(jwtString, algorithms)
if err != nil {
return nil, err
}
// Verify the signature using the retrieved key and return the payload
return keySet.VerifySignature(ctx, parsedJWS)
}
func main() {
ctx := context.Background()
// set the issuer to the ZITADEL instance URL
issuer := "http://localhost:8080"
// the oidc client library will call the discovery endpoint to get the JWKS URI and supported signing algorithms
discover, err := client.Discover(ctx, issuer, http.DefaultClient)
if err != nil {
log.Fatal(err)
}
signatureAlgorithms := make([]jose.SignatureAlgorithm, len(discover.IDTokenSigningAlgValuesSupported))
for i, alg := range discover.IDTokenSigningAlgValuesSupported {
signatureAlgorithms[i] = jose.SignatureAlgorithm(alg)
}
keySet := rp.NewRemoteKeySet(http.DefaultClient, discover.JwksURI)
// this example uses the private key from the PEM file to decrypt the JWE
// make sure to load your private key accordingly
privateKeyPEM, err := os.ReadFile("./private-key.pem")
if err != nil {
log.Fatal(err)
}
block, _ := pem.Decode(privateKeyPEM)
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
log.Fatal(err)
}
// handle the HTTP call under "/webhook"
http.HandleFunc("/webhook", webhook(signatureAlgorithms, keySet, privateKey))
// start an HTTP server with the before defined function to handle the endpoint under "http://localhost:8090"
http.ListenAndServe(":8090", nil)
}
@@ -0,0 +1,72 @@
//go:build ignore
package main
import (
"context"
"fmt"
"io"
"log"
"net/http"
"github.com/go-jose/go-jose/v4"
"github.com/zitadel/oidc/v3/pkg/client"
"github.com/zitadel/oidc/v3/pkg/client/rp"
"github.com/zitadel/oidc/v3/pkg/oidc"
)
// webhook HandleFunc to read the request body and then print out the contents
func webhook(signatureAlgorithms []jose.SignatureAlgorithm, keySet oidc.KeySet) func(w http.ResponseWriter, req *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
// read the body content
sentBody, err := io.ReadAll(req.Body)
if err != nil {
// if there was an error while reading the body return an error
http.Error(w, "error", http.StatusInternalServerError)
return
}
defer req.Body.Close()
// validate the JWT and extract the payload from it
payload, err := validateJWT(req.Context(), string(sentBody), signatureAlgorithms, keySet)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// print out the payload
fmt.Println(string(payload))
}
}
func validateJWT(ctx context.Context, jwtString string, algorithms []jose.SignatureAlgorithm, keySet oidc.KeySet) ([]byte, error) { //nolint:typecheck
// Parse the signed JWT to get the JWS object (which contains the signatures and unverified payload)
parsedJWS, err := jose.ParseSigned(jwtString, algorithms)
if err != nil {
return nil, err
}
// Verify the signature using the retrieved key and return the payload
return keySet.VerifySignature(ctx, parsedJWS)
}
func main() {
ctx := context.Background()
// set the issuer to the ZITADEL instance URL
issuer := "http://localhost:8080"
// the oidc client library will call the discovery endpoint to get the JWKS URI and supported signing algorithms
discover, err := client.Discover(ctx, issuer, http.DefaultClient)
if err != nil {
log.Fatal(err)
}
signatureAlgorithms := make([]jose.SignatureAlgorithm, len(discover.IDTokenSigningAlgValuesSupported))
for i, alg := range discover.IDTokenSigningAlgValuesSupported {
signatureAlgorithms[i] = jose.SignatureAlgorithm(alg)
}
keySet := rp.NewRemoteKeySet(http.DefaultClient, discover.JwksURI)
// handle the HTTP call under "/webhook"
http.HandleFunc("/webhook", webhook(signatureAlgorithms, keySet))
// start an HTTP server with the before defined function to handle the endpoint under "http://localhost:8090"
http.ListenAndServe(":8090", nil)
}
@@ -0,0 +1,133 @@
---
title: Migrate from Actions V1 to V2
sidebar_label: Migrate from V1 to V2
---
This guide helps existing users understand the shift from Actions V1 (Embedded) to V2 (Webhook), and provides guidance for new users on how to work with Actions V2.
<Callout type="caution">
Deprecation Notice
Actions V1 APIs are planned to be sunsetted in ZITADEL V5. While a definitive V5 release date is not yet available, this is the final timeline for V1 API removal. Actions V1 will receive no new features, and all new implementations must use V2.
</Callout>
## Why Actions Changed from V1 to V2
ZITADEL Actions evolved from V1 to V2 to address fundamental architectural limitations. While Actions V1 executed JavaScript code directly within ZITADEL's runtime, Actions V2 uses external HTTP endpoints.
This shift enables:
- **Better Scalability:** Distributed, serverless execution
- **Greater Flexibility:** Support for any technology stack (not just JavaScript)
- **Improved Resilience:** Isolating custom logic from core ZITADEL services
- **Cost Optimization:** Pay-per-execution models with serverless providers
- **Enhanced Monitoring:** Better debugging and observability of custom extensions
The transition represents a move from "embedded extensions" to "true webhooks," aligning ZITADEL with industry patterns (GitHub Actions, Zapier, etc.).
## Architecture Deep Dive: V1 vs V2
### Actions V1: The Embedded Model
Custom JavaScript code executed directly inside ZITADEL's process. For a complete list of V1 flow types, see [Actions V1 Flow Types](/guides/manage/console/actions-overview#available-flow-types).
- **Context:** Embedded goja JavaScript engine
- **Risks:** Shared memory space; errors could affect core functionality
- **Constraints:** Bound to ZITADEL process resources; limited timeout configuration
- **V1 Triggers:** Pre-event hooks (fire before operation) and Post-event hooks (fire after success)
### Actions V2: The Webhook Model
Custom logic lives in external HTTP endpoints called via webhooks.
- **Context:** Runs on your infrastructure (Cloudflare Workers, AWS Lambda, etc.)
- **Isolation:** Completely isolated memory; failures do not crash ZITADEL
- **Scaling:** Endpoints scale independently of ZITADEL
**V2 Execution Types:**
| Type | Description |
|------|-------------|
| **Request** | Triggers when a specific API request occurs |
| **Response** | Triggers when a specific API response occurs |
| **Function** | Triggers when specific functionality is used (useful for adding custom claims to tokens or executing external code during OIDC/SAML flows) |
| **Event** | Triggers reactively when ZITADEL events occur |
### Key Architectural Differences
| Aspect | Actions V1 | Actions V2 |
|--------|-----------|-----------|
| Execution Environment | Embedded in ZITADEL (sandboxed JavaScript) | External webhooks (Cloudflare Workers, AWS Lambda, etc.) |
| Code Location | Inline in ZITADEL Console | Hosted on your infrastructure |
| Language | JavaScript (limited runtime) | Any language |
| API Access | `ctx` and `api` objects provided by ZITADEL | ZITADEL REST/gRPC APIs via HTTP calls |
| Triggers | Flow + Trigger (e.g., "Pre Access Token Creation") | Specific API methods or webhook events |
| Dependencies | Limited (`zitadel/http`, `zitadel/log`) | Any framework |
| Security | Managed by ZITADEL | HMAC signature verification required (to validate incoming webhook requests from ZITADEL) |
| Scalability | Limited by ZITADEL instance | Independent scaling |
### Business Benefits
- **Technology Flexibility:** Use your preferred language instead of being locked into JavaScript
- **Operational Resilience:** Isolates failures to prevent cascading outages
- **Cost Optimization:** Leverage serverless "scale to zero" pricing for custom logic
- **Observability:** Integrate with tools such as Datadog and Sentry
## When to Use Each V2 Execution Type
We provide a collection of [ready-to-deploy Cloudflare Worker examples](https://github.com/zitadel/actions/tree/main/actions-v2-cloudflare-workers) that integrate with ZITADEL Actions V2. Each script demonstrates how to handle ZITADEL events, signatures, and responses directly from a serverless Cloudflare Worker environment. While the examples are designed for Cloudflare Workers, the code can be adapted to deploy on the platform of your choice (AWS Lambda, Azure Functions, etc.).
| If You Need To... | Use This V2 Type | When It Fires | Example |
|-------------------|------------------|---------------|---------|
| Add custom token claims | Function (`preaccesstoken`) | Before access token generated | [custom-claims](https://github.com/zitadel/actions/blob/main/actions-v2-cloudflare-workers/CUSTOM-CLAIMS.md) |
| Map roles to permissions | Function (`preaccesstoken`) | Before access token generated | [authorization](https://github.com/zitadel/actions/blob/main/actions-v2-cloudflare-workers/AUTHORIZATION.md) |
| Add "groups" claim from roles | Function (`preaccesstoken`/`preuserinfo`) | Before token/userinfo generated | [groups-claim](https://github.com/zitadel/actions/blob/main/actions-v2-cloudflare-workers/GROUPS-CLAIM.md) |
| Block login | Function (`preuserinfo`) + `interruptOnError: true` | Before userinfo generated | [block-login](https://github.com/zitadel/actions/blob/main/actions-v2-cloudflare-workers/BLOCK-LOGIN.md) |
| Track last login time | Function (`preuserinfo`) | Before userinfo generated | [set-user-metadata](https://github.com/zitadel/actions/blob/main/actions-v2-cloudflare-workers/SET-USER-METADATA.md) |
| Add custom SAML attributes | Function (`presamlresponse`) | Before SAML response sent | [saml-attributes](https://github.com/zitadel/actions/blob/main/actions-v2-cloudflare-workers/SAML-ATTRIBUTES.md) |
| Migrate users on first login | Request + Response (API methods) | Before/after user lookup & session | [jit-users-migration](https://github.com/zitadel/actions/blob/main/actions-v2-cloudflare-workers/JIT-USERS-MIGRATION.md) |
| Map IDP attributes | Response (`/RetrieveIdentityProviderIntent`) | After IDP authentication | [idp-mapping](https://github.com/zitadel/actions/blob/main/actions-v2-cloudflare-workers/IDP-MAPPING.md) |
| Auto-assign roles to new users | Event (`user.human.added`) | When user created | [set-role](https://github.com/zitadel/actions/blob/main/actions-v2-cloudflare-workers/SET-ROLE.md) |
| Forward events to monitoring | Event (any event) | When event occurs | [datadog-forwarder](https://github.com/zitadel/actions/blob/main/actions-v2-cloudflare-workers/DATADOG-FORWARDER.md) |
## Detailed Flow Explanations
### Flow 1: Internal Authentication
Maps user registration and login triggers to Request (Pre-operation) and Response (Post-operation) execution types.
**Example Scripts:**
- Block/validate requests: [block-login-flow.js](https://github.com/zitadel/actions/blob/main/actions-v2-cloudflare-workers/BLOCK-LOGIN.md)
- Add metadata post-creation: [metadata.js](https://github.com/zitadel/actions/blob/main/actions-v2-cloudflare-workers/SET-USER-METADATA.md)
### Flow 2: External Authentication
Handles IDP logins and JIT (Just-in-Time) provisioning via Response (Intent retrieval) and standard User Creation hooks.
**Example Scripts:**
- Map IDP attributes: [idp-mapping.js](https://github.com/zitadel/actions/blob/main/actions-v2-cloudflare-workers/IDP-MAPPING.md)
- JIT user migration: [jit-user-migration.js](https://github.com/zitadel/actions/blob/main/actions-v2-cloudflare-workers/JIT-USERS-MIGRATION.md)
### Flow 3: Complement Token
Moves from implicit hooks to specific V2 functions, primarily Function (`preaccesstoken`).
**Example Scripts:**
- Add custom claims: [custom-claims.js](https://github.com/zitadel/actions/blob/main/actions-v2-cloudflare-workers/CUSTOM-CLAIMS.md)
- Add permissions claim: [authorization.js](https://github.com/zitadel/actions/blob/main/actions-v2-cloudflare-workers/AUTHORIZATION.md)
### Flow 4: Customize SAML Response
Moves to the Function (`presamlresponse`).
**Example Scripts:**
- Add SAML attributes: [saml-attributes.js](https://github.com/zitadel/actions/blob/main/actions-v2-cloudflare-workers/SAML-ATTRIBUTES.md)
## Additional Resources
- [ZITADEL Actions V2 Documentation](/concepts/features/actions_v2)
- [Using Actions Guide](./usage)
- [Actions V2 Examples Repository](https://github.com/zitadel/actions/tree/main/actions-v2-cloudflare-workers)
@@ -0,0 +1,195 @@
---
title: Test Actions Event
---
This guide shows you how to leverage the ZITADEL actions feature to react to events in your ZITADEL instance.
You can use the actions feature to create a target that will be called when a specific event occurs.
This is useful for integrating with other systems or for triggering workflows based on events in ZITADEL.
## Prerequisites
Before you start, make sure you have everything set up correctly.
- You need to be at least a ZITADEL [_IAM_OWNER_](/guides/manage/console/managers)
- Your ZITADEL instance needs to have the actions feature enabled.
<Callout>
Note that this guide assumes that ZITADEL is running on the same machine as the target and can be reached via `localhost`.
In case you are using a different setup, you need to adjust the target URL accordingly and will need to make sure that the target is reachable from ZITADEL.
</Callout>
## Start example target
To test the actions feature, you need to create a target that will be called when an event occurs.
You will need to implement a listener that can receive HTTP requests and process the events.
For this example, we will use a simple Go HTTP server that will print the received events to standard output.
<Callout>
The signature of the received request can be checked, [please refer to the example for more information on how to](/guides/integrate/actions/testing-request-signature).
</Callout>
```go
package main
import (
"fmt"
"io"
"net/http"
)
// webhook HandleFunc to read the request body and then print out the contents
func webhook(w http.ResponseWriter, req *http.Request) {
// read the body content
sentBody, err := io.ReadAll(req.Body)
if err != nil {
// if there was an error while reading the body return an error
http.Error(w, "error", http.StatusInternalServerError)
return
}
defer req.Body.Close()
// print out the read content
fmt.Println(string(sentBody))
}
func main() {
// handle the HTTP call under "/webhook"
http.HandleFunc("/webhook", webhook)
// start an HTTP server with the before defined function to handle the endpoint under "http://localhost:8090"
http.ListenAndServe(":8090", nil)
}
```
<Callout title="">
The example above runs only on your local machine (`localhost`).
To test it with Zitadel, you must make your listener reachable from the internet.
You can do this by using **Webhook.site** (see [Creating a Listener with Webhook.site](./webhook-site-setup)).
</Callout>
## Create target
As you see in the example above the target is created with HTTP and port '8090' and if we want to use it as webhook, the
target can be created as follows:
See [Create a target](/reference/api/action/zitadel.action.v2.ActionService.CreateTarget) for more detailed information.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/actions/targets' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"name": "local webhook",
"restWebhook": {
"interruptOnError": true
},
"endpoint": "http://localhost:8090/webhook",
"timeout": "10s"
}'
```
Save the returned ID to set in the execution.
## Set execution
To configure ZITADEL to call the target when an event occurs, you need to set an execution and define the event
condition.
See [Set an execution](/reference/api/action/zitadel.action.v2.ActionService.SetExecution) for more detailed information.
```shell
curl -L -X PUT 'https://${CUSTOM_DOMAIN}/v2/actions/executions' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"condition": {
"event": {
"event": "user.human.added"
}
},
"targets": [
"<TargetID returned>"
]
}'
```
## Example call
Now that you have set up the target and execution, you can test it by creating a user through the Management Console UI or
by calling the ZITADEL API to create a human user.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/users/new' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"organizationId": "336392597046099971",
"human":
{
"profile":
{
"givenName": "Minnie",
"familyName": "Mouse",
"nickName": "Mini",
"displayName": "Minnie Mouse",
"preferredLanguage": "en",
"gender": "GENDER_FEMALE"
},
"email":
{
"email": "[email protected]"
}
}
}'
```
Your server should now print out something like the following. Check out
the [Sent information Event](./usage#sent-information-event) payload description.
```json
{
"aggregateID": "336494809936035843",
"aggregateType": "user",
"resourceOwner": "336392597046099971",
"instanceID": "336392597046034435",
"version": "v2",
"sequence": 1,
"event_type": "user.human.added",
"created_at": "2025-09-05T08:55:36.156333Z",
"userID": "336392597046755331",
"event_payload":
{
"email": "[email protected]",
"gender": 1,
"lastName": "Mouse",
"nickName": "Mini",
"userName": "[email protected]",
"firstName": "Minnie",
"displayName": "Minnie Mouse",
"preferredLanguage": "en"
}
}
```
The event_payload is base64 encoded and has the following content:
```json
{
"email": "[email protected]",
"gender": 1,
"lastName": "Mouse",
"nickName": "Mini",
"userName": "[email protected]",
"firstName": "Minnie",
"displayName": "Minnie Mouse",
"preferredLanguage": "en"
}
```
## Conclusion
You have successfully set up a target and execution to react to events in your ZITADEL instance.
This feature can now be used to integrate with your existing systems to create custom workflows or automate tasks based on events in ZITADEL.
Find more information about the actions feature in the [API documentation](/concepts/features/actions_v2).
@@ -0,0 +1,159 @@
---
title: Test Actions Function Manipulation
---
This guide shows you how to leverage the ZITADEL actions feature to enhance different functions in your ZITADEL instance.
You can use the actions feature to create a target that will be called when a specific functionality is used.
This is useful for integrating with other systems which need specific claims in tokens or for executing external code during OIDC or SAML flows.
## Prerequisites
Before you start, make sure you have everything set up correctly.
- You need to be at least a ZITADEL [_IAM_OWNER_](/guides/manage/console/managers)
- Your ZITADEL instance needs to have the actions feature enabled.
<Callout>
Note that this guide assumes that ZITADEL is running on the same machine as the target and can be reached via `localhost`.
In case you are using a different setup, you need to adjust the target URL accordingly and will need to make sure that the target is reachable from ZITADEL.
</Callout>
## Available functions
The available conditions can be found under [all available Functions](/reference/api/action/zitadel.action.v2.ActionService.ListExecutionFunctions).
## Start example target
To test the actions feature, you need to create a target that will be called when a function is used.
You will need to implement a listener that can receive HTTP requests and process the data.
For this example, we will use a simple Go HTTP server that will send back static data.
<Callout>
The signature of the received request can be checked, [please refer to the example for more information on how to](/guides/integrate/actions/testing-request-signature).
</Callout>
```go
package main
import (
"encoding/json"
"net/http"
)
type Response struct {
SetUserMetadata []*Metadata `json:"set_user_metadata,omitempty"`
AppendClaims []*AppendClaim `json:"append_claims,omitempty"`
AppendLogClaims []string `json:"append_log_claims,omitempty"`
}
type Metadata struct {
Key string `json:"key"`
Value []byte `json:"value"`
}
type AppendClaim struct {
Key string `json:"key"`
Value any `json:"value"`
}
// call HandleFunc to respond with static data
func call(w http.ResponseWriter, req *http.Request) {
// create the response with the correct structure
resp := &Response{
SetUserMetadata: []*Metadata{
{Key: "key", Value: []byte("value")},
},
AppendClaims: []*AppendClaim{
{Key: "claim", Value: "value"},
},
AppendLogClaims: []string{"log1", "log2", "log3"},
}
data, err := json.Marshal(resp)
if err != nil {
// if there was an error while marshalling the json
http.Error(w, "error", http.StatusInternalServerError)
return
}
w.Write(data)
}
func main() {
// handle the HTTP call under "/call"
http.HandleFunc("/call", call)
// start an HTTP server with the before defined function to handle the endpoint under "http://localhost:8090"
http.ListenAndServe(":8090", nil)
}
```
<Callout title="">
The example above runs only on your local machine (`localhost`).
To test it with Zitadel, you must make your listener reachable from the internet.
You can do this by using **Webhook.site** (see [Creating a Listener with Webhook.site](./webhook-site-setup)).
</Callout>
## Create target
As you see in the example above the target is created with HTTP and port '8090' and if we want to use it as call, the target can be created as follows:
See [Create a target](/reference/api/action/zitadel.action.v2.ActionService.CreateTarget) for more detailed information.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/actions/targets' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"name": "local call",
"restCall": {
"interruptOnError": true
},
"endpoint": "http://localhost:8090/call",
"timeout": "10s"
}'
```
Save the returned ID to set in the execution.
## Set execution
To configure ZITADEL to call the target when a function is executed, you need to set an execution and define the function
condition.
See [Set an execution](/reference/api/action/zitadel.action.v2.ActionService.SetExecution) for more detailed information.
```shell
curl -L -X PUT 'https://${CUSTOM_DOMAIN}/v2/actions/executions' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"condition": {
"function": {
"name": "preuserinfo"
}
},
"targets": [
"<TargetID returned>"
]
}'
```
## Example call
Now that you have set up the target and execution, you can test it by logging into Management Console UI or
by using any OIDC flow.
As a result 3 things happen:
- the user get the metadata with the key "key" and value "value" added
- the token has a claim "urn:zitadel:iam:claim" added with value "value"
- the token has the log claim "urn:zitadel:iam:action:preuserinfo:log" added with values "log1", "log2" and "log3".
For any further information related to [the OIDC Flow, refer to our documentation.](/guides/integrate/login/oidc/login-users)
## Conclusion
You have successfully set up a target and execution to react to functions in your ZITADEL instance.
This feature can now be used to integrate with your existing systems to create custom workflows or automate tasks based on functionality in ZITADEL.
Find more information about the actions feature in the [API documentation](/concepts/features/actions_v2).
@@ -0,0 +1,175 @@
---
title: Test Actions Function
---
This guide shows you how to leverage the ZITADEL actions feature to enhance different functions in your ZITADEL instance.
You can use the actions feature to create a target that will be called when a specific functionality is used.
This is useful for integrating with other systems which need specific claims in tokens or for executing external code during OIDC or SAML flows.
## Prerequisites
Before you start, make sure you have everything set up correctly.
- You need to be at least a ZITADEL [_IAM_OWNER_](/guides/manage/console/managers)
- Your ZITADEL instance needs to have the actions feature enabled.
<Callout>
Note that this guide assumes that ZITADEL is running on the same machine as the target and can be reached via `localhost`.
In case you are using a different setup, you need to adjust the target URL accordingly and will need to make sure that the target is reachable from ZITADEL.
</Callout>
## Available functions
The available conditions can be found under [all available Functions](/reference/api/action/zitadel.action.v2.ActionService.ListExecutionFunctions).
## Start example target
To test the actions feature, you need to create a target that will be called when a function is used.
You will need to implement a listener that can receive HTTP requests and process the data.
For this example, we will use a simple Go HTTP server that will print the received data to standard output.
<Callout>
The signature of the received request can be checked, [please refer to the example for more information on how to](/guides/integrate/actions/testing-request-signature).
</Callout>
```go
package main
import (
"fmt"
"io"
"net/http"
)
// webhook HandleFunc to read the request body and then print out the contents
func webhook(w http.ResponseWriter, req *http.Request) {
// read the body content
sentBody, err := io.ReadAll(req.Body)
if err != nil {
// if there was an error while reading the body return an error
http.Error(w, "error", http.StatusInternalServerError)
return
}
defer req.Body.Close()
// print out the read content
fmt.Println(string(sentBody))
}
func main() {
// handle the HTTP call under "/webhook"
http.HandleFunc("/webhook", webhook)
// start an HTTP server with the before defined function to handle the endpoint under "http://localhost:8090"
http.ListenAndServe(":8090", nil)
}
```
<Callout title="">
The example above runs only on your local machine (`localhost`).
To test it with Zitadel, you must make your listener reachable from the internet.
You can do this by using **Webhook.site** (see [Creating a Listener with Webhook.site](./webhook-site-setup)).
</Callout>
## Create target
As you see in the example above the target is created with HTTP and port '8090' and if we want to use it as webhook, the target can be created as follows:
See [Create a target](/reference/api/action/zitadel.action.v2.ActionService.CreateTarget) for more detailed information.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/actions/targets' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"name": "local call",
"restWebhook": {
"interruptOnError": true
},
"endpoint": "http://localhost:8090/webhook",
"timeout": "10s"
}'
```
Save the returned ID to set in the execution.
## Set execution
To configure ZITADEL to call the target when a function is executed, you need to set an execution and define the function
condition.
See [Set an execution](/reference/api/action/zitadel.action.v2.ActionService.SetExecution) for more detailed information.
```shell
curl -L -X PUT 'https://${CUSTOM_DOMAIN}/v2/actions/executions' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"condition": {
"function": {
"name": "preuserinfo"
}
},
"targets": [
"<TargetID returned>"
]
}'
```
## Example call
Now that you have set up the target and execution, you can test it by logging into Management Console UI or
by using any OIDC flow.
Your server should now print out something like the following. Check out the [Sent information Function](./usage#sent-information-function) payload description.
```json
{
"function" : "function/preuserinfo",
"userinfo" : {
"sub" : "312909075212468632"
},
"user" : {
"id" : "312909075212468632",
"creation_date" : "2025-03-26T15:52:23.917636Z",
"change_date" : "2025-03-26T15:52:23.917636Z",
"resource_owner" : "312909075211944344",
"sequence" : 2,
"state" : 1,
"username" : "[email protected]",
"preferred_login_name" : "[email protected]",
"human" : {
"first_name" : "Example firstname",
"last_name" : "Example lastname",
"display_name" : "Example displayname",
"preferred_language" : "en",
"email" : "[email protected]",
"is_email_verified" : true,
"password_changed" : "0001-01-01T00:00:00Z",
"mfa_init_skipped" : "0001-01-01T00:00:00Z"
}
},
"user_metadata" : [ {
"creation_date" : "2025-03-27T09:10:25.879677Z",
"change_date" : "2025-03-27T09:10:25.879677Z",
"resource_owner" : "312909075211944344",
"sequence" : 18,
"key" : "key",
"value" : "dmFsdWU="
} ],
"org" : {
"id" : "312909075211944344",
"name" : "ZITADEL",
"primary_domain" : "example.com"
}
}
```
For any further information related to [the OIDC Flow, refer to our documentation.](/guides/integrate/login/oidc/login-users)
## Conclusion
You have successfully set up a target and execution to react to functions in your ZITADEL instance.
This feature can now be used to customize the functionality in ZITADEL, in particular the content of the OIDC tokens and SAML responses.
Find more information about the actions feature in the [API documentation](/concepts/features/actions_v2).
@@ -0,0 +1,242 @@
---
title: Test Actions Request Manipulation
---
This guide shows you how to leverage the ZITADEL actions feature to manipulate API requests in your ZITADEL instance.
You can use the actions feature to create a target that will be called when a specific API request occurs.
This is useful for adding information to managed resources in ZITADEL.
## Prerequisites
Before you start, make sure you have everything set up correctly.
- You need to be at least a ZITADEL [_IAM_OWNER_](/guides/manage/console/managers)
- Your ZITADEL instance needs to have the actions feature enabled.
<Callout>
Note that this guide assumes that ZITADEL is running on the same machine as the target and can be reached via `localhost`.
In case you are using a different setup, you need to adjust the target URL accordingly and will need to make sure that the target is reachable from ZITADEL.
</Callout>
<Callout>
To marshal and unmarshal the request please use a package like [protojson](https://pkg.go.dev/google.golang.org/protobuf/encoding/protojson),
as the request is a protocol buffer message, to avoid potential problems with the attribute names.
</Callout>
## Start example target
To test the actions feature, you need to create a target that will be called when an API endpoint is called.
You will need to implement a listener that can receive HTTP requests, process the request and returns the manipulated request.
For this example, we will use a simple Go HTTP server that will return the request with added metadata.
<Callout>
The signature of the received request can be checked, [please refer to the example for more information on how to](/guides/integrate/actions/testing-request-signature).
</Callout>
```go
package main
import (
"encoding/json"
"io"
"net/http"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/user/v2"
"google.golang.org/protobuf/encoding/protojson"
)
type contextRequest struct {
Request *createUserRequestWrapper `json:"request"`
}
// createUserRequestWrapper necessary to marshal and unmarshal the JSON into the proto message correctly
type createUserRequestWrapper struct {
user.CreateUserRequest
}
func (r *createUserRequestWrapper) MarshalJSON() ([]byte, error) {
data, err := protojson.Marshal(r)
if err != nil {
return nil, err
}
return data, nil
}
func (r *createUserRequestWrapper) UnmarshalJSON(data []byte) error {
return protojson.Unmarshal(data, r)
}
// call HandleFunc to read the request body, manipulate the content and return the manipulated request
func call(w http.ResponseWriter, req *http.Request) {
// read the body content
sentBody, err := io.ReadAll(req.Body)
if err != nil {
// if there was an error while reading the body return an error
http.Error(w, "error", http.StatusInternalServerError)
return
}
defer req.Body.Close()
// read the request into the expected structure
request := new(contextRequest)
if err := json.Unmarshal(sentBody, request); err != nil {
http.Error(w, "error", http.StatusInternalServerError)
}
// build the response from the received request
response := request.Request
// manipulate the request to send back as response
metadata := response.GetHuman().GetMetadata()
if metadata == nil {
metadata = make([]*user.Metadata, 0)
}
metadata = append(metadata, &user.Metadata{Key: "organization", Value: []byte("dunder mifflin")})
response.GetHuman().Metadata = metadata
// marshal the request into json
data, err := json.Marshal(response)
if err != nil {
// if there was an error while marshalling the json
http.Error(w, "error", http.StatusInternalServerError)
return
}
// return the manipulated request
w.Write(data)
}
func main() {
// handle the HTTP call under "/call"
http.HandleFunc("/call", call)
// start an HTTP server with the before defined function to handle the endpoint under "http://localhost:8090"
http.ListenAndServe(":8090", nil)
}
```
<Callout title="">
The example above runs only on your local machine (`localhost`).
To test it with Zitadel, you must make your listener reachable from the internet.
You can do this by using **Webhook.site** (see [Creating a Listener with Webhook.site](./webhook-site-setup)).
</Callout>
## Create target
As you see in the example above the target is created with HTTP and port '8090' and if we want to use it as call, the target can be created as follows:
See [Create a target](/reference/api/action/zitadel.action.v2.ActionService.CreateTarget) for more detailed information.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/actions/targets' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"name": "local call",
"restCall": {
"interruptOnError": true
},
"endpoint": "http://localhost:8090/call",
"timeout": "10s"
}'
```
Save the returned ID to set in the execution.
## Set execution
To call the target just created before, with the intention to manipulate the request used for user creation by the user V2 API, we define an execution with a method condition.
See [Set an execution](/reference/api/action/zitadel.action.v2.ActionService.SetExecution) for more detailed information.
```shell
curl -L -X PUT 'https://${CUSTOM_DOMAIN}/v2/actions/executions' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"condition": {
"request": {
"method": "/zitadel.user.v2.UserService/CreateUser"
}
},
"targets": [
"<TargetID returned>"
]
}'
```
## Example call
Now that you have set up the target and execution, you can test it by creating a user through the Management Console UI or
by calling the ZITADEL API to create a human user.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/users/new' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"organizationId": "344648897353810062",
"human":
{
"profile":
{
"givenName": "Minnie",
"familyName": "Mouse",
"nickName": "Mini",
"displayName": "Minnie Mouse",
"preferredLanguage": "en",
"gender": "GENDER_FEMALE"
},
"email":
{
"email": "[email protected]"
}
}
}'
```
Your server should now manipulate the request to something like the following. Check out
the [Sent information Request](./usage#sent-information-request) payload description.
```shell
curl -L -X PUT 'https://${CUSTOM_DOMAIN}/v2/users/new' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"organizationId": "344648897353810062",
"human":
{
"profile":
{
"givenName": "Minnie",
"familyName": "Mouse",
"nickName": "Mini",
"displayName": "Minnie Mouse",
"preferredLanguage": "en",
"gender": "GENDER_FEMALE"
},
"email":
{
"email": "[email protected]"
},
"metadata":
[
{
"key": "organization",
"value": "ZHVuZGVyIG1pZmZsaW4="
}
]
}
}'
```
## Conclusion
You have successfully set up a target and execution to manipulate API requests in your ZITADEL instance.
This feature can now be used to add or manipulate information to managed resources in ZITADEL.
Find more information about the actions feature in the [API documentation](/concepts/features/actions_v2).
@@ -0,0 +1,240 @@
---
title: Verify Payload Integrity
---
import JSONCode from "./go/json.go";
import JWTCode from "./go/jwt.go";
import JWECode from "./go/jwe.go";
import CodeBlock from "@/components/docusaurus/code-block";
This guide shows you how to verify the integrity of the received data on your target.
There are three options available, which will be demonstrated in the following sections.
The examples are based on the request action, but the same principles apply to other action types as well.
## Prerequisites
Before you start, make sure you have everything set up correctly.
- You need to be at least a ZITADEL [_IAM_OWNER_](/guides/manage/console/managers)
<Callout type="info">
Note that this guide assumes that ZITADEL is running on the same machine as the target and can be reached via `localhost`.
In case you are using a different setup, you need to adjust the target URL accordingly and will need to make sure that the target is reachable from ZITADEL.
</Callout>
## Payload and Validation Types
ZITADEL supports the following three types of payload in actions:
- **JSON**: The payload is sent as JSON in the request body. Additionally, a signature header is sent to validate the payload.
This is the simplest form of payload and validation and doesn't require any additional calls to validate the payload. It's also the default type,
which will be used if no type is specified.
- **JWT**: The payload is sent as a JSON Web Token (JWT) in the request body. The JWT is signed with the signing key of the instance.
This allows you to validate the payload by verifying the signature of the JWT using the signing key managed though the webkeys endpoint of the ZITADEL instance,
which allows for easier key rotation and management. Additionally, it's the base for the JWE type, in case you need to encrypt the payload for certain use cases.
- **JWE**: The payload is sent as encrypted JSON Web Token (JWE) in the request body. The JWT is additionally encrypted with the public key provided by you.
This allows you to validate and decrypt the payload by verifying the signature of the JWT using the signing key managed though the webkeys endpoint of the ZITADEL instance,
and decrypting the payload using your private key. This is the most secure form of payload and validation, but requires additional setup to provide the public key to ZITADEL.
This type is recommended if the payload contains sensitive information that should not be exposed to any intermediaries.
## Create target
We'll start the endpoint on port '8090' and if we want to use it as webhook, the target can be created as follows:
See [Create a target](/apis/resources/action_service_v2/action-service-create-target) for more detailed information.
Specify the `payloadType` according to the implementation you want to test: `PAYLOAD_TYPE_JSON`, `PAYLOAD_TYPE_JWT`, or `PAYLOAD_TYPE_JWE`.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/actions/targets' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"name": "local webhook",
"restWebhook": {
"interruptOnError": true
},
"endpoint": "http://localhost:8090/webhook",
"timeout": "10s",
"payloadType": "PAYLOAD_TYPE_JSON"
}'
```
Example response after creating the target:
```json
{
"id": "344649040681500814",
"creationDate": "2025-10-31T15:00:36.432595dZ",
"signingKey": "somekey"
}
```
Save the returned ID to set in the execution. If you're intending to use the `PAYLOAD_TYPE_JSON`, additionally store the `signingKey` and use it in the example above.
### JWE Specific Setup
If you are using the `PAYLOAD_TYPE_JWE`, you need to provide a public key to ZITADEL so that it can encrypt the payload.
Create a public/private key pair. You can use the following command to generate an RSA key pair:
```shell
openssl genpkey -algorithm RSA -outform PEM -out private_key.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in private_key.pem -out public_key.pem
```
Then upload the public key to ZITADEL using the following command. Replace `<TargetID>` with the ID of the target you created earlier.
Use the base64 encoded content of the `public_key.pem` file as the value for `publicKey`.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/actions/targets/<TargetID>/publickeys' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"publicKey": "<base64 encoded contents of public_key.pem>"
}'
```
Be sure to also activate the public key for the target using the returned `<KeyID>` from the previous request:
```shell
curl -L -X PUT 'https://${CUSTOM_DOMAIN}/v2/actions/targets/<TargetID>/publickeys/<KeyID>/activate' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>'
```
## Set execution
To configure ZITADEL to call the target when an API endpoint is called, you need to set an execution and define the request
condition.
See [Set an execution](/apis/resources/action_service_v2/action-service-set-execution) for more detailed information.
```shell
curl -L -X PUT 'https://${CUSTOM_DOMAIN}/v2/actions/executions' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"condition": {
"request": {
"method": "/zitadel.user.v2.UserService/CreateUser"
}
},
"targets": [
"<TargetID returned>"
]
}'
```
## Start example target
To test the actions feature, you need to create a target that will be called when an API endpoint is called.
You will need to implement a listener that can receive HTTP requests.
For this example, we will use a simple Go HTTP server that will print the received request to standard output.
As mentioned before, this validation can and should be applied to any target implementation.
<Tabs>
<Tab value="JSON">
<CodeBlock language="go">{JSONCode}</CodeBlock>
</Tab>
<Tab value="JWT">
<CodeBlock language="go">{JWTCode}</CodeBlock>
</Tab>
<Tab value="JWE">
<CodeBlock language="go">{JWECode}</CodeBlock>
</Tab>
</Tabs>
<Callout type="info">
The example above runs only on your local machine (`localhost`).
To test it with Zitadel, you must make your listener reachable from the internet.
You can do this by using **Webhook.site** (see [Creating a Listener with Webhook.site](./webhook-site-setup)).
</Callout>
## Example call
Now that you have set up the target and execution, you can test it by creating a user through the Management Console UI or
by calling the ZITADEL API to create a human user.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/users/new' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"organizationId": "344648897353810062",
"human":
{
"profile":
{
"givenName": "Minnie",
"familyName": "Mouse",
"nickName": "Mini",
"displayName": "Minnie Mouse",
"preferredLanguage": "en",
"gender": "GENDER_FEMALE"
},
"email":
{
"email": "[email protected]"
}
}
}'
```
Your server should now print out something like the following. Check out
the [Sent information Request](./usage#sent-information-request) payload description.
```json
{
"fullMethod": "/zitadel.user.v2.UserService/CreateUser",
"instanceID": "344648897353744526",
"orgID": "344648897353810062",
"projectID": "344648897353875598",
"userID": "344648897354465422",
"request":
{
"organizationId": "344648897353810062",
"human":
{
"profile":
{
"givenName": "Minnie",
"familyName": "Mouse",
"nickName": "Mini",
"displayName": "Minnie Mouse",
"preferredLanguage": "en",
"gender": "GENDER_FEMALE"
},
"email":
{
"email": "[email protected]"
}
}
},
"headers":
{
"Content-Type":
[
"application/grpc"
],
"Host":
[
"localhost:8080"
],
"X-Forwarded-For":
[
"::1"
],
"X-Forwarded-Host":
[
"localhost:8080"
]
}
}
```
## Conclusion
You have successfully set up a target and verified the payload integrity for request actions using your preferred payload type.
You can now extend this setup to other action types and integrate it into your workflows as needed.
Selecting the appropriate payload type ensures that your data is transmitted securely and can be validated effectively on the receiving end.
Find more information about the actions feature in the [API documentation](/concepts/features/actions_v2).
@@ -0,0 +1,209 @@
---
title: Test Actions Request
---
This guide shows you how to leverage the ZITADEL actions feature to react to API requests in your ZITADEL instance.
You can use the actions feature to create a target that will be called when a specific API request occurs.
This is useful for information provisioning in between systems or for triggering workflows based on API requests in ZITADEL.
## Prerequisites
Before you start, make sure you have everything set up correctly.
- You need to be at least a ZITADEL [_IAM_OWNER_](/guides/manage/console/managers)
- Your ZITADEL instance needs to have the actions feature enabled.
<Callout>
Note that this guide assumes that ZITADEL is running on the same machine as the target and can be reached via `localhost`.
In case you are using a different setup, you need to adjust the target URL accordingly and will need to make sure that the target is reachable from ZITADEL.
</Callout>
<Callout>
To marshal and unmarshal the request please use a package like [protojson](https://pkg.go.dev/google.golang.org/protobuf/encoding/protojson),
as the request is a protocol buffer message, to avoid potential problems with the attribute names.
</Callout>
## Start example target
To test the actions feature, you need to create a target that will be called when an API endpoint is called.
You will need to implement a listener that can receive HTTP requests and process the request.
For this example, we will use a simple Go HTTP server that will print the received request to standard output.
<Callout>
The signature of the received request can be checked, [please refer to the example for more information on how to](/guides/integrate/actions/testing-request-signature).
</Callout>
```go
package main
import (
"fmt"
"io"
"net/http"
)
// webhook HandleFunc to read the request body and then print out the contents
func webhook(w http.ResponseWriter, req *http.Request) {
// read the body content
sentBody, err := io.ReadAll(req.Body)
if err != nil {
// if there was an error while reading the body return an error
http.Error(w, "error", http.StatusInternalServerError)
return
}
defer req.Body.Close()
// print out the read content
fmt.Println(string(sentBody))
}
func main() {
// handle the HTTP call under "/webhook"
http.HandleFunc("/webhook", webhook)
// start an HTTP server with the before defined function to handle the endpoint under "http://localhost:8090"
http.ListenAndServe(":8090", nil)
}
```
<Callout title="">
The example above runs only on your local machine (`localhost`).
To test it with Zitadel, you must make your listener reachable from the internet.
You can do this by using **Webhook.site** (see [Creating a Listener with Webhook.site](./webhook-site-setup)).
</Callout>
## Create target
As you see in the example above the target is created with HTTP and port '8090' and if we want to use it as webhook, the target can be created as follows:
See [Create a target](/reference/api/action/zitadel.action.v2.ActionService.CreateTarget) for more detailed information.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/actions/targets' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"name": "local webhook",
"restWebhook": {
"interruptOnError": true
},
"endpoint": "http://localhost:8090/webhook",
"timeout": "10s"
}'
```
Save the returned ID to set in the execution.
## Set execution
To configure ZITADEL to call the target when an API endpoint is called, you need to set an execution and define the request
condition.
See [Set an execution](/reference/api/action/zitadel.action.v2.ActionService.SetExecution) for more detailed information.
```shell
curl -L -X PUT 'https://${CUSTOM_DOMAIN}/v2/actions/executions' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"condition": {
"request": {
"method": "/zitadel.user.v2.UserService/CreateUser"
}
},
"targets": [
"<TargetID returned>"
]
}'
```
## Example call
Now that you have set up the target and execution, you can test it by creating a user through the Management Console UI or
by calling the ZITADEL API to create a human user.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/users/new' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"organizationId": "344648897353810062",
"human":
{
"profile":
{
"givenName": "Minnie",
"familyName": "Mouse",
"nickName": "Mini",
"displayName": "Minnie Mouse",
"preferredLanguage": "en",
"gender": "GENDER_FEMALE"
},
"email":
{
"email": "[email protected]"
}
}
}'
```
Your server should now print out something like the following. Check out
the [Sent information Request](./usage#sent-information-request) payload description.
The incoming request headers to the Execution are propagated via the request payload to the target.
```json
{
"fullMethod": "/zitadel.user.v2.UserService/CreateUser",
"instanceID": "344648897353744526",
"orgID": "344648897353810062",
"projectID": "344648897353875598",
"userID": "344648897354465422",
"request":
{
"organizationId": "344648897353810062",
"human":
{
"profile":
{
"givenName": "Minnie",
"familyName": "Mouse",
"nickName": "Mini",
"displayName": "Minnie Mouse",
"preferredLanguage": "en",
"gender": "GENDER_FEMALE"
},
"email":
{
"email": "[email protected]"
}
}
},
"headers":
{
"Content-Type":
[
"application/grpc"
],
"Host":
[
"localhost:8080"
],
"X-Forwarded-For":
[
"::1"
],
"X-Forwarded-Host":
[
"localhost:8080"
]
}
}
```
## Conclusion
You have successfully set up a target and execution to react to API requests in your ZITADEL instance.
This feature can now be used to provision information in between systems or for triggering workflows based on API requests in ZITADEL.
Find more information about the actions feature in the [API documentation](/concepts/features/actions_v2).
@@ -0,0 +1,298 @@
---
title: Test Actions Response Manipulation
---
This guide shows you how to leverage the ZITADEL actions feature to manipulate API responses in your ZITADEL instance.
You can use the actions feature to create a target that will be called when a specific API response occurs.
This is useful for triggering workflows based on API responses in ZITADEL. You can even use this to provide data necessary data to the new login UI as shown in this example.
## Prerequisites
Before you start, make sure you have everything set up correctly.
- You need to be at least a ZITADEL [_IAM_OWNER_](/guides/manage/console/managers)
- Your ZITADEL instance needs to have the actions feature enabled.
<Callout>
Note that this guide assumes that ZITADEL is running on the same machine as the target and can be reached via `localhost`.
In case you are using a different setup, you need to adjust the target URL accordingly and will need to make sure that the target is reachable from ZITADEL.
</Callout>
<Callout>
To marshal and unmarshal the request and response please use a package like [protojson](https://pkg.go.dev/google.golang.org/protobuf/encoding/protojson),
as the request and response are protocol buffer messages, to avoid potential problems with the attribute names.
</Callout>
## Start example target
To test the actions feature, you need to create a target that will be called when an API endpoint is called.
You will need to implement a listener that can receive HTTP requests, process the request and returns the manipulated request.
For this example, we will use a simple Go HTTP server that will return the request with added metadata.
<Callout>
The signature of the received request can be checked, [please refer to the example for more information on how to](/guides/integrate/actions/testing-request-signature).
</Callout>
```go
package main
import (
"encoding/json"
"io"
"net/http"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/user/v2"
"google.golang.org/protobuf/encoding/protojson"
)
type contextResponse struct {
Request *retrieveIdentityProviderIntentRequestWrapper `json:"request"`
Response *retrieveIdentityProviderIntentResponseWrapper `json:"response"`
}
// RetrieveIdentityProviderIntentRequestWrapper necessary to marshal and unmarshal the JSON into the proto message correctly
type retrieveIdentityProviderIntentRequestWrapper struct {
user.RetrieveIdentityProviderIntentRequest
}
func (r *retrieveIdentityProviderIntentRequestWrapper) MarshalJSON() ([]byte, error) {
data, err := protojson.Marshal(r)
if err != nil {
return nil, err
}
return data, nil
}
func (r *retrieveIdentityProviderIntentRequestWrapper) UnmarshalJSON(data []byte) error {
return protojson.Unmarshal(data, r)
}
// RetrieveIdentityProviderIntentResponseWrapper necessary to marshal and unmarshal the JSON into the proto message correctly
type retrieveIdentityProviderIntentResponseWrapper struct {
user.RetrieveIdentityProviderIntentResponse
}
func (r *retrieveIdentityProviderIntentResponseWrapper) MarshalJSON() ([]byte, error) {
data, err := protojson.Marshal(r)
if err != nil {
return nil, err
}
return data, nil
}
func (r *retrieveIdentityProviderIntentResponseWrapper) UnmarshalJSON(data []byte) error {
return protojson.Unmarshal(data, r)
}
// call HandleFunc to read the response body, manipulate the content and return the response
func call(w http.ResponseWriter, req *http.Request) {
// read the body content
sentBody, err := io.ReadAll(req.Body)
if err != nil {
// if there was an error while reading the body return an error
http.Error(w, "error", http.StatusInternalServerError)
return
}
defer req.Body.Close()
// read the response into the expected structure
request := new(contextResponse)
if err := json.Unmarshal(sentBody, request); err != nil {
http.Error(w, "error", http.StatusInternalServerError)
}
// build the response from the received response
resp := request.Response
// manipulate the received response to send back as response
if resp != nil && resp.AddHumanUser != nil {
// manipulate the response
resp.AddHumanUser.Metadata = append(resp.AddHumanUser.Metadata, &user.SetMetadataEntry{Key: "organization", Value: []byte("company")})
}
// marshal the response into json
data, err := json.Marshal(resp)
if err != nil {
// if there was an error while marshalling the json
http.Error(w, "error", http.StatusInternalServerError)
return
}
// return the manipulated response
w.Write(data)
}
func main() {
// handle the HTTP call under "/call"
http.HandleFunc("/call", call)
// start an HTTP server with the before defined function to handle the endpoint under "http://localhost:8090"
http.ListenAndServe(":8090", nil)
}
```
<Callout title="">
The example above runs only on your local machine (`localhost`).
To test it with Zitadel, you must make your listener reachable from the internet.
You can do this by using **Webhook.site** (see [Creating a Listener with Webhook.site](./webhook-site-setup)).
</Callout>
## Create target
As you see in the example above the target is created with HTTP and port '8090' and if we want to use it as call, the
target can be created as follows:
See [Create a target](/reference/api/action/zitadel.action.v2.ActionService.CreateTarget) for more detailed information.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/actions/targets' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"name": "local call",
"restCall": {
"interruptOnError": true
},
"endpoint": "http://localhost:8090/call",
"timeout": "10s"
}'
```
Save the returned ID to set in the execution.
## Set execution
To call the target just created before, with the intention to manipulate the retrieve of an intent by the user V2 API,
we define an execution with a response condition.
See [Set an execution](/reference/api/action/zitadel.action.v2.ActionService.SetExecution) for more detailed information.
```shell
curl -L -X PUT 'https://${CUSTOM_DOMAIN}/v2/actions/executions' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"condition": {
"response": {
"method": "/zitadel.user.v2.UserService/RetrieveIdentityProviderIntent"
}
},
"targets": [
"<TargetID returned>"
]
}'
```
## Example call
Now that you have set up the target and execution, you can test it by using a login-flow in the typescript login with an external IDP.
```json
{
"details": {
"sequence": "599",
"changeDate": "2023-06-15T06:44:26.039444Z",
"resourceOwner": "163840776835432705"
},
"idpInformation": {
"oauth": {
"accessToken": "ya29...",
"idToken": "ey..."
},
"idpId": "218528353504723201",
"userId": "218528353504723202",
"username": "test-user@localhost",
"rawInformation": {
"User": {
"email": "test-user@localhost",
"email_verified": true,
"family_name": "User",
"given_name": "Test",
"hd": "mouse.com",
"locale": "de",
"name": "Minnie Mouse",
"picture": "https://lh3.googleusercontent.com/a/AAcKTtf973Q7NH8KzKTMEZELPU9lx45WpQ9FRBuxFdPb=s96-c",
"sub": "111392805975715856637"
}
}
},
"addHumanUser": {
"idpLinks": [
{"idpId": "218528353504723201", "userId": "218528353504723202", "userName": "test-user@localhost"}
],
"username": "test-user@localhost",
"profile": {
"givenName": "Test",
"familyName": "User",
"displayName": "Test User",
"preferredLanguage": "de"
},
"email": {
"email": "[email protected]",
"isVerified": true
},
"metadata": []
}
}
```
Your server should now manipulate the response to something like the following. Check out
the [Sent information Response](./usage#sent-information-response) payload description.
```json
{
"details": {
"sequence": "599",
"changeDate": "2023-06-15T06:44:26.039444Z",
"resourceOwner": "163840776835432705"
},
"idpInformation": {
"oauth": {
"accessToken": "ya29...",
"idToken": "ey..."
},
"idpId": "218528353504723201",
"userId": "218528353504723202",
"username": "test-user@localhost",
"rawInformation": {
"User": {
"email": "test-user@localhost",
"email_verified": true,
"family_name": "User",
"given_name": "Test",
"hd": "mouse.com",
"locale": "de",
"name": "Minnie Mouse",
"picture": "https://lh3.googleusercontent.com/a/AAcKTtf973Q7NH8KzKTMEZELPU9lx45WpQ9FRBuxFdPb=s96-c",
"sub": "111392805975715856637"
}
}
},
"addHumanUser": {
"idpLinks": [
{"idpId": "218528353504723201", "userId": "218528353504723202", "userName": "test-user@localhost"}
],
"username": "test-user@localhost",
"profile": {
"givenName": "Test",
"familyName": "User",
"displayName": "Test User",
"preferredLanguage": "de"
},
"email": {
"email": "[email protected]",
"isVerified": true
},
"metadata": [
{"key": "organization", "value": "Y29tcGFueQ=="}
]
}
}
```
## Conclusion
You have successfully set up a target and execution to manipulate API responses in your ZITADEL instance.
This feature can now be used to add necessary information for clients including the new login UI.
Find more information about the actions feature in the [API documentation](/concepts/features/actions_v2).
@@ -0,0 +1,214 @@
---
title: Test Actions Response
---
This guide shows you how to leverage the ZITADEL actions feature to react to API responses in your ZITADEL instance.
You can use the actions feature to create a target that will be called when a specific API response occurs.
This is useful for information provisioning in between systems or for triggering workflows based on API responses in ZITADEL.
## Prerequisites
Before you start, make sure you have everything set up correctly.
- You need to be at least a ZITADEL [_IAM_OWNER_](/guides/manage/console/managers)
- Your ZITADEL instance needs to have the actions feature enabled.
<Callout>
Note that this guide assumes that ZITADEL is running on the same machine as the target and can be reached via `localhost`.
In case you are using a different setup, you need to adjust the target URL accordingly and will need to make sure that the target is reachable from ZITADEL.
</Callout>
<Callout>
To marshal and unmarshal the request and response please use a package like [protojson](https://pkg.go.dev/google.golang.org/protobuf/encoding/protojson),
as the request and response are protocol buffer messages, to avoid potential problems with the attribute names.
</Callout>
## Start example target
To test the actions feature, you need to create a target that will be called when an API endpoint is called.
You will need to implement a listener that can receive HTTP requests and process the request.
For this example, we will use a simple Go HTTP server that will print the received request to standard output.
<Callout>
The signature of the received request can be checked, [please refer to the example for more information on how to](/guides/integrate/actions/testing-request-signature).
</Callout>
```go
package main
import (
"fmt"
"io"
"net/http"
)
// webhook HandleFunc to read the request body and then print out the contents
func webhook(w http.ResponseWriter, req *http.Request) {
// read the body content
sentBody, err := io.ReadAll(req.Body)
if err != nil {
// if there was an error while reading the body return an error
http.Error(w, "error", http.StatusInternalServerError)
return
}
defer req.Body.Close()
// print out the read content
fmt.Println(string(sentBody))
}
func main() {
// handle the HTTP call under "/webhook"
http.HandleFunc("/webhook", webhook)
// start an HTTP server with the before defined function to handle the endpoint under "http://localhost:8090"
http.ListenAndServe(":8090", nil)
}
```
<Callout title="">
The example above runs only on your local machine (`localhost`).
To test it with Zitadel, you must make your listener reachable from the internet.
You can do this by using **Webhook.site** (see [Creating a Listener with Webhook.site](./webhook-site-setup)).
</Callout>
## Create target
As you see in the example above the target is created with HTTP and port '8090' and if we want to use it as webhook, the target can be created as follows:
See [Create a target](/reference/api/action/zitadel.action.v2.ActionService.CreateTarget) for more detailed information.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/actions/targets' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"name": "local webhook",
"restWebhook": {
"interruptOnError": true
},
"endpoint": "http://localhost:8090/webhook",
"timeout": "10s"
}'
```
Save the returned ID to set in the execution.
## Set execution
To configure Zitadel to call the target when an API endpoint is called, you need to set an execution and define the response
condition.
See [Set an execution](/reference/api/action/zitadel.action.v2.ActionService.SetExecution) for more detailed information.
```shell
curl -L -X PUT 'https://${CUSTOM_DOMAIN}/v2/actions/executions' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"condition": {
"response": {
"method": "/zitadel.user.v2.UserService/CreateUser"
}
},
"targets": [
"<TargetID returned>"
]
}'
```
## Example call
Now that you have set up the target and execution, you can test it by creating a user through the Management Console UI or
by calling the ZITADEL API to create a human user.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/users/new' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"organizationId": "344648897353810062",
"human":
{
"profile":
{
"givenName": "Minnie",
"familyName": "Mouse",
"nickName": "Mini",
"displayName": "Minnie Mouse",
"preferredLanguage": "en",
"gender": "GENDER_FEMALE"
},
"email":
{
"email": "[email protected]"
}
}
}'
```
Your server should now print out something like the following. Check out
the [Sent information Response](./usage#sent-information-response) payload description.
The incoming request headers to the Execution are propagated via the request payload to the target.
```json
{
"fullMethod": "/zitadel.user.v2.UserService/CreateUser",
"instanceID": "344648897353744526",
"orgID": "344648897353810062",
"projectID": "344648897353875598",
"userID": "344648897354465422",
"request":
{
"organizationId": "344648897353810062",
"human":
{
"profile":
{
"givenName": "Minnie",
"familyName": "Mouse",
"nickName": "Mini",
"displayName": "Minnie Mouse",
"preferredLanguage": "en",
"gender": "GENDER_FEMALE"
},
"email":
{
"email": "[email protected]"
}
}
},
"response":
{
"id": "336494809936035843",
"creationDate": "2025-09-05T08:55:36.156333Z"
},
"headers":
{
"Content-Type":
[
"application/grpc"
],
"Host":
[
"localhost:8080"
],
"X-Forwarded-For":
[
"::1"
],
"X-Forwarded-Host":
[
"localhost:8080"
]
}
}
```
## Conclusion
You have successfully set up a target and execution to react to API responses in your ZITADEL instance.
This feature can now be used to provision information in between systems or for triggering workflows based on API responses in ZITADEL.
Find more information about the actions feature in the [API documentation](/concepts/features/actions_v2).
@@ -0,0 +1,491 @@
---
title: Using Actions
---
The Action API provides a flexible mechanism for customizing and extending the functionality of ZITADEL. By allowing you to define targets and executions, you can implement custom workflows triggered on an API requests and responses, events or specific functions.
**How it works:**
- Create Target
- Set Execution with condition and target
- Custom Code will be triggered and executed
**Use Cases:**
- User Management: Automate provisioning user data to external systems when users are created, updated or deleted.
- Security: Implement IP blocking or rate limiting based on API usage patterns.
- Extend Workflows: Automatically setup resources in your application, when a new organization in ZITADEL is created.
- Token extension: Add custom claims to the tokens.
## Endpoints
ZITADEL sends an HTTP Post request to the endpoint set as Target, the received request than can be edited and send back or custom processes can be handled.
### Sent information Request
The information sent to the Endpoint is structured as JSON:
```json
{
"fullMethod": "full method of the GRPC call",
"instanceID": "instanceID of the called instance",
"orgID": "ID of the organization related to the calling context",
"projectID": "ID of the project related to the used application",
"userID": "ID of the calling user",
"request": {
"attribute": "Attribute value of full request of the call"
}
}
```
<Callout>
To marshal and unmarshal the request please use a package like [protojson](https://pkg.go.dev/google.golang.org/protobuf/encoding/protojson),
as the request is a protocol buffer message, to avoid potential problems with the attribute names.
</Callout>
### Sent information Response
The information sent to the Endpoint is structured as JSON:
```json
{
"fullMethod": "full method of the GRPC call",
"instanceID": "instanceID of the called instance",
"orgID": "ID of the organization related to the calling context",
"projectID": "ID of the project related to the used application",
"userID": "ID of the calling user",
"request": {
"attribute": "Attribute value of full request of the call"
},
"response": {
"attribute": "Attribute value of full response of the call"
}
}
```
<Callout>
To marshal and unmarshal the request and response please use a package like [protojson](https://pkg.go.dev/google.golang.org/protobuf/encoding/protojson),
as the request and response are protocol buffer messages, to avoid potential problems with the attribute names.
</Callout>
### Sent information Function
Information sent and expected back are specific to the function.
#### PreUserinfo
The information sent to the Endpoint is structured as JSON:
```json
{
"function": "Name of the function",
"userinfo": {
"given_name": "",
"family_name": "",
"middle_name": "",
"nickname": "",
"profile": "",
"picture": "",
...
"preferred_username": "",
"email": "",
"email_verified": true,
"phone_number": "",
"phone_number_verified": true
},
"user": {
"id": "",
"creation_date": "",
...
"human": {
"first_name": "",
"last_name": "",
...
"email": "",
"is_email_verified": true,
"phone": "",
"is_phone_verified": true
}
},
"user_metadata": [
{
"creation_date": "",
"change_date": "",
"resource_owner": "",
"sequence": "",
"key": "",
"value": ""
}
],
"org": {
"id": "ID of the organization the user belongs to",
"name": "Name of the organization the user belongs to",
"primary_domain": "Primary domain of the organization the user belongs to"
},
"user_grants": [
{
"id": "",
"projectGrantId": "The ID of the project grant",
"state": 1,
"creationDate": "",
"changeDate": "",
"sequence": 1,
"userId": "",
"roles": [
"role"
],
"userResourceOwner": "The ID of the organization the user belongs to",
"userGrantResourceOwner": "The ID of the organization the user got authorization granted",
"userGrantResourceOwnerName": "The name of the organization the user got authorization granted",
"projectId": "",
"projectName": ""
}
]
}
```
The expected structure of the JSON as response:
```json
{
"set_user_metadata": [
{
"key": "key of metadata to be set on the user",
"value": "base64 value of metadata to be set on the user"
}
],
"append_claims": [
{
"key": "key of claim to be set on the user",
"value": "value of claim to be set on the user"
}
],
"append_log_claims": [
"Log to be appended to the log claim on the token"
]
}
```
#### PreAccessToken
The information sent to the Endpoint is structured as JSON:
```json
{
"function": "Name of the function",
"userinfo": {
"given_name": "",
"family_name": "",
"middle_name": "",
"nickname": "",
"profile": "",
"picture": "",
...
"preferred_username": "",
"email": "",
"email_verified": true/false,
"phone_number": "",
"phone_number_verified": true/false
},
"user": {
"id": "",
"creation_date": "",
...
"human": {
"first_name": "",
"last_name": "",
...
"email": "",
"is_email_verified": true,
"phone": "",
"is_phone_verified": true
}
},
"user_metadata": [
{
"creation_date": "",
"change_date": "",
"resource_owner": "",
"sequence": "",
"key": "",
"value": ""
}
],
"org": {
"id": "ID of the organization the user belongs to",
"name": "Name of the organization the user belongs to",
"primary_domain": "Primary domain of the organization the user belongs to"
},
"user_grants": [
{
"id": "",
"projectGrantId": "The ID of the project grant",
"state": 1,
"creationDate": "",
"changeDate": "",
"sequence": 1,
"userId": "",
"roles": [
"role"
],
"userResourceOwner": "The ID of the organization the user belongs to",
"userGrantResourceOwner": "The ID of the organization the user got authorization granted",
"userGrantResourceOwnerName": "The name of the organization the user got authorization granted",
"projectId": "",
"projectName": ""
}
]
}
```
The expected structure of the JSON as response:
```json
{
"set_user_metadata": [
{
"key": "key of metadata to be set on the user",
"value": "base64 value of metadata to be set on the user"
}
],
"append_claims": [
{
"key": "key of claim to be set on the user",
"value": "value of claim to be set on the user"
}
],
"append_log_claims": [
"Log to be appended to the log claim on the token"
]
}
```
#### PreSAMLResponse
The information sent to the Endpoint is structured as JSON:
```json
{
"function": "Name of the function",
"userinfo": {
"given_name": "",
"family_name": "",
"middle_name": "",
"nickname": "",
"profile": "",
"picture": "",
...
"preferred_username": "",
"email": "",
"email_verified": true,
"phone_number": "",
"phone_number_verified": true
},
"user": {
"id": "",
"creation_date": "",
...
"human": {
"first_name": "",
"last_name": "",
...
"email": "",
"is_email_verified": true,
"phone": "",
"is_phone_verified": true
}
},
"user_grants": [
{
"id": "",
"projectGrantId": "The ID of the project grant",
"state": 1,
"creationDate": "",
"changeDate": "",
"sequence": 1,
"userId": "",
"roles": [
"role"
],
"userResourceOwner": "The ID of the organization the user belongs to",
"userGrantResourceOwner": "The ID of the organization the user got authorization granted",
"userGrantResourceOwnerName": "The name of the organization the user got authorization granted",
"projectId": "",
"projectName": ""
}
]
}
```
The expected structure of the JSON as response:
```json
{
"set_user_metadata": [
{
"key": "key of metadata to be set on the user",
"value": "base64 value of metadata to be set on the user"
}
],
"append_attribute": [
{
"name": "name of the attribute to be added to the response",
"name_format": "name format of the attribute to be added to the response",
"value": "value of the attribute to be added to the response"
}
]
}
```
### Sent information Event
The information sent to the Endpoint is structured as JSON:
```json
{
"aggregateID": "ID of the aggregate",
"aggregateType": "Type of the aggregate",
"resourceOwner": "Resourceowner the aggregate belongs to",
"instanceID": "ID of the instance the aggregate belongs to",
"version": "Version of the aggregate",
"sequence": "Sequence of the event",
"event_type": "Type of the event",
"created_at": "Time the event was created",
"userID": "ID of the creator of the event",
"event_payload": "Content of the event in JSON format"
}
```
## Target
The Target describes how ZITADEL interacts with the Endpoint.
There are different types of Targets:
- `Webhook`, the call handles the status code but response is irrelevant, can be InterruptOnError
- `Call`, the call handles the status code and response, can be InterruptOnError
- `Async`, the call handles neither status code nor response, but can be called in parallel with other Targets
`InterruptOnError` means that the Execution gets interrupted if any of the calls return with a status code >= 400, and the next Target will not be called anymore.
The API documentation to create a target can be found [here](/reference/api/action/zitadel.action.v2.ActionService.CreateTarget)
### Content Signing
To ensure the integrity of request content, each call includes a 'ZITADEL-Signature' in the headers. This header contains an HMAC value computed from the request content and a timestamp, which can be used to time out requests. The logic for this process is provided in 'pkg/actions/signing.go'. The goal is to verify that the HMAC value in the header matches the HMAC value computed by the Target, ensuring that the sent and received requests are identical.
Each Target resource now contains also a Signing Key, which gets generated and returned when a Target is [created](/reference/api/action/zitadel.action.v2.ActionService.CreateTarget),
and can also be newly generated when a Target is [patched](/reference/api/action/zitadel.action.v2.ActionService.UpdateTarget).
For an example on how to check the signature, [refer to the example](/guides/integrate/actions/testing-request-signature).
## Execution
ZITADEL decides on specific conditions if one or more Targets have to be called.
The Execution resource contains 2 parts, the condition and the called targets.
The condition can be defined for 4 types of processes:
- `Requests`, before a request is processed by ZITADEL
- `Responses`, before a response is sent back to the application
- `Functions`, handling specific functionality in the logic of ZITADEL
- `Events`, after a specific event happened and was stored in ZITADEL
The API documentation to set an Execution can be found [here](/reference/api/action/zitadel.action.v2.ActionService.SetExecution)
### Condition Best Match
As the conditions can be defined on different levels, ZITADEL tries to find out which Execution is the best match.
This means that for example if you have an Execution defined on `all requests`, on the service `zitadel.user.v2.UserService` and on `/zitadel.user.v2.UserService/AddHumanUser`,
ZITADEL would with a call on the `/zitadel.user.v2.UserService/AddHumanUser` use the Executions with the following priority:
1. `/zitadel.user.v2.UserService/AddHumanUser`
2. `zitadel.user.v2.UserService`
3. `all`
If you then have a call on `/zitadel.user.v2.UserService/UpdateHumanUser` the following priority would be found:
1. `zitadel.user.v2.UserService`
2. `all`
And if you use a different service, for example `zitadel.session.v2.SessionService`, then the `all` Execution would still be used.
### Targets
An execution can contain only a list of Targets, and Targets are comma separated string values.
Here's an example of a Target defined on a service (e.g. `zitadel.user.v2.UserService`)
```json
{
"condition": {
"request": {
"service": "zitadel.user.v2.UserService"
}
},
"targets": [
"<TargetID1>"
]
}
```
Here's an example of a Target defined on a method (e.g. `/zitadel.user.v2.UserService/AddHumanUser`)
```json
{
"condition": {
"request": {
"method": "/zitadel.user.v2.UserService/AddHumanUser"
}
},
"targets": [
"<TargetID2>",
"<TargetID1>"
]
}
```
The called Targets on `/zitadel.user.v2.UserService/AddHumanUser` would be, in order:
1. `<TargetID2>`
2. `<TargetID1>`
### Condition for Requests and Responses
For Request and Response there are 3 levels the condition can be defined:
- `Method`, handling a request or response of a specific GRPC full method, which includes the service name and method of the ZITADEL API
- `Service`, handling any request or response under a service of the ZITADEL API
- `All`, handling any request or response under the ZITADEL API
The available conditions can be found under:
- [All available Methods](/reference/api/action/zitadel.action.v2.ActionService.ListExecutionMethods), for example `/zitadel.user.v2.UserService/AddHumanUser`
- [All available Services](/reference/api/action/zitadel.action.v2.ActionService.ListExecutionServices), for example `zitadel.user.v2.UserService`
### Condition for Functions
The available conditions can be found under [all available Functions](/reference/api/action/zitadel.action.v2.ActionService.ListExecutionFunctions).
### Condition for Events
For event there are 3 levels the condition can be defined:
- Event, handling a specific event
- Group, handling a specific group of events
- All, handling any event in ZITADEL
The concept of events can be found under [Events](/concepts/architecture/software#events)
### Error forwarding
If you want to forward a specific error from the Target through ZITADEL, you can provide a response from the Target with status code 200 and a JSON in the following format:
```json
{
"forwardedStatusCode": 403,
"forwardedErrorMessage": "Call is forbidden through the IP AllowList definition"
}
```
Only values from 400 to 499 will be forwarded through ZITADEL, other StatusCodes will end in a PreconditionFailed error.
If the Target returns any other status code than >= 200 and < 299, the execution is looked at as failed, and a PreconditionFailed error is logged.
<Callout>
To interrupt the execution while forwarding this error, "interruptOnError" must be **true** for the Target
</Callout>
@@ -0,0 +1,172 @@
---
title: Testing Actions V2 with Webhook.site
sidebar_label: Testing Actions V2 with Webhook.site
---
## 1. Introduction
This guide explains how to test **ZITADEL Actions V2** locally using [Webhook.site](https://webhook.site).
We will use Webhook.site’s **XHR Redirect** feature to forward requests from ZITADEL to your local machine.
**What you will learn in this guide:**
- How to add the required CORS headers to your local listener
- How to configure Webhook.site with XHR Redirect
- How to create a target in ZITADEL and link it to your Action
- How to verify forwarded requests in your terminal
---
## 2. Prerequisites
### 2.1. Local Listener
You should already have a local HTTP server (for example, the Go example listener from [Testing a Request](./testing-request)) running at `http://localhost:8090/webhook`.
### 2.2. CORS Headers
Because Webhook.site’s XHR redirect runs in your browser, your listener must allow CORS.
Add the following headers in your handler:
```go
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "*")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if req.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
```
---
## 3. Configure Webhook.site XHR Redirect
1. Open your unique Webhook.site URL.
2. Click **Configure XHR Redirect**.
3. Enable **XHR Redirect**.
4. Enter your local endpoint in **Target**, e.g.:
```
http://localhost:8090/webhook
```
5. Leave other fields empty unless you need custom headers.
6. Keep the Webhook.site browser tab open while testing.
<img src="/docs/img/actions-v2/webhook-xhr.png" alt="Screenshot: Webhook.site XHR Redirect configuration interface"/>
---
## 4. Run Your Listener
Start your local listener:
```sh
go run actionsRequest.go
```
You should see output in your console whenever the listener is called.
<img src="/docs/img/actions-v2/console.png" alt="Screenshot: Console output showing listener receiving webhook requests"/>
---
## 5. Create Target in ZITADEL
As shown in the example above, the target is created with HTTP and port '8090'. If you want to use it as a webhook, the target can be created as follows:
See [Create a target](/reference/api/action/zitadel.action.v2.ActionService.CreateTarget) for more detailed information. Notice that the `endpoint` is your Webhook.site URL.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/actions/targets' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"name": "local webhook",
"restWebhook": {
"interruptOnError": true
},
"endpoint": "https://webhook.site/29fa2769-e2f6-44ff-8eb0-2434b83507d8/webhook",
"timeout": "10s"
}'
```
<img src="/docs/img/actions-v2/webhook-url.png" alt="Screenshot: Webhook.site target creation interface"/>
---
Save the returned ID to use in the execution step. A sample response looks like this:
```json
{
"id": "337246363446151234",
"creationDate": "2025-09-10T13:21:36.959699Z",
"signingKey": "OpUHaCtEqh8swdJ5xUYbQ2bhej1abcXYZ"
}
```
## 6. Set execution
To configure ZITADEL to call the target when an API endpoint is called, set an execution and define the request condition.
See [Set an execution](/reference/api/action/zitadel.action.v2.ActionService.SetExecution) for more detailed information.
Here, `<TargetID returned>` is the `id` from the previous step.
```shell
curl -L -X PUT 'https://${CUSTOM_DOMAIN}/v2/actions/executions' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"condition": {
"request": {
"method": "/zitadel.user.v2.UserService/AddHumanUser"
}
},
"targets": [
"<TargetID returned>"
]
}'
```
---
## 7. Example Call
Now that you have set up the target and execution, you can test it by creating a user through the Management Console UI or by calling the ZITADEL API to create a human user.
Here, we are creating a user via the API:
```shell
curl -L -X PUT 'https://${CUSTOM_DOMAIN}/v2/users/human' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"profile": {
"givenName": "Example_given",
"familyName": "Example_family"
},
"email": {
"email": "[email protected]"
}
}'
```
Both your Webhook.site and your local listener should now print out something like the following. See the [Sent information Request](./usage#sent-information-request) payload description for details.
Your local listener should look like this:
<img src="/docs/img/actions-v2/local-listener.png" alt="Screenshot: Local listener output"/>
Your Webhook.site should look like this:
<img src="/docs/img/actions-v2/webhook-listener.png" alt="Screenshot: Webhook.site request log"/>
---
## 8. Done
You now have a fully working setup for testing ZITADEL Actions V2 with Webhook.site. This allows you to forward requests securely from ZITADEL to your local environment without needing a public IP address or domain.
@@ -0,0 +1,45 @@
import ThemedImage from "@/components/docusaurus/themed-image";
import AuthType from "./_auth-type.mdx";
import RedirectURIs from "./_redirect-uris.mdx";
import GenerateKey from "./_generate-key.mdx";
import ReviewConfig from "./_review-config.mdx";
export default function CreateApp(props) {
return (
<div>
<h3>
Create the {props.appName ? props.appName + " app" : "application"}
</h3>
<p>
Go to the detail page of your project and click the "+"-button in the
application-section. This will lead you to the the creation wizard.
</p>
<img
alt="Add application"
src="/docs/img/guides/console/addapplication.png"
width="120px"
/>
<p>
Create the app by setting a name and select the application type "Web"
</p>
<img
alt={"create " + props.appType + " preview"}
src={"/img/guides/application/create-" + props.appType + "-app.png"}
/>
<h3>Select the authentication method</h3>
<p>
The authentication method defines the communication flow during a login
</p>
<AuthType components={props.components} appType={props.appType} authType={props.authType} />
<h3>Redirect URIs</h3>
<RedirectURIs components={props.components}
appType={props.appType}
redirectURI={props.redirectURI}
postLogoutURI={props.postLogoutURI}
/>
<h3>Review your configuration</h3>
<ReviewConfig components={props.components} appType={props.appType} authType={props.authType} />
<GenerateKey components={props.components} appType={props.appType} authType={props.authType} />
</div>
);
}
@@ -0,0 +1,183 @@
import ThemedImage from "@/components/docusaurus/themed-image";
export default function AuthType(props) {
return <div>{defaultAuthTypes(props.appType, props.authType)}</div>;
}
export function defaultAuthTypes(appType, authType) {
let rows;
switch (appType) {
case "web":
rows = web(authType);
break;
case "user-agent":
rows = userAgent(authType);
break;
case "api":
rows = api(authType);
break;
case "native":
rows = native();
break;
default:
return null;
}
return (
<table>
<tbody>{rows.map((fn) => fn())}</tbody>
</table>
);
}
export const web = (typ) => {
switch (typ) {
case "pkce":
return [pkce];
case "code":
return [code];
case "jwt":
return [jwt];
case "post":
return [post];
}
return [pkce, code, jwt, post];
};
export const userAgent = (typ) => {
switch (typ) {
case "pkce":
return [pkce];
case "implicit":
return [implicit];
}
return [pkce, implicit];
};
export const api = (typ) => {
switch (typ) {
case "jwt":
return [jwt];
case "basic":
return [basic];
}
return [jwt, basic];
};
export const native = () => {
return [
() => (
<tr key="native">
<td>
Native only supports code authentication type, that's why you don't
have to select any
</td>
</tr>
),
];
};
export const pkce = () => (
<tr key="pkce">
<td>
<img
width="300px"
src="/docs/img/guides/application/pkce-logo-dark.png"
alt="pkce preview"
/>
</td>
<td>
<h4>PKCE</h4>
<p>Recommended because it's the most secure.</p>
</td>
</tr>
);
export const code = () => (
<tr key="code">
<td>
<img
width="300px"
src="/docs/img/guides/application/code-logo-dark.png"
alt="code preview"
/>
</td>
<td>
<h4>Code</h4>
<p>Use if your application needs client id and client secret</p>
</td>
</tr>
);
export const jwt = () => (
<tr key="jwt">
<td>
<img
width="300px"
src="/docs/img/guides/application/jwt-logo-dark.png"
alt="jwt preview"
/>
</td>
<td>
<h4>(Private Key) JWT</h4>
<p>
Key file to authorize your application. You can create keys after
created the application see{" "}
<a href="#create-key-for-private-key-jwt">below</a>
</p>
</td>
</tr>
);
export const post = () => (
<tr key="post">
<td>
<img
width="300px"
src="/docs/img/guides/application/post-logo-dark.png"
alt="post preview"
/>
</td>
<td>
<h4>Post</h4>
<p>
Only use if you have no other possibilities. Client id and client secret
in request body
</p>
</td>
</tr>
);
export const implicit = () => (
<tr key="implicit">
<td>
<img
width="300px"
src="/docs/img/guides/application/implicit-logo-dark.png"
alt="Implicit preview"
/>
</td>
<td>
<h4>Implicit</h4>
<p>
Only use if you have no other possibilities. The flow is objective to be
removed.
</p>
</td>
</tr>
);
export const basic = () => (
<tr key="basic">
<td>
<img
width="300px"
src="/docs/img/guides/application/basic-logo-dark.png"
alt="Basic preview"
/>
</td>
<td>
<h4>Basic</h4>
<p>The application sends username and password</p>
</td>
</tr>
);
@@ -0,0 +1,19 @@
import ThemedImage from "@/components/docusaurus/themed-image";
export default function GenerateKey(props) {
return props.appType == "api" || props.authType == "jwt" ? (
<div>
<h3>Create key for private key JWT</h3>
<p>
After you successfully created your application with authentication type
JWT your can create keys in the Configuration section and Keys Card of
the app details like following image shows:
</p>
<img
width="400px"
alt="Generate key"
src="/docs/img/guides/application/generate-key.png"
/>
</div>
) : null;
}
@@ -0,0 +1,51 @@
import ThemedImage from "@/components/docusaurus/themed-image";
import Admonition from "@/components/docusaurus/admonition";
export default function RedirectURIs(props) {
return ["web", "native", "user-agent"].includes(props.appType) ? (
<div>
<p>
During the login flow the application defines where a user is redirected
to after login or logout.
<br />
ZITADEL verifies if the URL the user gets redirected to is valid by
checking if one of the redirect URIs match.
</p>
<ul>
<li>
<b>Redirect URIs</b> are verified during the login process.
</li>
{props.redirectURI ? (
<ul>
<li>
The default redirect uri of your app is{" "}
<code>{props.redirectURI}</code>
</li>
</ul>
) : null}
<li>
<b>Post Logout URIs</b> are verified during the logout process.
</li>
{props.postLogoutURI ? (
<ul>
<li>
The default post logout uri of your app is{" "}
<code>{props.postLogoutURI}</code>
</li>
</ul>
) : null}
</ul>
<Admonition type="note">
<p>
The default redirect uri of your app is{" "}
<code>{props.redirectURI}</code>
</p>
</Admonition>
<img
width="600px"
alt="Redirect URIs configuration"
src={"/img/guides/application/redirect-uris.png"}
/>
</div>
) : null;
}
@@ -0,0 +1,41 @@
import ThemedImage from "@/components/docusaurus/themed-image";
export default function ReviewConfig(props) {
let clientObjects = [];
if (clientID(props.appType, props.authType)) {
clientObjects.push("id");
}
if (clientSecret(props.appType, props.authType)) {
clientObjects.push("secret");
}
return clientObjects.length > 0 ? (
<div>
<p>
The last page of the stepper shows a summary of what will be created.
After you have reviewed the configuration you can create the
application.
</p>
<h3>Client information</h3>
<p>
Please make sure to safe the <b>client {clientObjects.join(" and ")}</b>{" "}
for later use in the application.
</p>
<img
alt="client infos"
src={`/docs/img/guides/application/client-${clientObjects.join("-")}.png`}
width="700px"
/>
</div>
) : null;
}
export function clientID(appType, authType) {
return (
["pkce", "code", "jwt", "post", "implicit", "basic"].includes(authType) ||
appType === "native"
);
}
export function clientSecret(appType, authType) {
return ["code", "post", "basic"].includes(authType);
}
@@ -0,0 +1,86 @@
---
title: Embed Authenticated MongoDB Charts Using ZITADEL
sidebar_label: Authenticated MongoDB Charts
---
This integration guide shows how you can embed authenticated MongoDB Charts in your web application using ZITADEL as authentication provider.
## Setup ZITADEL Application
Before you can embed an authenticated chart in your application, you have to do a few configuration steps in ZITADEL Console.
You will need to provide some information about your app. We recommend creating a new app to start from scratch.
1. Navigate to your Project
2. Add a new application at the top of the page.
3. Select Web application type and continue.
4. Use [Authorization Code](/apis/openidoauth/grant-types#authorization-code) in combination with [Proof Key for Code Exchange (PKCE)](/apis/openidoauth/grant-types#proof-key-for-code-exchange).
5. Skip the redirect settings and confirm the app creation
6. Copy the client ID, you will need to tell MongoDB Charts about it.
7. When you created the app, expand its _OIDC Configuration_ section, change the _Auth Token Type_ to _JWT_ and save the change.
Your application configuration should now look similar to this:
![Create app in management console](../../../public/img/integrations/mongodb-charts-app-create-light.png)
## Setup Custom JWT Provider for MongoDB Charts
Configure ZITADEL as your _Custom JWT Provider_ following the [MongoDB docs](https://docs.mongodb.com/charts/configure-auth-providers/) .
Configure the following values:
- Signing Algorithm: RS256
- Signing Key: JWK or JWKS URL
- JWKS: https://$CUSTOM_DOMAIN/oauth/v2/keys
- Audience: Your app's client ID which you copied when you created the ZITADEL app
Your configuration should look similar to this:
![](../../../public/img/integrations/mongodb-charts-auth-provider-light.png)
## Embedding your Chart
Embed a chart into your application now, following the corresponding [MongoDB docs](https://docs.mongodb.com/charts/saas/embed-chart-jwt-auth/).
If you've done the [Angular Quickstart](/examples/login/angular), your code could look something like this:
```html
<div id="chart"></div>
```
```css
/* chart.component.css */
div#chart {
height: 500px;
}
```
```ts
// chart.component.ts
@Component({
selector: 'app-chart',
templateUrl: './chart.component.html',
styleUrls: ['./chart.component.css']
})
export class ChartComponent implements OnInit {
constructor(private auth: AuthenticationService) { }
ngOnInit(): void {
this.renderChart().catch(e => window.alert(e.message));
}
async renderChart() {
const sdk = new ChartsEmbedSDK({
baseUrl: "<YOUR CHARTS BASE URL HERE>",
getUserToken: () => {
return this.auth.getAccessToken()
},
});
const chart = sdk.createChart({
chartId: "<YOUR CHART ID HERE>"
});
await chart.render(<HTMLElement>document.getElementById("chart"));
}
}
```
@@ -0,0 +1,176 @@
---
title: OIDC Back-Channel Logout
sidebar_label: Back-Channel Logout
---
The Back-Channel Logout implements [OpenID Connect Back-Channel Logout 1.0](https://openid.net/specs/openid-connect-backchannel-1_0.html)
and can be used to notify clients about session termination at the OpenID Provider. This guide will explain how
back-channel logout is implemented inside ZITADEL and gives some usage examples.
<Callout type="info">
Back-Channel Logout is currently an experimental [beta](https://help.zitadel.com/zitadel-software-release-cycle#beta) feature.
Be sure to enable it on the [feature API](#feature-api) before using it.
We highly recommend also enabling the `webkey` feature on your instance. This will prevent issues with automatic key rotation.
</Callout>
In this guide we assume your already familiar with getting and validation token. You should already have a good
understanding on the following topics before starting with this guide:
- Integrate your app with the [OIDC flow](/guides/integrate/login/oidc/login-users) to obtain tokens
- [Claims](/apis/openidoauth/claims)
- [Scope](/apis/openidoauth/scopes)
- Audience
## Concept
ZITADEL provides the possibility for OpenID Connect clients to be notified about the session termination, for example
if a user signs out from another client using the same SSO session.
This allows the client to also invalidate the user's session without the need for an active browser session.
![](../../../public/img/guides/back-channel-logout/back-channel-logout-flow.png)
1. When an unauthenticated user visits your application,
2. it will create an authorization request to the authorization endpoint.
3. The Authorization Server (ZITADEL) will send an HTTP 302 to the user's browser, which will redirect them to the login UI.
4. The user will have to authenticate using the demanded auth mechanics.
5. Your application will be called on the registered callback path (redirect_uri) for the authorization code exchange.
See [OIDC Flow](/guides/integrate/login/oidc/login-users) for more details.
On successful exchange, an SSO session will be created.
6. If the user opens another application,
7. the application will also create an authorization request to the authorization endpoint.
8. ZITADEL can then reuse the existing SSO session and will not ask the user to authenticate again and directly return the code for exchange.
See [OIDC Flow](/guides/integrate/login/oidc/login-users) again for details.
9. At a later point, the user signs out from one of the applications, in this case the second one.
10. The application will redirect the user to the end_session endpoint.
11. ZITADEL will terminate the SSO session and redirect the user back to the application's post_logout_redirect_uri.
The application can delete the local session.
12. ZITADEL will also send a back-channel logout request to every registered application with previously opened sessions.
The application can then invalidate the user's session without the need for an active browser session.
### Indicating Support
As required by the [specification](https://openid.net/specs/openid-connect-backchannel-1_0.html#BCSupport), ZITADEL
will advertise the `backchannel_logout_supported` and `backchannel_logout_session_supported`
on the discovery endpoint once the feature flag is enabled.
The latter boolean indicates, that ZITADEL will provide a session ID (`sid`) claim as part of the logout token. This
provides the possibility to match the exact SSO session, which was terminated.
### Client
To enable the back-channel logout on a client, they simply need to register a `backchannel_logout_uri` as part of their
configuration, e.g. [creating an OIDC application](https://zitadel.com/docs/reference/api/management/management-service-add-oidc-app).
As soon as the URI is set, every new authorization request will register a back-channel notification to be sent once
the session is terminated by a user sign out.
## Back-Channel Request
When the session is terminated, ZITADEL will send back-channel logout requests asynchronously to every registered
client of the corresponding session.
The request is an `application/x-www-form-urlencoded` POST request to the registered URI including a `logout_token`
parameter in the body.
Please be aware that body *may* contain other values in addition to logout_token. Values that are not understood by the
implementation *must* be ignored.
### Logout Token
The `logout_token` sent in the request is a JWT similar to an ID Token.
<Callout>
Note however, that a Logout Token must never contain a `nonce` claim, to make sure it cannot be used as an ID Token.
</Callout>
The following Claims are used within the Logout Token:
| Claim | Example | Description |
|--------|----------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
| iss | `${CUSTOM_DOMAIN}` | Issuer Identifier |
| sub | `77776025198584418` | Subject Identifier (the user who signed out) |
| aud | `69234237810729019` | Audience(s), will always contain your client_id |
| iat | `1311280970` | Issued at time |
| exp | `1311281970` | Expiration time (by default 15min after the issued at time) |
| jti | `69234237813329048` | Unique identifier for the token |
| events | `{ "http://schemas.openid.net/event/backchannel-logout": {} }` | JSON object, which always contains http://schemas.openid.net/event/backchannel-logout. This declares that the JWT is a Logout Token. |
| sid | `291693710356251044` | Session ID - String identifier for a Session. |
#### Validation
Verify the Logout Token the same way you verify an ID Token including signature validation, issuer, audience, expiration and issued at time claim checks.
Make sure that either a subject (`sub`) and / or a session ID (`sid`) is present in the token to identify the user or its session.
Also, check that the `events` claim contains the `http://schemas.openid.net/event/backchannel-logout` value.
Optionally, you can also verify that the TokenID (`jti`) has not been used before.
For details on how to validate the logout token, please refer to the [OpenID Connect Back-Channel Logout specification](https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation).
## Example
This example assumes that your application is registered with client_id `243864426485212395@example` and the back-channel logout
URI is set to `https://example.com/logout`.
### Authentication
When the user signed in to your application, ZITADEL issued the following id_token:
```
eyJhbGciOiJSUzI1NiIsImtpZCI6IjI5NjkzMzA1NjAxNzY0Nzg3NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJzdWIiOiIxNjQ4NDk0OTQyOTc0NzczNzciLCJhdWQiOlsiMjQzODY0NDI2NDg1MjEyMzk1QGV4YW1wbGUiLCIyNDM4NjQzODExNjk4ODY0NDMiXSwiZXhwIjoxNzMzNzgyNDQ0LCJpYXQiOjE3MzM3MzkyNDQsImF1dGhfdGltZSI6MTczMzczOTI0NCwiYW1yIjpbInVzZXIiLCJtZmEiXSwiYXpwIjoiMjQzODY0NDI2NDg1MjEyMzk1QGV4YW1wbGUiLCJjbGllbnRfaWQiOiIyNDM4NjQ0MjY0ODUyMTIzOTVAZXhhbXBsZSIsImF0X2hhc2giOiJSWVFPSkJuT01LS0hrN1VnLWY1eFJnIiwic2lkIjoiVjFfMjk3MzY0ODE4OTgwMDM0MDA0In0K.lZxHE_Z4tiaDQE-DPtYjnvb0H9rz4wMoGfBMeEm4EG837DGJb7RTq7PuMHWc4Z2e_6lilwfVBWDEOhmrnjmkQwDVxInbbJfN0NiWgeqoW-C1SZ_G00UVIbJdaxPy2-haRihDNNpy0Gjmi7q3FkGXGqkJx9S7ZtC5ISbXLnqfbRbuapoMs7hHNf-Iltf8v7dMs3K8dcAPSHJm0X0x6Cu1ZMeAS2a6H05xKXGM0bRK830AZlL8xmxTNj_q_WZKzxz304XrRNHvYRcHKmJqURSHvRNUR38QeNaiKzINlV2sVvPEY6Dru_PHSPNFu7YLWiUi34VUla6VTxy9ctI_BtI4nw
```
This represents the following claims:
```json
{
"iss": "http://localhost:8080",
"sub": "164849494297477377",
"aud": [
"243864426485212395@example",
"243864381169886443"
],
"exp": 1733782444,
"iat": 1733739244,
"auth_time": 1733739244,
"amr": [
"user",
"mfa"
],
"azp": "243864426485212395@example",
"client_id": "243864426485212395@example",
"at_hash": "RYQOJBnOMKKHk7Ug-f5xRg",
"sid": "V1_297364818980034004"
}
```
As you can see, the `sid` claim is present in the token. This represents the application's specific session ID of the
user session.
### Sign Out and Back-Channel Logout
When the user signs out from another application, ZITADEL will send a POST request to `https://example.com/logout` with
the following body:
```http
POST /logout HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
logout_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjI5NjkzMzA1NjAxNzY0Nzg3NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJzdWIiOiIxNjQ4NDk0OTQyOTc0NzczNzciLCJhdWQiOlsiMjQzODY0NDI2NDg1MjEyMzk1QGV4YW1wbGUiXSwiaWF0IjoxNzMzNzM5MjUyLCJleHAiOjE3MzM3NDAxNTMsImp0aSI6IjI5NzM2NDgzNDQ5ODkyMTEzNyIsImV2ZW50cyI6eyJodHRwOi8vc2NoZW1hcy5vcGVuaWQubmV0L2V2ZW50L2JhY2tjaGFubmVsLWxvZ291dCI6e319LCJzaWQiOiJWMV8yOTczNjQ4MTg5ODAwMzQwMDQifQo.ELOPuS61fy8GgCKEtru5df4-9GI4-KQlNf_DMp6b5mtJZIrfykA_M7lYOxOskYhTDicBoQ2jjOjzsDqktI4r6ptD068c5LEOx-k2OVk7ybADsK7tht5omYy4tsbHmkDCZN065WMH0SQH7NKGroVW-MACi6Peuiz3nlQsfho0EnLECqhZT60qxu6qtofvBVhHe15Zlkzffy0vxjKEIeJoTmX_cNVsHlrC_n1vTqZStBrkqu3_rwZxZuynX47vf7_kj_kKhJ3TffRF561n1AP5xnhZ9i--rnaucbGtGGImlKi2sdqC4GzjtdlaKJaRuVF-91x758SLBxqJXPucroJoWw
```
```json
{
"iss": "http://localhost:8080",
"sub": "164849494297477377",
"aud": [
"243864426485212395@example"
],
"iat": 1733739252,
"exp": 1733740153,
"jti": "297364834498921137",
"events": {
"http://schemas.openid.net/event/backchannel-logout": {}
},
"sid": "V1_297364818980034004"
}
```
After validating the token, the application can now invalidate the user's local session based on the `sid` claim.
Some applications might also want to delete all user sessions. In this case, the `sub` claim can be used to identify the user.
@@ -0,0 +1,80 @@
---
title: Streaming audit logs to external systems (SIEM/SOC)
sidebar_label: Audit Logs (SIEM / SOC)
---
This document details integrating ZITADEL with external systems for streaming events and audit logs.
This functionality allows you to centralize ZITADEL activity data alongside other security and operational information, facilitating comprehensive monitoring, analysis, and compliance reporting.
Integrating ZITADEL with external systems offers several advantages:
- **Centralized Monitoring**: Streamlining ZITADEL events into a single platform alongside data from various sources enables a consolidated view of your security posture. This comprehensive view simplifies threat detection, investigation, and incident response.
- **Enhanced Security Analytics**: External systems, such as Security Information and Event Management (SIEM) solutions, can leverage ZITADEL events to identify suspicious activities, potential security breaches, and user access anomalies.
- **Compliance Reporting**: ZITADEL events can be used to generate detailed audit trails, fulfilling regulatory compliance requirements for data access and user activity.
By integrating ZITADEL with external systems, you gain valuable insights into user behavior, system activity, and potential security threats, ultimately strengthening your overall security posture and regulatory compliance.
ZITADEL provides different solutions how to send events to external systems, the solution you choose might differ depending on your use case, your database and your environment (ZITADEL Cloud, Self-hosting).
The following table shows the available integration patterns for streaming audit logs to external systems.
| | Description | Self-hosting | ZITADEL Cloud |
|-------------------------------------|----------------------------------------------------------------------------------------------------------------|-------------|---------------|
| Events-API | Pulling events of all ZITADEL resources such as Users, Projects, Apps, etc. (Events = Change Log of Resources) | ✅ | ✅ |
| ZITADEL Actions Log to Stdout | Custom log to messages possible on predefined triggers during login / register Flow | ✅ | ❌ |
| ZITADEL Actions trigger API/Webhook | Custom API/Webhook request on predefined triggers during login / register | ✅ | ✅ |
### Events API
The ZITADEL Event API empowers you to proactively pull audit logs for comprehensive security and compliance monitoring, regardless of your environment (cloud or self-hosted).
This API offers granular control through various filters, enabling you to:
- **Specify Event Types**: Focus on specific events of interest, such as user token created, password changed, or project added.
- **Target Aggregates**: Narrow down the data scope by filtering for events related to particular organizations, projects, or users.
- **Define Time Frames**: Retrieve audit logs for precise time periods, allowing you to schedule data retrieval at desired intervals (e.g., hourly) and analyze activity within specific windows.
You can find a comprehensive guide on how to use the events API for different use cases here: [Get Events from ZITADEL](/guides/integrate/zitadel-apis/event-api)
## ZITADEL Actions
ZITADEL [Actions](/concepts/features/actions) offer a powerful mechanism for extending the platform's capabilities and integrating with external systems tailored to your specific requirements.
Actions are essentially custom JavaScript snippets that execute at predefined triggers during the registration or login flow of a user.
In the future ZITADEL Actions will be extended to allow to not only define them during the login and register flow, but also on each API Request, Event or Predefined Functions.
### Log to stdout
With the [log module](/apis/actions/modules#log) you can log any custom message to stdout.
Those logs in stdout can be collected by your external system.
Example Use Case:
In my external system for example Splunk I want to be able to get an information each time a user has authenticated.
1. Define an action that logs successful and failed login to your stdout.
Make sure the name of the action is the same as of the function in the script.
```ts reference
https://github.com/zitadel/actions/blob/main/examples/post_auth_log.js
```
2. Add the action to the following Flows and Triggers
- Flow: Internal Authentication - Trigger: Post Authentication
- Flow: External Authentication - Trigger: Post Authentication
3. Authenticate User
4. Collect Data from stdout
### Webhook/API request
The [http module](/apis/actions/modules#http) allows you to make a request to a REST API.
This allows you to send a request at a specific point during the login or registration flow with the data you defined in your action.
Example use case:
You want to send a request to an endpoint each time after an authentication (successful or not).
1. Define an action that calls API endpoint.
Make sure the name of the action is the same as of the function in the script.
Example how to call an API endpoint:
```ts reference
https://github.com/zitadel/actions/blob/main/examples/make_api_call.js
```
2. Add the action to the following flows and triggers
- Flow: Internal Authentication - Trigger: Post Authentication
- Flow: External Authentication - Trigger: Post Authentication
3. Authenticate user
4. Get data on your API
@@ -0,0 +1,9 @@
Once you created the provider, it is listed in the providers overview.
Activate it by selecting the tick with the tooltip *set as available*.
If you deactivate a provider, your users with links to it will not be able to authenticate anymore.
You can reactivate it and the logins will work again.
The provider can also be activated via API. As the identity providers are sub-resources of the login settings, this is done by linking the provider to the settings:
- [Activate Default Identity Provider](/reference/api/admin/zitadel.admin.v1.AdminService.AddIDPToLoginPolicy)
- [Activate Organization Identity Provider](/reference/api/management/zitadel.management.v1.ManagementService.AddIDPToLoginPolicy)
@@ -0,0 +1,11 @@
1. Go to the Settings
- To allow external IdP logins by default, go to your instance default settings at `${CUSTOM_DOMAIN}/ui/console/instance?id=general`
- To allow external IdP logins on an organization, go to `${CUSTOM_DOMAIN}/ui/console/org-settings?id=login` and ensure you have the right org context.
2. Modify your login policy in the menu "Login Behavior and Security"
3. Enable the attribute "External Login allowed"
You can also change the settings through the API directly either in the default settings or on a specific organization:
- [Update Default Login Settings](/reference/api/admin/zitadel.admin.v1.AdminService.UpdateLoginPolicy)
- [Update Organization Login Settings](/reference/api/management/zitadel.management.v1.ManagementService.UpdateCustomLoginPolicy)
![](../../../../public/img/guides/zitadel_allow_external_idp.png)
@@ -0,0 +1,8 @@
<p><strong>Automatic creation</strong>: If this setting is enabled the user will be created automatically within ZITADEL, if it doesn't exist.</p>
<p><strong>Automatic update</strong>: If this setting is enabled, the user will be updated within ZITADEL, if some user data is changed withing the provider. E.g if the lastname changes on the {props.provider_account}, the information will be changed on the ZITADEL account on the next login.</p>
<p><strong>Account creation allowed</strong>: This setting determines if account creation within ZITADEL is allowed or not.</p>
<p><strong>Account linking allowed</strong>: This setting determines if account linking is allowed. When logging in with a {props.provider_account}, a linkable ZITADEL account has to exist already.</p>
<Callout>
Either account creation or account linking have to be enabled. Otherwise, the provider can't be used.
</Callout>
@@ -0,0 +1,12 @@
The Generic OAuth Provider allows you to configure any OAuth compliant identity providers.
The following information you need to fill out by yourself:
<li><b>Name</b> {props.name}</li>
<li><b>Authorization Endpoint</b> {props.authendpoint}</li>
<li><b>Token Endpoint</b> {props.tokenendpoint}</li>
<li><b>User Endpoint</b> {props.userendpoint}</li>
<li><b>ID Attribute</b> {props.id}</li>
<li><b>Client-ID</b> {props.clientid}</li>
<li><b>Client-Secret</b> {props.clientid}</li>
<li><b>Scopes</b>: (openid, profile, email is preconfigured)</li>
@@ -0,0 +1,8 @@
When you use an LDAP provider in ZITADEL, this is the login process:
1. ZITADEL tries to connect to the LDAP server with or without TLS depending on the configuration
2. If the connection fails, the next server in the list will be used to try again.
3. ZITADEL tries a bind with the BindDN and BindPassword to check if it's possible to proceed
4. ZITADEL does a SearchQuery to find the UserDN with the provided configuration of base, filters and objectClasses
5. ZITADEL tries a bind with the provided loginname and password
6. LDAP attributes get mapped to ZITADEL attributes as provided by the configuration
@@ -0,0 +1,8 @@
Go to the settings page of your instance or organization and choose "Identity Providers".
In the table you can see all the providers you have configured.
Also, you see all provider templates that are available.
![](../../../../public/img/guides/zitadel_identity_provider_overview.png)
<p>Select the <b>{props.templates}</b> Provider template.</p>
@@ -0,0 +1,9 @@
<p>This guides shows you how to connect {props.provider} as an identity provider in ZITADEL.</p>
<Callout>
<p>
In ZITADEL you can connect an Identity Provider (IdP) like {props.provider} to your instance and provide it as default to all organizations.
Also, you can register the IdP to a specific organization only.
If you allow so, your organizations members can do the same in self-service.
</p>
</Callout>
@@ -0,0 +1,5 @@
<p>You can use a ZITADEL action if you want to prefill the fields {props.fields} with {props.provider} data.</p>
1. Go to the users target organizations settings page.
2. Add a new action with the body below. Make sure the action name equals the scripts function name. Also change the id in the script to match your provider configurations id.
3. Add the action to the flow "External Authentication" and trigger it on "Post Authentication"
@@ -0,0 +1,11 @@
<p>
To test the setup, use incognito mode and browse to your login page. You see a
new button which redirects you to {props.loginscreen} screen.
</p>
By default, ZITADEL shows what you define in the default settings.
If you overwrite the default settings for an organization, you need to send the organization scope in your auth request.
The organization scope looks like this: `urn:zitadel:iam:org:id:{id}`.
You can [read more about the reserved scopes](/apis/openidoauth/scopes#reserved-scopes)
or [use the ZITADEL OIDC Playground](https://zitadel.com/playgrounds/oidc) to see what happens with the login when you send different scopes.
@@ -0,0 +1,5 @@
<p>
New unlinked users are presented with the screen below.
<span> {props.provider}</span> is an OAuth provider and does not provide a standardized way to get the user data.
This means that ZITADEL has no way to prefill the first and lastname fields.
</p>
@@ -0,0 +1,15 @@
---
title: Profile Pre-filling from External IdP
sidebar_label: Profile Pre-filling from External IdP
---
import PrefillAction from './_prefill_action.mdx';
## Automatically pre-fill user data
<PrefillAction components={props.components} fields="specific fields like firstname, lastname and email verified" provider="your providers"/>
This action is an example for OKTA. You can also use it for any other provider
```js reference
https://github.com/zitadel/actions/blob/main/examples/okta_identity_provider.js
```
@@ -0,0 +1,88 @@
---
title: Configure Apple as an Identity Provider in ZITADEL
sidebar_label: Apple
---
import GeneralConfigDescription from './_general_config_description.mdx';
import Intro from './_intro.mdx';
import CustomLoginPolicy from './_custom_login_policy.mdx';
import IDPsOverview from './_idps_overview.mdx';
import Activate from './_activate.mdx';
import TestSetup from './_test_setup.mdx';
<Intro components={props.components} provider="Apple"/>
## Open the Apple Identity Provider Template
<IDPsOverview components={props.components} templates="Apple"/>
Click on the ZITADEL Callback URL to copy it to your clipboard.
You will have to paste it in the Apple service later.
![](../../../../public/img/guides/zitadel_apple_create_provider.png)
## Apple Configuration
### Register a new App
1. Go to the Identifiers of your Apple Developer Account
2. Click the add button "+" on the top left
3. Choose App IDs and click "continue"
4. Add a description and a unique identifier
5. Enable "Sign in with Apple" and click "continue"
### Register a new service
1. Go to the Identifiers of your Apple Developer Account: [https://developer.apple.com/account/resources/identifiers/list](https://developer.apple.com/account/resources/identifiers/list)
2. Click the add button "+" on the top left
3. Choose Services IDs and click "continue"
4. Add a description and a unique identifier and click "register"
5. Select your registered service from the list and enable sign in with Apple, then click "configure"
6. Choose the previously created App in the Primary App ID List
7. Add your Custom Domain in the domains and subdomains field
- Example domain for `https://acme-gzoe4x.zitadel.cloud` would look like this: `acme-gzoe4x.zitadel.cloud`
8. [Paste the ZITADEL Callback URL you copied before](#open-the-apple-identity-provider-template) to the Return URLs
![](../../../../public/img/guides/apple_service_create.png)
### Register a new key
1. Go to the keys list of your Apple Developer Account: [https://developer.apple.com/account/resources/authkeys/list](https://developer.apple.com/account/resources/authkeys/list)
2. Click the add button "+" on the top left
3. Give your key a name
4. Enable "Sign in with Apple" and click configure
5. Choose your app from the list
6. Register the key and download it
## ZITADEL Configuration
Go back [to the Apple provider template you opened before in ZITADEL](#open-the-apple-identity-provider-template).
1. Add the Client ID, this is [the identifier of the service you created in your Apple Account](#register-a-new-service)
2. Fill the Team ID, you can find it when you login to your Apple Developer account, in your membership
3. Enter the [Key ID and upload the Private Key you created before](#register-a-new-key)
You can optionally configure the following settings.
A useful default will be filled if you don't change anything.
**Scopes**: The scopes define which scopes will be sent to the provider, `name` and `email` are prefilled. This information will be taken to create/update the user within ZITADEL.
<GeneralConfigDescription components={props.components} provider_account="Apple account" />
### Activate IdP
<Activate components={props.components} />
![](../../../../public/img/guides/zitadel_activate_apple.png)
### Ensure your Login Policy allows External IDPs
<CustomLoginPolicy components={props.components} />
## Test the setup
<TestSetup components={props.components} loginscreen="your Apple login"/>
![](../../../../public/img/guides/zitadel_login_apple.png)
![](../../../../public/img/guides/apple_login.png)
@@ -0,0 +1,112 @@
---
title: Configure Entra ID as an Identity Provider in ZITADEL
sidebar_label: Entra ID
---
import GeneralConfigDescription from './_general_config_description.mdx';
import Intro from './_intro.mdx';
import CustomLoginPolicy from './_custom_login_policy.mdx';
import IDPsOverview from './_idps_overview.mdx';
import TestSetup from './_test_setup.mdx';
import Activate from './_activate.mdx';
<Intro components={props.components} provider="Entra ID (former Azure Active Directory)"/>
## Open the Microsoft Identity Provider Template
<IDPsOverview components={props.components} templates="Microsoft"/>
Click on the ZITADEL Callback URL to copy it to your clipboard.
You will have to paste it in the Entra ID Client later.
![](../../../../public/img/guides/zitadel_azure_provider2.png)
## Entra ID Configuration
You need to have access to an Entra ID Tenant. If you do not yet have one follow [this guide from Microsoft](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-create-new-tenant) to create one for free.
### Register a new client
1. Browse to the [App registration menus create dialog](https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/CreateApplicationBlade/quickStartType~/null/isMSAApp~/false) to create a new app.
2. Give the application a name and choose who should be able to login (Single-Tenant, Multi-Tenant, Personal Accounts, etc.) This setting will also have an impact on how to configure the provider later on in ZITADEL.
3. Choose "Web" in the redirect uri field and [paste the ZITADEL Callback URL you copied before](#open-the-microsoft-identity-provider-template).
4. Save the Application (client) ID and the Directory (tenant) ID from the detail page
5. Register the application
![](../../../../public/img/guides/azure_app_registration.png)
![](../../../../public/img/guides/azure_client_tenant_id.png)
### Add client secret
Generate a new client secret to authenticate your user.
1. Click on client credentials on the detail page of the application or use the menu "Certificates & secrets"
2. Click on "+ New client secret" and enter a description and an expiry date, add the secret afterwards
3. Copy the value of the secret. You will not be able to see the value again after some time
![](../../../../public/img/guides/azure_client_secret.png)
### Token configuration
To allow ZITADEL to get the information from the authenticating user you have to configure what kind of optional claims should be returned in the token.
1. Click on Token configuration in the side menu
2. Click on "+ Add optional claim"
3. Add email, family_name (last name of the user), given_name (first name of the user) and preferred_username to the id token
![](../../../../public/img/guides/azure_token_configuration.png)
### API permissions
To be able to get all the information that ZITADEL needs, you have to configure the correct permissions.
1. Go to "API permissions" in the side menu
2. Make sure the permissions include "Microsoft Graph": email, profile and User.Read
3. The "Other permissions granted" should include "Microsoft Graph: openid"
![](../../../../public/img/guides/azure_api_permissions.png)
## ZITADEL Configuration
Go back [to the Microsoft provider template you opened before in ZITADEL](#open-the-microsoft-identity-provider-template).
Add the [client ID and secret created before on your Entra ID application](#add-client-secret).
You can optionally configure the following settings.
A useful default will be filled if you don't change anything.
**Scopes**: The scopes define which scopes will be sent to the provider, `openid`, `profile`, and `email` are prefilled.
This information will be taken to create/update the user within ZITADEL. Make sure to also add `User.Read`. ZITADEL ensures that at least `openid` and `User.Read` scopes are always sent.
**Email Verified**: Entra ID doesn't send the email verified claim in the users token, if you don't enable this setting.
The user is then created with an unverified email, which results in an email verification message.
If you want to avoid that, make sure to enable "Email verified".
In that case, the user is created with a verified email address.
**Tenant Type**: Configure the tenant type according to what you have chosen in the settings of your Entra ID application previously.
- Common: Choose common if you want all Microsoft accounts being able to login.
In this case, configure "Accounts in any organizational directory and personal Microsoft accounts" in your Entra ID App.
- Organizations: Choose organization if you have Entra ID Tenants and no personal accounts. (You have configured either "Accounts in this organization" or "Accounts in any organizational directory" on your Azure APP)
- Consumers: Choose this if you want to allow public accounts. (In your Entra ID App you have configured "Personal Microsoft accounts only")
**Tenant ID**: If you have selected *Tenant ID* as *Tenant Type*, you have to enter the *Directory (Tenant) ID* into the *Tenant ID* field, copied previously from the Azure App configuration.
<GeneralConfigDescription components={props.components} provider_account="Microsoft account" />
### Activate IdP
<Activate components={props.components} />
![](../../../../public/img/guides/zitadel_activate_azure.png)
### Ensure your Login Policy allows External IDPs
<CustomLoginPolicy components={props.components} />
## Test the setup
<TestSetup components={props.components} loginscreen="your Microsoft login"/>
![](../../../../public/img/guides/zitadel_login_azure.png)
![](../../../../public/img/guides/microsoft_login.png)
@@ -0,0 +1,129 @@
---
title: Configure Entra ID as a SAML Service Provider in ZITADEL
sidebar_label: Entra ID SAML
---
import GeneralConfigDescription from './_general_config_description.mdx';
import Intro from './_intro.mdx';
import CustomLoginPolicy from './_custom_login_policy.mdx';
import IDPsOverview from './_idps_overview.mdx';
import Activate from './_activate.mdx';
import PrefillAction from './_prefill_action.mdx';
<Intro components={props.components} provider="Entra ID (former Azure Active Directory)"/>
## Entra ID SAML Configuration
You need to have access to an Entra ID Tenant. If you do not yet have one follow [this guide from Microsoft](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-create-new-tenant) to create one for free.
### Register a new enterprise application in Entra
We start setting up the enterprise application.
1. Browse to the [Enterprise App registration menu](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/AppGalleryBladeV2).
2. Search for "SAML Toolkit" and click on the "Microsoft Entra SAML Toolkit" card.
3. Change the name if wanted and click "Create"
![](../../../../public/img/guides/azure_saml_app_create.png)
### Disable required assignment
To allow all users to sign in using ZITADEL we need to manually disable required assignment:
1. Go to Manage > Properties
2. Set "Assignment required?" to No
3. Hit Save
![](../../../../public/img/guides/azure_disable_assignment_required.png)
### Setup SAML
Configure the sign-on method of the app.
1. Go to Manage > Single sign-on
2. Select SAML
3. You will be redirected to the Single Sign-On details page
4. Copy the URL of SAML Certificates > App Federation Metadata Url to your clipboard
![](../../../../public/img/guides/azure_saml_overview.png)
## ZITADEL Configuration
### Go to the IdP Providers Overview
<IDPsOverview components={props.components} templates="SAML"/>
### Create a new SAML Service Provider (SP)
Now we configure the identity provider on ZITADEL.
1. Set a name like "Microsoft Entra"
2. Paste the previously copied URL into the "Metadata URL"-field. The metadata will automatically be fetched from the provided URL after creation.
3. Select the "SAML_POST_BINDING" as binding
4. Ensure that the "Signed Request"-box is ticked
5. Change the options if needed. Microsoft Entra works out of the box using the pre configured options.
6. Click Create
<GeneralConfigDescription components={props.components} provider_account="Microsoft account" />
![](../../../../public/img/guides/zitadel_azure_saml_provider.png)
## Configure Basic SAML Configuration
After you created the SAML provider in ZITADEL, you can copy the URLs you need to configure in your Entra ID application.
![](../../../../public/img/guides/zitadel_azure_saml_provider_urls.png)
1. Go to Microsoft Entra > Manage > Single sign-on
2. Edit the "Basic SAML Configuration"
3. **Identifier (Entity ID)**: Paste the *ZITADEL Metadata URL*.
4. **Reply URL (Assertion Consumer Service URL)**: Paste the *ZITADEL ACS Login Form URL*
5. **Sign on URL**: Paste the *ZITADEL ACS Login Form URL*
6. **Logout URL**: Paste the *ZITADEL Single Logout URL*
7. Optionally, you can enable the "Federated Logout", which will log out the user from Entra ID once they terminate their session in ZITADEL using the OIDC End Session Endpoint.
8. Click Save
<Callout>
You can ignore the ZITADEL ACS Intent API URL for now.
This is relevant if you want to [programmatically sign users in at ZITADEL via a SAML Service Provider](/guides/integrate/login-ui/external-login).
</Callout>
![](../../../../public/img/guides/azure_saml_overview2.png)
## Enable the Microsoft Entra Button in ZITADELs Login Page
Go back to ZITADEL and activate the IdP.
### Activate IdP
<Activate components={props.components} />
![](../../../../public/img/guides/zitadel_activate_azure_saml.png)
### Ensure your Login Policy allows External IDPs
<CustomLoginPolicy components={props.components} />
## Test the setup
<p>
To test the setup, use incognito mode and browse to your login page.
You see a new button which redirects you to Microsoft Entra screen.
</p>
By default, ZITADEL shows what you define in the default settings.
If you overwrite the default settings for an organization, you need to send the organization scope in your auth request.
![](../../../../public/img/guides/zitadel_login_azure_saml.png)
Click **Microsoft Entra**
![](../../../../public/img/guides/microsoft_login.png)
### Add Action to map user attributes
<PrefillAction components={props.components} fields="username, firstname, lastname, displayname, email and email verified" provider="Entra"/>
```js reference
https://github.com/zitadel/actions/blob/main/examples/entra_id_saml_prefil_register_form.js
```
@@ -0,0 +1,68 @@
---
title: Configure a Generic OIDC Identity Provider in ZITADEL
sidebar_label: Generic OIDC
id: generic-oidc
---
import GeneralConfigDescription from './_general_config_description.mdx';
import Intro from './_intro.mdx';
import CustomLoginPolicy from './_custom_login_policy.mdx';
import IDPsOverview from './_idps_overview.mdx';
import Activate from './_activate.mdx';
import PrefillAction from './_prefill_action.mdx';
import TestSetup from './_test_setup.mdx';
<Intro components={props.components} provider="your OIDC Provider"/>
## Open the Generic OIDC Provider Template
<IDPsOverview components={props.components} templates="Generic OIDC"/>
Click on the ZITADEL Callback URL to copy it to your clipboard.
You will have to paste this URL in your external OIDC provider's application configuration later.
![Generic OIDC Provider](../../../../public/img/guides/zitadel_generic_oidc_create_provider.png)
## OIDC Provider Configuration
### Register a new client/application
1. Log in to your external OIDC Provider's dashboard or management console.
2. Navigate to the applications, clients, or integrations section (name may vary depending on your provider).
3. Create a new application or client and select "OIDC" or "OpenID Connect" as the type.
4. Set a name for the application.
5. [Paste the ZITADEL Callback URL you copied before](#open-the-generic-oidc-provider-template) into the "redirect URIs" or "sign-in redirect URIs" field.
6. Save or copy the generated **Client ID** and **Client Secret**.
> The exact steps and terminology may vary for each provider. Refer to your provider's documentation if you don't see these options.
## ZITADEL Configuration
1. Return to [the Generic OIDC Provider template in ZITADEL](#open-the-generic-oidc-provider-template).
2. Enter the **Client ID** and **Client Secret** from your OIDC provider's application.
3. Give the provider a name (e.g., "My OIDC Provider"). This name will appear as a button on the ZITADEL login screen.
4. Enter the **Issuer URL** of your OIDC provider (e.g., `https://your-provider.example.com`).
You can optionally configure the following settings. ZITADEL provides sensible defaults if you leave these unchanged.
**Scopes**: The scopes define which information ZITADEL will request from the provider. `openid`, `profile`, and `email` are pre-filled.
This information is used to create or update the user within ZITADEL.
ZITADEL ensures that at least the `openid` scope is always sent.
**Use PKCE**: If enabled, Proof Key for Code Exchange (PKCE) will be used to secure the authorization code flow in addition to the client secret.
<GeneralConfigDescription components={props.components} provider_account="OIDC provider account" />
### Activate IdP
<Activate components={props.components} />
![Activate the OIDC Provider](../../../../public/img/guides/zitadel_activate_okta.png)
### Ensure your Login Policy allows External IDPs
<CustomLoginPolicy components={props.components} />
## Test the setup
<TestSetup components={props.components} loginscreen="your OIDC provider login"/>
@@ -0,0 +1,90 @@
---
title: Configure GitHub as an Identity Provider in ZITADEL
sidebar_label: GitHub
---
import GeneralConfigDescription from './_general_config_description.mdx';
import Intro from './_intro.mdx';
import CustomLoginPolicy from './_custom_login_policy.mdx';
import IDPsOverview from './_idps_overview.mdx';
import Activate from './_activate.mdx';
import TestSetup from './_test_setup.mdx';
import UnlinkedOAuth from './_unlinked_oauth.mdx';
import PrefillAction from './_prefill_action.mdx';
<Intro components={props.components} provider="GitHub"/>
## Open the GitHub Identity Provider Template
<IDPsOverview components={props.components} templates="GitHub or GitHub Enterprise Server"/>
Click on the ZITADEL Callback URL to copy it to your clipboard.
You will have to paste it to the GitHub OAuth application later.
![](../../../../public/img/guides/zitadel_github_create_provider.png)
## GitHub Configuration
### Register a new application
1. Create a new OAuth application
- For **GitHub** browse to the [Register a new OAuth application](https://github.com/settings/applications/new). You can find this link withing [Settings](https://github.com/settings/profile) - [Developer Settings](https://github.com/settings/apps) - - [OAuth Apps](https://github.com/settings/developers).
- For **GitHub Enterprise** go to your GitHub Enterprise home page and then to Settings - Developer Settings - OAuth Apps - Register a new application/New OAuth App
2. Fill in the application name and homepage URL.
3. [Paste the ZITADEL Callback URL you copied before](#open-the-github-identity-provider-template) to the Authorization callback URL field.
4. Click "Register application".
![](../../../../public/img/guides/github_oauth_app_registration.png)
### Client ID and secret
After clicking "Register application", you see the detail page of the application you have just created.
Copy the client ID directly from the detail page.
Generate a new secret by clicking "Generate new client secret".
Make sure to save the secret, as you will not be able to show it again.
![](../../../../public/img/guides/github_oauth_client_id_secret.png)
## ZITADEL Configuration
Go back [to the GitHub provider template you opened before in ZITADEL](#open-the-github-identity-provider-template).
Add the [client ID and secret you created before when you registered your GitHub OAuth application](#client-id-and-secret).
You can optionally configure the following settings.
A useful default will be filled if you don't change anything.
**Scopes**: The scopes define which scopes will be sent to the provider, `openid`, `profile`, and `email` are prefilled.
This information is used to create and/or update the user within ZITADEL.
ZITADEL ensures that at least the `openid`-scope is always sent.
<GeneralConfigDescription components={props.components} provider_account="GitHub account" />
### Activate IdP
<Activate components={props.components} />
![](../../../../public/img/guides/zitadel_activate_github.png)
### Ensure your Login Policy allows External IDPs
<CustomLoginPolicy components={props.components} />
## Test the setup
<TestSetup components={props.components} loginscreen="your GitHub login"/>
![](../../../../public/img/guides/zitadel_login_github.png)
![](../../../../public/img/guides/github_login.png)
<UnlinkedOAuth components={props.components} provider="GitHub"/>
![](../../../../public/img/guides/zitadel_login_external_not_found_registration.png)
## Optional: Add ZITADEL action to autofill userdata
<PrefillAction components={props.components} fields="firstname and lastname" provider="GitHub"/>
```js reference
https://github.com/zitadel/actions/blob/main/examples/github_identity_provider.js
```
@@ -0,0 +1,88 @@
---
title: Configure GitLab as an Identity Provider in ZITADEL
sidebar_label: GitLab
---
import GeneralConfigDescription from './_general_config_description.mdx';
import Intro from './_intro.mdx';
import CustomLoginPolicy from './_custom_login_policy.mdx';
import IDPsOverview from './_idps_overview.mdx';
import Activate from './_activate.mdx';
import TestSetup from './_test_setup.mdx';
import UnlinkedOAuth from './_unlinked_oauth.mdx';
import PrefillAction from './_prefill_action.mdx';
<Intro components={props.components} provider="GitLab"/>
## Open the GitLab Identity Provider Template
<IDPsOverview components={props.components} templates="GitLab or GitLab Self Hosted"/>
Click on the ZITADEL Callback URL to copy it to your clipboard.
You will have to paste it to the GitLab Application later.
![](../../../../public/img/guides/zitadel_gitlab_create_provider.png)
## GitLab Configuration
### Register a new application
1. Login to [gitlab.com](https://gitlab.com) or to your GitLab self-hosted instance
2. Select [Edit Profile](https://gitlab.com/-/profile)
3. Click on [Applications](https://gitlab.com/-/profile/applications) in the side navigation
4. Fill in the application name
5. [Paste the ZITADEL Callback URL you copied before](#open-the-gitlab-identity-provider-template) to the Redirect URI field.
![](../../../../public/img/guides/gitlab_app_registration.png)
### Client ID and secret
After clicking "Save application", you will see the detail page of the application you have just created.
To be able to connect GitLab to ZITADEL you will need a client ID and a client secret.
Save the ID and the Secret, you will not be able to copy the secret again, if you lose it you have to generate a new one.
![](../../../../public/img/guides/gitlab_app_id_secret.png)
## ZITADEL Configuration
Go back [to the GitLab provider template you opened before in ZITADEL](#open-the-gitlab-identity-provider-template).
Add the [client ID and secret you created before when you registered your GitLab application](#client-id-and-secret).
You can optionally configure the following settings.
A useful default will be filled if you don't change anything.
**Scopes**: The scopes define which scopes will be sent to the provider, `openid`, `profile`, and `email` are prefilled.
This information will be taken to create/update the user within ZITADEL.
ZITADEL ensures that at least the `openid`-scope is always sent.
<GeneralConfigDescription components={props.components} provider_account="GitLab account" />
### Activate IdP
<Activate components={props.components} />
![](../../../../public/img/guides/zitadel_activate_gitlab.png)
### Ensure your Login Policy allows External IDPs
<CustomLoginPolicy components={props.components} />
## Test the setup
<TestSetup components={props.components} loginscreen="your GitLab login"/>
![](../../../../public/img/guides/zitadel_login_gitlab.png)
![](../../../../public/img/guides/gitlab_login.png)
<UnlinkedOAuth components={props.components} provider="GitLab"/>
![](../../../../public/img/guides/zitadel_login_external_not_found_registration.png)
## Optional: Add ZITADEL action to autofill userdata
<PrefillAction components={props.components} fields="firstname and lastname" provider="GitLab"/>
```js reference
https://github.com/zitadel/actions/blob/main/examples/gitlab_identity_provider.js
```
@@ -0,0 +1,74 @@
---
title: Configure Google as an Identity Provider in ZITADEL
sidebar_label: Google
---
import GeneralConfigDescription from './_general_config_description.mdx';
import Intro from './_intro.mdx';
import CustomLoginPolicy from './_custom_login_policy.mdx';
import IDPsOverview from './_idps_overview.mdx';
import Activate from './_activate.mdx';
import TestSetup from './_test_setup.mdx';
import { ResponsivePlayer } from "@/components/player";
<Intro components={props.components} provider="Google"/>
<ResponsivePlayer controls url='https://www.youtube.com/watch?v=wg-ee-EnHdE' />
## Open the Google Identity Provider Template
<IDPsOverview components={props.components} templates="Google"/>
Click on the ZITADEL Callback URL to copy it to your clipboard.
You will have to paste it in the Google Cloud Platform later.
![](../../../../public/img/guides/zitadel_google_create_provider.png)
## Google Configuration
### Register a new client
1. Go to the Google Cloud Platform and choose your project: [https://console.cloud.google.com/apis/credentials](https://console.cloud.google.com/apis/credentials)
2. Click on "+ CREATE CREDENTIALS" and choose "OAuth client ID"
3. Choose "Web application" as application type and give a name
4. [Paste the ZITADEL Callback URL you copied before](#open-the-google-identity-provider-template) into the authorized redirect URIs
![](../../../../public/img/guides/google_oauth_app_registration.png)
### Client ID and secret
You will need the Client ID and Client secret to configure the Google Identity Provider in ZITADEL.
![](../../../../public/img/guides/google_client_id_secret.png)
## ZITADEL Configuration
Go back [to the Google provider template you opened before in ZITADEL](#open-the-google-identity-provider-template).
Add the [client ID and secret created before on your Google App](#client-id-and-client-secret).
You can optionally configure the following settings.
A useful default will be filled if you don't change anything.
**Scopes**: The scopes define which scopes will be sent to the provider, `openid`, `profile`, and `email` are prefilled.
This information will be taken to create/update the user within ZITADEL.
ZITADEL ensures that at least the `openid`-scope is always sent.
<GeneralConfigDescription components={props.components} provider_account="Google account" />
### Activate IdP
<Activate components={props.components} />
![](../../../../public/img/guides/zitadel_activate_google.png)
### Ensure your Login Policy allows External IDPs
<CustomLoginPolicy components={props.components} />
## Test the setup
<TestSetup components={props.components} loginscreen="your Google login"/>
![](../../../../public/img/guides/zitadel_login_google.png)
![](../../../../public/img/guides/google_login.png)
@@ -0,0 +1,148 @@
---
title: Let Users Login with Preferred Identity Provider
sidebar_label: Login users with SSO
---
## External Identity Providers and SSO authentication
An **Identity Provider (IdP)** is a service that creates and maintains identity information and then provides authentication services to your applications. Incorporating SSO (Single Sign-On) authentication allows users to access multiple services with a single set of credentials, enhancing user convenience and security. As you develop your application, you want to give users the freedom to choose an Identity Provider they use to sign in to your application, despite having your own IdP (e.g., ZITADEL). Many users already have trusted accounts with popular IdPs like Google, GitHub, or LinkedIn, making these services effective identity brokers in the SSO landscape. Or it could be an IdP they’re already using in the workplace, such as EntraID or Auth0, which supports SSO authentication. These can be identified as *External Identity Providers*. Allowing them to use these accounts for signing into your application means they don’t need to create new usernames and passwords, thereby significantly enhancing their convenience and the likelihood they’ll engage with your product.
Using external IdPs can also improve security and user trust in your application. These major IdPs have robust security measures in place, reducing the burden on your application to manage secure passwords. Furthermore, by leveraging external IdPs as part of an SSO framework, you're aligning with a user-centric approach, prioritizing their preferences and simplifying their access to your services.
However, integrating these external IdPs with your system requires a clear understanding of the connection process with your IdP, acting as an identity broker to facilitate SSO authentication . This ensures that you can offer this flexibility without compromising on security or user experience. Integrating external IdPs alongside your own offers the best of both worlds: the ease and security of established accounts through SSO and the tailored experience of your application.
## Where ZITADEL fits in
ZITADEL positions itself as the central hub or the identity broker in the interaction between your application and various external IdPs. It handles the orchestration of authentication requests and manages the seamless flow of identity verification between your application and the chosen IdP. This is achieved through a process known as federation, where ZITADEL acts as a federated identity provider, integrating IdPs via authentication protocols like OpenID Connect and SAML.
## Adding external identity providers to your application
With ZITADEL, you can enhance your application's accessibility by integrating social login IdPs such as Google or other IdPs such as EntraID (formerly known as AzureAD) and Auth0. For organizations with bespoke identity solutions, ZITADEL supports integration with custom-built IdPs that adhere to OpenID Connect or SAML protocols. By default, ZITADEL can serve as the primary user store for your applications. This centralizes user management and simplifies the authentication process, allowing users to sign in with their email and password, or via the external IdPs you've integrated.
ZITADEL excels in B2B scenarios by offering the flexibility to configure different IdPs for distinct customers, enhancing ease of use and customization. For instance, you could have Customer A utilize EntraID for authentication, while Customer B uses Okta. This versatility enables you to tailor authentication solutions to meet the specific needs of each customer, streamlining their access while maintaining a centralized management system.
## The advantages of using ZITADEL
The benefits of integrating ZITADEL for managing external IdPs are multiple:
- **No need for custom authentication code**: Your application only needs to interact with ZITADEL, which handles all communications with external IdPs. This abstraction saves you from the complexity of direct integration with multiple IdPs.
- **Unified protocol handling**: ZITADEL abstracts away the specific protocols used by different IdPs. Your application communicates with ZITADEL using a standard protocol (e.g., OpenID Connect), while ZITADEL takes care of the rest.
- **Centralized user management**: All user profiles are managed within ZITADEL, allowing for a unified view of user identities regardless of the IdP used for authentication.
- **Dynamic profile synchronization**: When users update their profiles on an external IdP, those changes are reflected in ZITADEL at the next login, ensuring that user data remains current.
- **Simplified account linking**: ZITADEL can link identities from multiple IdPs to a single user profile, facilitating a cohesive user experience across different authentication methods.
## The user journey
- **Integration with external identity providers**: ZITADEL supports integrating a variety of external identity providers, including social logins like Google or GitHub, as well as custom IdPs that use OpenID Connect or SAML protocols.
- **Initiation of sign-in process**: Users select to sign in with an external IdP within your application.
- **Redirection to ZITADEL**: The application redirects the user to ZITADEL, which guides them to their chosen external IdP for authentication.
- **User authentication**: After successfully logging in at the external IdP site, users are redirected back to the application through ZITADEL, bringing along authentication tokens and profile information.
- **Account linking for new users**: If the user's identity from the external IdP does not exist in ZITADEL, they're presented with two options before moving forward:
- **Create a new account**: Choosing this option creates a new ZITADEL account linked to the external IdP.
- **Linking with an existing local account**: Users have the option to link their new external identity to an existing local account in ZITADEL, enabling future logins with either their local account or the external IdP.
- **Profile pre-filling from external IdP**: ZITADEL uses information from the external IdP to pre-fill the user's profile, simplifying the account creation or linking process.
- **User option to update profile**: Users can review and, if necessary, update their pre-filled profile information.
- **Session creation and access granting**: After the account is created or linked and the profile is set, the application grants access by creating a session for the user based on their authenticated identity.
## Setting up external identity providers in ZITADEL
In ZITADEL, you have the flexibility to link an external Identity Provider (IdP) to your entire instance, making it the default option for all organizations within your instance, or to connect it exclusively to a specific organization. This setup allows organization members to leverage similar capabilities in self-service if permitted.
### Adjusting the custom login policy
The login policy can be set as a default at the instance level and can be customized for each organization. The configuration process varies slightly depending on your focus:
- **For default settings**, navigate to: `${CUSTOM_DOMAIN}/ui/console/instance?id=general`
- **For specific organization settings**, select the organization from the menu and visit: `${CUSTOM_DOMAIN}/ui/console/org-settings?id=login`
Once in the settings:
- Access the **Login Behavior and Security** section to modify your login policy. Here, ensure you enable the option for **External IDP Allowed**.
![](../../../../public/img/guides/zitadel_allow_external_idp.png)
### Configuring IdP Providers
Access the settings page of your instance or the specific organization and select **Identity Providers**.
The ZITADEL Management Console will display a list of all the IdPs you've configured, along with available provider templates. Selecting any listed IdP will guide you through the process of configuring that specific Identity Provider.
![](../../../../public/img/guides/zitadel_identity_provider_overview.png)
## Available guides
In the guides below, some of which utilize the Generic OIDC or SAML templates for configuration, you'll learn how to configure and set up your preferred external Identity Provider (IdP) in ZITADEL.
- [Google](./google)
- [Entra ID (OIDC)](./azure-ad-oidc)
- [Entra ID SAML](./azure-ad-saml)
- [GitHub](./github)
- [GitLab](./gitlab)
- [Apple](./apple)
- [LDAP](./ldap)
- [Local OpenLDAP](./openldap.mdx)
- [OKTA generic OIDC](./okta-oidc)
- [OKTA SAML](./okta_saml)
- [Keycloak generic OIDC](./keycloak)
- [MockSAML](./mocksaml)
- [JWT IdP](./jwt_idp)
### Configuring IdPs without predefined templates
If ZITADEL doesn't offer a specific template for your Identity Provider (IdP) and your IdP is fully compliant with OpenID Connect (OIDC), you have the option to use the generic OIDC provider configuration.
For those utilizing a SAML Service Provider, the SAML Service Provider option is available. You can learn how to set up a SAML Service Provider with our [MockSAML example](/guides/integrate/identity-providers/mocksaml).
Should you wish to transition from a generic OIDC provider to Entra ID (formerly Azure Active Directory) or Google, consider following this [guide](/guides/integrate/identity-providers/migrate).
## Key settings on the templates
When configuring external IdP templates in ZITADEL, several common settings enable customized integration to suit your application's authentication flow. Below is a generic explanation of these settings:
- **Scopes**: Specifies the permissions your application requests from the user's account on the external IdP. Common scopes include `openid`, `profile`, and `email`, essential for accessing basic user information for authentication and account management in ZITADEL. You can specify additional scopes based on your application's requirements and the information needed from the external IdP.
- **Automatic creation**: When enabled, this allows ZITADEL to automatically create a new user account if someone logs in with their external IdP credentials and no corresponding account exists in ZITADEL. This facilitates a smooth user onboarding experience by eliminating the need for manual account creation.
- **Automatic update**: This feature, when activated, allows ZITADEL to automatically update a user's profile information whenever changes are detected in the user's account on the external IdP. For example, if a user changes their last name in their Google or Microsoft account, ZITADEL will reflect this update in the user's account upon their next login.
- **Account creation allowed (manually)**: Determines whether new user accounts can be created in ZITADEL through the external IdP authentication process. Enabling this setting is crucial for allowing users who are new to your application to register and create accounts seamlessly via their existing external IdP accounts. However, if you rely on the **automatic creation** and want to prevent users to manually create their accounts or edit information during the automatic process, you need to disable this option.
- **Account linking allowed (manually)**: Enables existing ZITADEL accounts to be linked with identities from external IdPs. It requires that a linkable ZITADEL account already exists for the user attempting to log in with an external IdP. Account linking is beneficial for users who wish to associate multiple login methods with their ZITADEL account, providing flexibility and convenience in how they access your application. However, if you rely on an **automatic linking option** and want to prevent users to manually link their accounts, you need to disable this option.
- **Automatic linking options**: Enables existing ZITADEL accounts to be linked with identities from external IdPs. If not disabled, ZITADEL will check for an existing account with the configured criteria (username or email) and prompt the user to link the account.
## Configure external IdPs at the organization level or on the default settings
Deciding whether to configure an external Identity Provider (IdP) at the organization level or in the default settings in ZITADEL depends on the scope of access and management you intend to provide. Here’s when to choose each option:
### Create an external IdP on an organization
- **Targeted access control**: When you want to allow authentication through the external IdP specifically for users of a particular organization. This is useful for businesses that manage multiple organizations within ZITADEL and require distinct authentication strategies for each.
- **Customized authentication flow**: If different organizations have unique requirements or policies around user authentication, configuring an IdP at the organization level allows for customized authentication flows that cater to the specific needs of each organization.
- **Delegated administration**: Enabling organizations to manage their own IdPs empowers them to administer their authentication mechanisms independently. This is beneficial in scenarios where organizations have the autonomy to choose their preferred IdPs or when they manage their user base directly.
### Create an external IdP as default
- **Unified authentication strategy**: When you aim to provide a consistent authentication experience across all organizations within your ZITADEL instance. Configuring an IdP in the default settings applies the same authentication mechanism universally, simplifying management and user experience.
- **Centralized management**: Setting up an IdP in the default settings is ideal for scenarios where a single administrative body oversees user authentication across all organizations. This approach centralizes the management of external IdPs, making it easier to maintain and update authentication policies.
- **Broad access needs**: If all users, regardless of their organization, require access to external IdPs for authentication, configuring the IdP in the default settings ensures that these options are available universally. This is particularly useful for platforms that serve a wide range of users with common access requirements.
## References
- [Identity brokering in ZITADEL](/concepts/features/identity-brokering)
- [The ZITADEL API reference for managing external IdPs](/reference/api/admin)
- [Handle external logins in a custom login UI](/guides/integrate/login-ui/external-login)
@@ -0,0 +1,161 @@
---
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:
![](../../../../public/img/guides/jwt_idp.png)
**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 configuration
- Ensure the JWT is not expired
@@ -0,0 +1,76 @@
---
title: Configure Keycloak as an Identity Provider in ZITADEL
sidebar_label: Keycloak generic OIDC
id: keycloak
---
import GeneralConfigDescription from './_general_config_description.mdx';
import Intro from './_intro.mdx';
import CustomLoginPolicy from './_custom_login_policy.mdx';
import IDPsOverview from './_idps_overview.mdx';
import Activate from './_activate.mdx';
import TestSetup from './_test_setup.mdx';
<Intro components={props.components} provider="Keycloak"/>
## Open the Generic OIDC Provider Template
<IDPsOverview components={props.components} templates="Generic OIDC"/>
Click on the ZITADEL Callback URL to copy it to your clipboard.
You will have to paste it in the Keycloak Client later.
![](../../../../public/img/guides/zitadel_generic_oidc_create_provider.png)
## Keycloak Configuration
### Register a new client
1. Login to your Keycloak account and go to the clients list: `$KEYCLOAK-DOMAIN/auth/admin/$REALM/console/#/$REALM/clients`
2. Click on "Create Client"
3. Choose OpenID Connect as Client Type and give your client an ID
4. Enable Client authentication and the standard flow and direct access grants as authentication flow
5. [Paste the ZITADEL Callback URL you copied before](#open-the-generic-oidc-provider-template) to the redirect URIs
6. Go to the credentials tab and copy the secret
![](../../../../public/img/guides/keycloak_add_client.png)
![](../../../../public/img/guides/keycloak_client_secret.png)
## ZITADEL configuration
1. Go back [to the Generic OIDC Provider template you opened before in ZITADEL](#open-the-generic-oidc-provider-template).
2. Add the [client ID and secret created before on your Keycloak Client](#register-a-new-client).
3. Give the provider a name, e.g. Keycloak. This name will be displayed on the login screen button.
4. Add the issuer URL of your Keycloak realm with the path /auth/realms/$REALM, e.g. `https://lemur-0.cloud-iam.com/auth/realms/acme`
You can optionally configure the following settings.
A useful default will be filled if you don't change anything.
**Scopes**: The scopes define which scopes will be sent to the provider, `openid`, `profile`, and `email` are prefilled.
This information will be taken to create/update the user within ZITADEL.
ZITADEL ensures that at least the `openid`-scope is always sent.
**Use PKCE**: If enabled, the provider will use Proof Key for Code Exchange (PKCE) to secure the authorization code flow
in addition to the client secret.
<GeneralConfigDescription components={props.components} provider_account="Keycloak account" />
![](../../../../public/img/guides/zitadel_keycloak_create_provider.png)
### Activate IdP
<Activate components={props.components} />
![](../../../../public/img/guides/zitadel_activate_keycloak.png)
### Ensure your Login Policy allows External IDPs
<CustomLoginPolicy components={props.components} />
## Test the setup
<TestSetup components={props.components} loginscreen="your Keycloak login"/>
![](../../../../public/img/guides/zitadel_login_keycloak.png)
![](../../../../public/img/guides/keycloak_login.png)
@@ -0,0 +1,76 @@
---
title: Configure LDAP as an Identity Provider in ZITADEL
sidebar_label: LDAP
---
import Intro from './_intro.mdx';
import CustomLoginPolicy from './_custom_login_policy.mdx';
import IDPsOverview from './_idps_overview.mdx';
import GeneralConfigDescription from './_general_config_description.mdx';
import Activate from './_activate.mdx';
import TestSetup from './_test_setup.mdx';
import HowLDAPIDPWorks from './_how_ldap_idp_works.mdx'
<Intro components={props.components} provider="an LDAP server"/>
## How it works
<HowLDAPIDPWorks components={props.components} />
## ZITADEL Configuration
### Go to the IdP Providers Overview
<IDPsOverview components={props.components} templates="Active Directory / LDAP"/>
### Create a new LDAP Provider
Fill in the following fields in the LDAP template.
<Callout type="warn">
We highly recommend to use LDAPS or StartTLS enable servers.
Otherwise, your users passwords are sent in clear text through the wire.
</Callout>
**Name**: Name of the identity provider
**Servers**: List of servers in a format of "schema://host:port", as example "ldap://localhost:389". If possible, replace "ldap" with "ldaps" with the corresponding port.
**BaseDN**: BaseDN which will be used with each request to the LDAP server
**BindDn** and **BindPassword**: BindDN and password used to connect to the LDAP for the SearchQuery, should be an admin or user with enough permissions to search for the users to login.
**Userbase**: Base used for the user, normally "dn" but can also be configured.
**User filters**: Attributes of the user which are "or"-joined in the query for the user, used value is the input of the loginname, for example if you try to login with [email protected] and filters "uid" and "email" the resulting SearchQuery contains "(|([email protected])([email protected]))"
**User Object Classes**: ObjectClasses which are "and"-joined in the SearchQuery and the user has to have in the LDAP.
**LDAP Attributes**: Mapping of LDAP attributes to ZITADEL attributes, the ID attributes is required, the rest depends on usage of the identity provider
**StartTLS**: If this setting is enabled after the initial connection ZITADEL tries to build a TLS connection. If your LDAP server doesn't support LDAPS, at least it should support StartTLS.
**Timeout**: If this setting is set all connection run with a set timeout, if it is 0s the default timeout of 60s is used.
<GeneralConfigDescription components={props.components} provider_account="LDAP user" />
![](../../../../public/img/guides/zitadel_ldap_create_provider.png)
### Activate IdP
<Activate components={props.components} />
![](../../../../public/img/guides/zitadel_activate_ldap.png)
### Ensure your Login Policy allows External IDPs
<CustomLoginPolicy components={props.components} />
## Test the setup
<TestSetup components={props.components} loginscreen="ZITADELs LDAP login"/>
![](../../../../public/img/guides/zitadel_login_ldap.png)
![](../../../../public/img/guides/zitadel_login_ldap_input.png)
@@ -0,0 +1,82 @@
---
title: Configure LinkedIn as an OAuth Identity Provider in ZITADEL
sidebar_label: LinkedIn generic OAuth
id: linkedin-oauth
---
import GeneralConfigDescription from './_general_config_description.mdx';
import Intro from './_intro.mdx';
import CustomLoginPolicy from './_custom_login_policy.mdx';
import IDPsOverview from './_idps_overview.mdx';
import GenericOAuth from './_generic_oauth.mdx';
import Activate from './_activate.mdx';
import PrefillAction from './_prefill_action.mdx';
import TestSetup from './_test_setup.mdx';
<Intro components={props.components} provider="LinkedIn"/>
## LinkedIn Configuration
### Register a new client
1. Go to the LinkedIn Developer console and create a new App: https://www.linkedin.com/developers/apps/new
2. Add your App Name, your Company Page and a Logo
3. Add "Sign In with LinkedIn using OpenID Connect" by clicking "Request access"
4. Go to the Auth Settings of the App and add the following URL to the "Authorized redirect URLs"
- Login V1: `${CUSTOM_DOMAIN}/ui/login/login/externalidp/callback`
- Example redirect url for the domain `https://acme.zitadel.cloud` would look like this: `https://acme.zitadel.cloud/ui/login/login/externalidp/callback`
- Login V2: `${LOGINGV2_DOMAIN}/idps/callback`
- In this case the url would look like this: `https://acme.zitadel.cloud/idps/callback`
5. Verify the app as your company
6. In the Auth - OAuth 2.0 scopes section you should see `openid`, `profile` and `email` listed
7. Save Client ID and Primary Client Secret from the Application credentials
![](../../../../public/img/guides/linkedin_add_app.png)
## ZITADEL Configuration
### Add custom login policy
<CustomLoginPolicy components={props.components} />
### Go to the IdP Providers Overview
<IDPsOverview components={props.components} templates="Generic OAuth"/>
### Create a new Generic OAuth Provider
<GenericOAuth components={props.components}
name=": e.g. LinkedIn"
authendpoint="https://www.linkedin.com/oauth/v2/authorization"
tokenendpoint="https://www.linkedin.com/oauth/v2/accessToken"
userendpoint="https://api.linkedin.com/v2/userinfo"
id="sub"
clientid=": Client id from the application previously created in your LinkedIn account"
clientsecret=": Client Secret from the application previously created in your LinkedIn account"
/>
<GeneralConfigDescription components={props.components} provider_account="LinkedIn account" />
![](../../../../public/img/guides/zitadel_linkedin_create_provider.png)
### Activate IdP
<Activate components={props.components} />
![](../../../../public/img/guides/zitadel_activate_linkedin.png)
## Test the setup
<TestSetup components={props.components} loginscreen="your LinkedIn login"/>
![](../../../../public/img/guides/zitadel_login_linkedin.png)
![](../../../../public/img/guides/linkedin_login.png)
## Optional: Add ZITADEL action to autofill userdata
<PrefillAction components={props.components} fields="firstname, lastname, username, email and email verified" provider="LinkedIn"/>
```js reference
https://github.com/zitadel/actions/blob/main/examples/linkedin_identity_provider.js
```
@@ -0,0 +1,59 @@
---
title: Migrate from Generic Provider to specific Identity Provider
sidebar_label: Migrate IdP
---
## Migrate Generic OIDC Provider
You can migrate from a generic OIDC provider to the following supported templates:
- Entra ID (former Azure Active Directory)
- Google
To migrate, you either use the [Migrate Generic OIDC Identity Provider (Instance)](/reference/api/admin/zitadel.admin.v1.AdminService.MigrateGenericOIDCProvider) or [Migrate Generic OIDC Identity Provider (Organization)](/reference/api/management/zitadel.management.v1.ManagementService.MigrateGenericOIDCProvider) API request.
These calls change the type of the provider and don't delete any linked users.
<Callout title="Linked users will not notice the change and be able to login as usual.">
</Callout>
### Google Configuration
The available configuration is described in [Google Configuration](./google).
### Entra ID Configuration
The available configuration is described in [Entra ID Configuration](./azure-ad-oidc).
## Migrate with Terraform
Please note that you only have to perform this migration if you already have an existing IDP with linked users, that should not loose the connection to the provider.
If that isn't your case please just add a new provider from scratch.
To migrate to a specific provider, you need to follow a few essential steps:
1. Create a desired IDP as Terraform resource for example [Google](https://registry.terraform.io/providers/zitadel/zitadel/latest/docs/resources/idp_google).
2. Remove the old terraform resource from the state as to not destroy the migrated IDP accidentally.
```bash
# terraform state rm *address*
terraform state rm zitadel_idp_oidc.oidc_idp
```
After this command you can also remove the resource from the terraform files, as it is not managed anymore but also not deleted.
3. Make the corresponding API call to [migrate the IDP](./migrate#migrate-generic-oidc-provider), save the ID of the IDP for the import
4. Before applying the Terraform resources again, import the new IDP resource.
```bash
#resource "zitadel_idp_google" "google" {
# name = "Google"
# client_id = "182902..."
# client_secret = "GOCSPX-*****"
# scopes = ["openid", "profile", "email"]
# is_linking_allowed = false
# is_creation_allowed = true
# is_auto_creation = false
# is_auto_update = true
#}
# terraform import zitadel_idp_google.*resource_name* *id*:*client_secret*
terraform import zitadel_idp_google.google 222302827723096428:GOCSPX-*****
```
You have now migrated your provider and you should be able to apply the resource again. There should be no changes and the IDP is maintained by Terraform again.
@@ -0,0 +1,274 @@
---
title: Configure MockSAML as an Identity Provider in ZITADEL
sidebar_label: MockSAML
id: mocksaml
---
import GeneralConfigDescription from './_general_config_description.mdx';
import Intro from './_intro.mdx';
import CustomLoginPolicy from './_custom_login_policy.mdx';
import IDPsOverview from './_idps_overview.mdx';
import Activate from './_activate.mdx';
import TestSetup from './_test_setup.mdx';
import PrefillAction from './_prefill_action.mdx';
<Intro components={props.components} provider="MockSAML"/>
<Callout type="warn">
MockSAML is not intended for any production environment, only for test purposes
</Callout>
## MockSAML
### Download metadata
You can either download the metadata under [https://mocksaml.com/api/saml/metadata?download=true](https://mocksaml.com/api/saml/metadata?download=true) or skip this step and
fill in the URL when creating the SAML Provider in ZITADEL.
## ZITADEL configuration
### Go to the IdP providers overview
<IDPsOverview components={props.components} templates="SAML"/>
### Create a new SAML ServiceProvider
The SAML provider template has everything you need preconfigured.
Add the metadata.xml or the URL to the metadata which are accessible by you ZITADEL instance.
All the necessary configuration is contained in the metadata which has to be exchanged by the ServiceProvider and the IdentityProvider.
<GeneralConfigDescription components={props.components} provider_account="SAML account" />
![](../../../../public/img/guides/zitadel_saml_create_provider.png)
### Download metadata
Normally, you would need to download the ServiceProvider metadata from ZITADEL to upload to the IdentityProvider.
They are available under `https://${CUSTOMDOMAIN}/idps/\{ID of the provider in ZITADEL}/saml/metadata`, but this step can be skipped due to the fact that MockSAML is only for testing purposes.
### Activate IdP
<Activate components={props.components} />
![](../../../../public/img/guides/zitadel_activate_saml.png)
### Ensure your Login Policy allows External IDPs
<CustomLoginPolicy components={props.components} />
## Configure an action to autofill user data
### Required for Login V2
The creation of users in ZITADEL will fail if the required fields to create a user are not set.
**Actions V2** can be used to map the SAML attributes returned by the IDP to the required fields in ZITADEL.
See the [Actions V2 Response Manipulation](/guides/integrate/actions/testing-response-manipulation)
guide for more information on setting up a Target and an Execution. In short,
* Create an Actions V2 Target of type `REST Call`
* Create an Execution of type `Response` on the method `/zitadel.user.v2.UserService/RetrieveIdentityProviderIntent`
The following minimal example modifies the response of `/zitadel.user.v2.UserService/RetrieveIdentityProviderIntent` to set the required fields
for user creation. This example is specific to MockSAML, please adjust the attributes according to your IDP.
``` go
package main
import (
"encoding/json"
"io"
"log"
"net/http"
"github.com/muhlemmer/gu"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/user/v2"
"google.golang.org/protobuf/encoding/protojson"
)
type contextResponse struct {
Request *retrieveIdentityProviderIntentRequestWrapper `json:"request"`
Response *retrieveIdentityProviderIntentResponseWrapper `json:"response"`
}
// RetrieveIdentityProviderIntentRequestWrapper necessary to marshal and unmarshal the JSON into the proto message correctly
type retrieveIdentityProviderIntentRequestWrapper struct {
user.RetrieveIdentityProviderIntentRequest
}
func (r *retrieveIdentityProviderIntentRequestWrapper) MarshalJSON() ([]byte, error) {
data, err := protojson.Marshal(r)
if err != nil {
return nil, err
}
return data, nil
}
func (r *retrieveIdentityProviderIntentRequestWrapper) UnmarshalJSON(data []byte) error {
return protojson.Unmarshal(data, r)
}
// RetrieveIdentityProviderIntentResponseWrapper necessary to marshal and unmarshal the JSON into the proto message correctly
type retrieveIdentityProviderIntentResponseWrapper struct {
user.RetrieveIdentityProviderIntentResponse
}
func (r *retrieveIdentityProviderIntentResponseWrapper) MarshalJSON() ([]byte, error) {
data, err := protojson.Marshal(r)
if err != nil {
return nil, err
}
return data, nil
}
func (r *retrieveIdentityProviderIntentResponseWrapper) UnmarshalJSON(data []byte) error {
return protojson.Unmarshal(data, r)
}
// call HandleFunc to read the response body, manipulate the content and return the response
func call(w http.ResponseWriter, req *http.Request) {
// read the body content
sentBody, err := io.ReadAll(req.Body)
if err != nil {
// if there was an error while reading the body return an error
http.Error(w, "error", http.StatusInternalServerError)
return
}
defer req.Body.Close()
// read the response into the expected structure
request := new(contextResponse)
if err := json.Unmarshal(sentBody, request); err != nil {
http.Error(w, "error", http.StatusInternalServerError)
return
}
// build the response from the received response
resp := request.Response
// manipulate the received response to send back as response
if err = manipulateResponse(resp); err != nil {
http.Error(w, "error modifying response", http.StatusInternalServerError)
return
}
// marshal the response into json
data, err := json.Marshal(resp)
if err != nil {
// if there was an error while marshalling the json
http.Error(w, "error marshaling response", http.StatusInternalServerError)
return
}
// return the manipulated response
w.Write(data)
}
type rawInformation struct {
Attributes struct {
Email []string `json:"email"`
FirstName []string `json:"firstName"`
Id []string `json:"id"`
LastName []string `json:"lastName"`
} `json:"attributes"`
Id string `json:"id"`
}
func manipulateResponse(resp *retrieveIdentityProviderIntentResponseWrapper) error {
if resp == nil || resp.IdpInformation == nil || resp.IdpInformation.RawInformation == nil {
log.Println("missing IDP/User information in the IDP response")
return nil
}
// retrieve raw information from the IDP
var rawInfoBytes []byte
var err error
if rawInfoBytes, err = resp.IdpInformation.RawInformation.MarshalJSON(); err != nil {
return err
}
// NOTE: the raw information struct used here is specific to MockSAML.
// Please adapt this to your IDP.
var rawInfo rawInformation
if err = json.Unmarshal(rawInfoBytes, &rawInfo); err != nil {
return err
}
if len(rawInfo.Attributes.Email) == 0 ||
len(rawInfo.Attributes.FirstName) == 0 ||
len(rawInfo.Attributes.LastName) == 0 {
log.Println("missing required attributes in the IDP response")
return nil
}
// to create a new user.
if resp.AddHumanUser != nil {
// set the required fields to create a new user in Zitadel based on the information returned by the IDP
username := rawInfo.Attributes.Email[0]
resp.IdpInformation.UserName = username
resp.AddHumanUser.Profile = &user.SetHumanProfile{
GivenName: rawInfo.Attributes.FirstName[0],
FamilyName: rawInfo.Attributes.LastName[0],
}
resp.AddHumanUser.Email = &user.SetHumanEmail{
Email: rawInfo.Attributes.Email[0],
Verification: &user.SetHumanEmail_IsVerified{IsVerified: true},
}
resp.AddHumanUser.Username = gu.Ptr(username)
resp.AddHumanUser.IdpLinks = []*user.IDPLink{
{
UserName: username,
UserId: resp.GetIdpInformation().GetUserId(),
IdpId: resp.GetIdpInformation().GetIdpId(),
},
}
}
return nil
}
func main() {
// handle the HTTP call under "/call"
http.HandleFunc("/call", call)
// start an HTTP server with the before defined function to handle the endpoint under "http://localhost:8090"
http.ListenAndServe(":8090", nil)
}
```
### Optional for Login V1 (using Actions V1)
<PrefillAction components={props.components} fields="firstname, lastname, email, display name and username" provider="mockSaml"/>
NOTE: The name of the action should be the same as the function name. In this example, the action should be named `mapSAMLIDPAttrs`
```js
function mapSAMLIDPAttrs(ctx, api) {
if (ctx.v1.externalUser.externalIdpId != "external-idp-id") {
return
}
// the attribute names below represent the ones used by MockSAML, please adjust accordingly for your IDP.
let firstname = ctx.v1.providerInfo.attributes["firstName"];
let lastname = ctx.v1.providerInfo.attributes["lastName"];
let email = ctx.v1.providerInfo.attributes["email"];
let username = ctx.v1.providerInfo.attributes["email"];
if (firstname != undefined) {
api.setFirstName(firstname[0]);
}
if (lastname != undefined) {
api.setLastName(lastname[0]);
}
if (email != undefined) {
api.setEmail(email[0]);
api.setEmailVerified(true);
}
if (username != undefined) {
api.setPreferredUsername(username[0]);
}
}
```
## Test the setup
<TestSetup components={props.components} loginscreen="your SAML login"/>
![](../../../../public/img/guides/zitadel_login_saml.png)
![](../../../../public/img/guides/mocksaml_login.png)
@@ -0,0 +1,81 @@
---
title: Configure OKTA as an OIDC Identity Provider in ZITADEL
sidebar_label: OKTA generic OIDC
id: okta-oidc
---
import GeneralConfigDescription from './_general_config_description.mdx';
import Intro from './_intro.mdx';
import CustomLoginPolicy from './_custom_login_policy.mdx';
import IDPsOverview from './_idps_overview.mdx';
import Activate from './_activate.mdx';
import PrefillAction from './_prefill_action.mdx';
import TestSetup from './_test_setup.mdx';
<Intro components={props.components} provider="OKTA"/>
## Open the Generic OIDC Provider Template
<IDPsOverview components={props.components} templates="Generic OIDC"/>
Click on the ZITADEL Callback URL to copy it to your clipboard.
You will have to paste it in the OKTA application later.
![](../../../../public/img/guides/zitadel_generic_oidc_create_provider.png)
## OKTA Configuration
### Register a new client
1. Login to your OKTA Account and go to the applications list: `OKTA-DOMAIN/admin/apps/active^
2. Click on "Create App Integration" and choose "OIDC - OpenID Connect"
3. Choose Web application as Application type and give a name
4. [Paste the ZITADEL Callback URL you copied before](#open-the-generic-oidc-provider-template) to the Sign-in redirect URIs
5. Save the clientid and client secret
![](../../../../public/img/guides/okta_add_app.png)
## ZITADEL Configuration
1. Go back [to the Generic OIDC Provider template you opened before in ZITADEL](#open-the-generic-oidc-provider-template).
2. Add the [client ID and secret created before on your Web application](#register-a-new-client).
3. Give the provider a name, e.g. OKTA. This name will be displayed on the login screen button.
4. Add the issuer URL of your OKTA account, e.g. `https://trial-1925566.okta.com`
You can optionally configure the following settings.
A useful default will be filled if you don't change anything.
**Scopes**: The scopes define which scopes will be sent to the provider, `openid`, `profile`, and `email` are prefilled.
This information will be taken to create/update the user within ZITADEL.
ZITADEL ensures that at least the `openid`-scope is always sent.
**Use PKCE**: If enabled, the provider will use Proof Key for Code Exchange (PKCE) to secure the authorization code flow
in addition to the client secret.
<GeneralConfigDescription components={props.components} provider_account="OKTA account" />
### Activate IdP
<Activate components={props.components} />
![](../../../../public/img/guides/zitadel_activate_okta.png)
### Ensure your Login Policy allows External IDPs
<CustomLoginPolicy components={props.components} />
## Test the setup
<TestSetup components={props.components} loginscreen="your OKTA login"/>
![](../../../../public/img/guides/zitadel_login_okta.png)
![](../../../../public/img/guides/okta_login.png)
## Optional: Add ZITADEL action to autofill userdata
<PrefillAction components={props.components} fields="firstname, lastname and email verified" provider="Okta"/>
```js reference
https://github.com/zitadel/actions/blob/main/examples/okta_identity_provider.js
```
@@ -0,0 +1,122 @@
---
title: Configure OKTA as a SAML Identity Provider in ZITADEL
sidebar_label: OKTA SAML
id: okta-saml
---
import GeneralConfigDescription from './_general_config_description.mdx';
import Intro from './_intro.mdx';
import CustomLoginPolicy from './_custom_login_policy.mdx';
import IDPsOverview from './_idps_overview.mdx';
import Activate from './_activate.mdx';
import PrefillAction from './_prefill_action.mdx';
import TestSetup from './_test_setup.mdx';
<Intro components={props.components} provider="OKTA"/>
## ZITADEL Configuration
### Go to the IdP Providers Overview
<IDPsOverview components={props.components} templates="SAML"/>
### Create a new SAML Provider
To be able to create the application in OKTA we need the provider id from ZITADEL.
1. Create a new SAML Provider with a name and a random text in the Metadata Xml field.
We will fill that as soon as we have done the configuration in OKTA.
2. Save Configuration
As an alternative you can add the SAML identity provider through the API, either on the default settings or on a specific organization:
- [Add Default SAML Identity Provider](/reference/api/admin/zitadel.admin.v1.AdminService.AddSAMLProvider)
- [Add SAML Identity Provider on Organization](/reference/api/management/zitadel.management.v1.ManagementService.AddSAMLProvider)
![](../../../../public/img/guides/zitadel_okta_saml_provider_empty.png)
After you created the SAML Provider in ZITADEL, you can copy the URLs you need to configure in your OKTA application.
![](../../../../public/img/guides/zitadel_okta_saml_provider_urls.png)
## OKTA Configuration
### Register a new client
1. Log in to your OKTA Account and go to the applications list: `OKTA-DOMAIN/admin/apps/active`
2. Click on "Create App Integration" and choose "SAML 2.0"
3. Give the application a name
4. Click on the ZITADEL URLs that your SAML IDP shows since you created it in ZITADEL and paste them accordingly:
- **Single sign-on URL**: Paste the *ZITADEL ACS Login Form URL*.
- **Audience URI (SP Entity ID)**: Paste the *ZITADEL Metadata URL*
5. Save the configuration
6. Copy the metadata URL from the details
![](../../../../public/img/guides/okta_add_saml_app.png)
### Add Attribute Statements
To send the user data from OKTA to ZITADEL you have to add some attribute mappings in your SAML Settings
You can define the name by yourself, just ensure you use the same later on in the ZITADEL Action we will add.
Add the following three mappings:
| Name | Name format | Value |
| ------------ | ----------- |--------------- |
| givenname | Basic | user.firstName |
| surname | Basic | user.lastName |
| emailaddress | Basic | user.email |
![](../../../../public/img/guides/okta_saml_attribute_mapping.png)
### Assign Users to Application
To allow users to authenticate with that app go to the "Assign" Tab.
1. Click the Assign Button
2. Choose Assign To People
3. Select the users you like to be able to authenticate
![](../../../../public/img/guides/okta_assign_user_to_app.png)
## Finish ZITADEL Configuration
You are now finished with the configuration in OKTA and you can switch back to your identity provider configuration in ZITADEL.
### Add Metadata Xml
Add [the metadata URL you have saved before from OKTA](#register-a-new-client) to the Metadata URL.
As soon as you have saved the provider, and you have a look at the detail you should now see the Metadata Xml field filled.
If you prefer changing the configuration through the API you can update the SAML provider on the default settings or a specific organization:
- [Update Default SAML Identity Provider](/reference/api/admin/zitadel.admin.v1.AdminService.UpdateSAMLProvider)
- [Update SAML Identity Provider on Organization](/reference/api/management/zitadel.management.v1.ManagementService.UpdateSAMLProvider)
![](../../../../public/img/guides/zitadel_okta_saml_provider_filled.png)
You can also fill the optional fields if needed:
<GeneralConfigDescription components={props.components} provider_account="OKTA account" />
### Activate IdP
<Activate components={props.components} />
![](../../../../public/img/guides/zitadel_activate_okta_saml.png)
### Ensure your Login Policy allows External IDPs
<CustomLoginPolicy components={props.components} />
## Test the setup
<TestSetup components={props.components} loginscreen="your OKTA login"/>
![](../../../../public/img/guides/zitadel_login_okta.png)
![](../../../../public/img/guides/okta_login.png)
### Add Action to map user attributes
<PrefillAction components={props.components} fields="username, firstname, lastname, email and email verified" provider="OKTA"/>
```js reference
https://github.com/zitadel/actions/blob/main/examples/okta_saml_prefil_register_form.js
```
@@ -0,0 +1,330 @@
---
title: Configure OneLogin as a SAML Identity Provider in ZITADEL
sidebar_label: OneLogin SAML
id: onelogin-saml
---
import GeneralConfigDescription from './_general_config_description.mdx';
import Intro from './_intro.mdx';
import CustomLoginPolicy from './_custom_login_policy.mdx';
import IDPsOverview from './_idps_overview.mdx';
import Activate from './_activate.mdx';
import TestSetup from './_test_setup.mdx';
<Intro components={props.components} provider="OneLogin"/>
## OneLogin SAML configuration
You need access to a OneLogin admin portal with permission to create and configure SAML applications.
We’ll first configure OneLogin sufficiently to **export IdP metadata**, which we then import into ZITADEL.
After the ZITADEL SAML provider exists, we’ll return to OneLogin and finish the SAML configuration using the ZITADEL URLs.
### Create a new SAML application for ZITADEL
1. Sign in to the **OneLogin admin portal**.
2. Go to **Applications > Applications** (or **Apps > Add Apps**, depending on your UI).
3. Click **Add App**.
4. In **Find Applications**, search for:
- **SAML Test Connector (IdP w/ attr)** (recommended), or
- **SAML Custom Connector (Advanced)** if you prefer a more generic connector.
5. Select the connector and set:
- **Display Name** – for example: `ZITADEL SAML`.
- Any optional icon or description you like.
6. Click **Save** to create the application.
![Image: OneLogin Add Apps screen showing SAML connector selection](../../../../public/img/guides/onelogin-add-apps.png)
### Export OneLogin IdP metadata
Now export OneLogin IdP metadata so ZITADEL can trust OneLogin as an identity provider.
1. In the OneLogin admin portal, open the **ZITADEL SAML** application.
2. To export the full metadata XML:
- In the top-right corner, open the **More Actions** menu.
- Select **SAML Metadata** to download the metadata XML file.
3. Alternatively, you can copy the **Issuer URL**, open it in a browser, and save the XML response.
This URL often serves as an IdP metadata endpoint.
Keep either:
- the **IdP Metadata URL** (Issuer URL that returns XML), or
- the **IdP metadata XML file**,
ready for the ZITADEL configuration steps.
## ZITADEL configuration
### Go to the IdP providers overview
<IDPsOverview components={props.components} templates="SAML"/>
### Create a new SAML Service Provider
The SAML provider template has everything you need preconfigured.
Add the metadata XML or the URL to the metadata which is accessible by your ZITADEL instance.
All the necessary configuration is contained in the metadata which has to be exchanged between the Service Provider and the Identity Provider.
<GeneralConfigDescription components={props.components} provider_account="OneLogin" />
![SAML Provider](../../../../public/img/guides/zitadel_saml_create_provider.png)
After saving, open the newly created **OneLogin SAML provider** in ZITADEL.
In the details view you will find the **ZITADEL Service Provider** URLs, such as:
- **ZITADEL Metadata URL**
- **ZITADEL ACS Login Form URL** (for Login V1 and standard login flows)
- **ZITADEL Single Logout URL**
- **ZITADEL ACS Intent API URL** (for Login V2 and programmatic SAML flows)
For a typical browser-based login using the ZITADEL hosted login V2 you will use the:
- **ZITADEL ACS Intent API URL**, and
- **ZITADEL Metadata URL**.
You’ll need these URLs in the next step when finishing the OneLogin configuration.
### Complete OneLogin configuration with ZITADEL URLs
Now that ZITADEL is configured, return to OneLogin and finish the SAML configuration of the ZITADEL app.
1. In the OneLogin admin portal, open the **ZITADEL SAML** application.
2. Go to the **Configuration** tab (or the section where SAML SP settings such as ACS and Audience are configured).
3. Fill in the SAML settings using the values from the ZITADEL SAML provider:
- **ACS (Consumer) URL / SAML Consumer URL**
Set this to the **ZITADEL ACS Intent API URL**.
- **Recipient** (if present as a separate field)
Set this to the **ZITADEL ACS Intent API URL** as well.
- **Audience / SP Entity ID / SAML Audience**
Set this to the **ZITADEL Metadata URL**.
- **Single Logout URL** (optional but recommended)
Set this to the **ZITADEL Single Logout URL**.
- **ACS (Consumer) URL Validator** (if present)
For testing, you can use a permissive value like `.*`, then tighten it later to match the exact ACS URL.
![Image: OneLogin SAML configuration with ZITADEL URLs](../../../../public/img/guides/onelogin-configuration.png)
4. Save your changes.
> _Optional: Import ZITADEL SP metadata_
> Some OneLogin SAML connectors provide an **Upload Metadata** or **Upload metadata file** option.
> In that case you can:
> - Download the SP metadata XML from the **ZITADEL Metadata URL**, and
> - Upload the XML file in OneLogin instead of manually filling in ACS and Audience.
> This will automatically populate many of the SAML settings and certificates.
5. Assign users or groups to the **ZITADEL SAML** application in OneLogin (for example via the **Users** or **Access** tab), so they can use it for SSO.
### Configure SAML attributes in OneLogin
To provision users correctly in ZITADEL, configure which attributes OneLogin sends in the SAML assertion.
1. In the OneLogin admin portal, open the **ZITADEL SAML** application.
2. Go to the **Parameters** tab.
3. Add or edit parameters so that at least the following are sent and **included in the SAML assertion**:
- `email` → mapped to the user’s email address (for example **Email**).
- `firstName` → mapped to the user’s first name (for example **First Name**).
- `lastName` → mapped to the user’s last name (for example **Last Name**).
4. Ensure the **NameID** is set to a unique identifier (commonly the user’s email address), if your OneLogin connector requires it.
5. Save your changes.
These attributes are later available in ZITADEL’s **IdP information** and can be mapped to ZITADEL user fields via Actions V2.
### Activate IdP
<Activate components={props.components} />
![Activate OneLogin SAML provider in ZITADEL](../../../../public/img/guides/zitadel-activate-onelogin-saml.png)
### Ensure your Login Policy allows External IdPs
<CustomLoginPolicy components={props.components} />
## Configure an action to autofill user data
### Required for Login V2
The creation of users in ZITADEL will fail if the required fields to create a user are not set.
**Actions V2** can be used to map the SAML attributes returned by the IdP to the required fields in ZITADEL.
See the [Actions V2 Response Manipulation](/guides/integrate/actions/testing-response-manipulation)
guide for more information on setting up a Target and an Execution. In short,
* Create an Actions V2 Target of type `REST Call`.
* Create an Execution of type `Response` on the method `/zitadel.user.v2.UserService/RetrieveIdentityProviderIntent`.
The following minimal example modifies the response of `/zitadel.user.v2.UserService/RetrieveIdentityProviderIntent` to set the required fields
for user creation. This is just an example, please adjust the attributes according to your IdP.
``` go
package main
import (
"encoding/json"
"io"
"log"
"net/http"
"github.com/muhlemmer/gu"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/user/v2"
"google.golang.org/protobuf/encoding/protojson"
)
type contextResponse struct {
Request *retrieveIdentityProviderIntentRequestWrapper `json:"request"`
Response *retrieveIdentityProviderIntentResponseWrapper `json:"response"`
}
// RetrieveIdentityProviderIntentRequestWrapper necessary to marshal and unmarshal the JSON into the proto message correctly
type retrieveIdentityProviderIntentRequestWrapper struct {
user.RetrieveIdentityProviderIntentRequest
}
func (r *retrieveIdentityProviderIntentRequestWrapper) MarshalJSON() ([]byte, error) {
data, err := protojson.Marshal(r)
if err != nil {
return nil, err
}
return data, nil
}
func (r *retrieveIdentityProviderIntentRequestWrapper) UnmarshalJSON(data []byte) error {
return protojson.Unmarshal(data, r)
}
// RetrieveIdentityProviderIntentResponseWrapper necessary to marshal and unmarshal the JSON into the proto message correctly
type retrieveIdentityProviderIntentResponseWrapper struct {
user.RetrieveIdentityProviderIntentResponse
}
func (r *retrieveIdentityProviderIntentResponseWrapper) MarshalJSON() ([]byte, error) {
data, err := protojson.Marshal(r)
if err != nil {
return nil, err
}
return data, nil
}
func (r *retrieveIdentityProviderIntentResponseWrapper) UnmarshalJSON(data []byte) error {
return protojson.Unmarshal(data, r)
}
// call HandleFunc to read the response body, manipulate the content and return the response
func call(w http.ResponseWriter, req *http.Request) {
// read the body content
sentBody, err := io.ReadAll(req.Body)
if err != nil {
// if there was an error while reading the body return an error
http.Error(w, "error", http.StatusInternalServerError)
return
}
defer req.Body.Close()
// read the response into the expected structure
request := new(contextResponse)
if err := json.Unmarshal(sentBody, request); err != nil {
http.Error(w, "error", http.StatusInternalServerError)
return
}
// build the response from the received response
resp := request.Response
// manipulate the received response to send back as response
if err = manipulateResponse(resp); err != nil {
http.Error(w, "error modifying response", http.StatusInternalServerError)
return
}
// marshal the response into json
data, err := json.Marshal(resp)
if err != nil {
// if there was an error while marshalling the json
http.Error(w, "error marshaling response", http.StatusInternalServerError)
return
}
// return the manipulated response
w.Write(data)
}
type rawInformation struct {
Attributes struct {
Email []string `json:"email"`
FirstName []string `json:"firstName"`
Id []string `json:"id"`
LastName []string `json:"lastName"`
} `json:"attributes"`
Id string `json:"id"`
}
func manipulateResponse(resp *retrieveIdentityProviderIntentResponseWrapper) error {
if resp == nil || resp.IdpInformation == nil || resp.IdpInformation.RawInformation == nil {
log.Println("missing IdP/User information in the IdP response")
return nil
}
// retrieve raw information from the IdP
var rawInfoBytes []byte
var err error
if rawInfoBytes, err = resp.IdpInformation.RawInformation.MarshalJSON(); err != nil {
return err
}
// NOTE: the raw information struct used here is just an example.
// Please adapt this to your IdP.
var rawInfo rawInformation
if err = json.Unmarshal(rawInfoBytes, &rawInfo); err != nil {
return err
}
if len(rawInfo.Attributes.Email) == 0 ||
len(rawInfo.Attributes.FirstName) == 0 ||
len(rawInfo.Attributes.Id) == 0 ||
len(rawInfo.Attributes.LastName) == 0 {
log.Println("missing required attributes in the IdP response")
return nil
}
// to create a new user.
if resp.AddHumanUser != nil {
// set the required fields to create a new user in ZITADEL based on the information returned by the IdP
resp.IdpInformation.UserName = rawInfo.Attributes.Email[0]
resp.AddHumanUser.Profile = &user.SetHumanProfile{
GivenName: rawInfo.Attributes.FirstName[0],
FamilyName: rawInfo.Attributes.LastName[0],
}
resp.AddHumanUser.Email = &user.SetHumanEmail{
Email: rawInfo.Attributes.Email[0],
Verification: &user.SetHumanEmail_IsVerified{IsVerified: true},
}
resp.AddHumanUser.Username = gu.Ptr(rawInfo.Attributes.Email[0])
resp.AddHumanUser.IdpLinks = []*user.IDPLink{
{
UserName: rawInfo.Attributes.Email[0],
},
}
}
return nil
}
func main() {
// handle the HTTP call under "/call"
http.HandleFunc("/call", call)
// start an HTTP server with the before defined function to handle the endpoint under "http://localhost:8090"
http.ListenAndServe(":8090", nil)
}
```
## Test the setup
<TestSetup components={props.components} loginscreen="your OneLogin login"/>
![Image: OneLogin button on ZITADEL login](../../../../public/img/guides/zitadel-login-onelogin-button.png)
@@ -0,0 +1,232 @@
---
title: Configure Local OpenLDAP as an Identity Provider in ZITADEL
sidebar_label: Local OpenLDAP
---
import Intro from './_intro.mdx';
import CustomLoginPolicy from './_custom_login_policy.mdx';
import IDPsOverview from './_idps_overview.mdx';
import GeneralConfigDescription from './_general_config_description.mdx';
import Activate from './_activate.mdx';
import TestSetup from './_test_setup.mdx';
import HowLDAPIDPWorks from './_how_ldap_idp_works.mdx'
<Callout type="warn">
This guide shows you how you can configure an LDAP server locally.
ZITADEL needs access to the LDAP server, so this won't work in ZITADEL Cloud.
You have to spin up your own local ZITADEL.
The easiest way to do so is [by following the Docker Compose installation guide](/self-hosting/deploy/compose).
</Callout>
<Callout type="caution">
Beware that this example configuration neither supports LDAPS nor StartTLS.
We highly recommend to enable LDAPS or StartTLS in your production setup.
Otherwise, your users passwords are sent in clear text through the wire.
</Callout>
<Intro components={props.components} provider="a local OpenLDAP server"/>
## How it works
<HowLDAPIDPWorks components={props.components} />
## OpenLDAP Configuration
### Basic configuration
You can run OpenLdap via `docker-compose` using the following:
```
version: '2'
networks:
my-network:
driver: bridge
services:
openldap:
image: bitnami/openldap:latest
ports:
- '389:1389'
environment:
- LDAP_ADMIN_USERNAME=admin
- LDAP_ADMIN_PASSWORD=Password1!
- LDAP_USERS=test
- LDAP_PASSWORDS=Password1!
- LDAP_ROOT=dc=example,dc=com
- LDAP_ADMIN_DN=cn=admin,dc=example,dc=com
networks:
- my-network
volumes:
- 'openldap_data:/bitnami/openldap'
volumes:
openldap_data:
driver: local
````
Alternatively, you can run LDAP locally. To run LDAP locally to test it with ZITADEL please refer to [OpenLDAP](https://www.openldap.org/) with [slapd](https://www.openldap.org/software/man.cgi?query=slapd).
For a quickstart guide please refer to their [official documentation](https://www.openldap.org/doc/admin22/quickstart.html).
A basic configuration would be like this
```
#
# See slapd.conf(5) for details on configuration options.
# This file should NOT be world readable.
#
include /usr/local/etc/openldap/schema/core.schema
include /usr/local/etc/openldap/schema/cosine.schema
include /usr/local/etc/openldap/schema/inetorgperson.schema
include /usr/local/etc/openldap/schema/nis.schema
include /usr/local/etc/openldap/schema/misc.schema
# Define global ACLs to disable default read access.
# Do not enable referrals until AFTER you have a working directory
# service AND an understanding of referrals.
#referral ldap://root.openldap.org
pidfile /usr/local/var/run/slapd.pid
argsfile /usr/local/var/run/slapd.args
# Load dynamic backend modules:
modulepath /usr/local/Cellar/openldap/2.4.53/libexec/openldap
moduleload back_mdb.la
moduleload back_ldap.la
# Sample security restrictions
# Require integrity protection (prevent hijacking)
# Require 112-bit (3DES or better) encryption for updates
# Require 63-bit encryption for simple bind
# security ssf=1 update_ssf=112 simple_bind=64
# Sample access control policy:
# Root DSE: allow anyone to read it
# Subschema (sub)entry DSE: allow anyone to read it
# Other DSEs:
# Allow self write access
# Allow authenticated users read access
# Allow anonymous users to authenticate
# Directives needed to implement policy:
# access to dn.base="" by * read
# access to dn.base="cn=Subschema" by * read
# access to *
# by self write
# by users read
# by anonymous auth
#
# if no access controls are present, the default policy
# allows anyone and everyone to read anything but restricts
# updates to rootdn. (e.g., "access to * by * read")
#
# rootdn can always read and write EVERYTHING!
#######################################################################
# MDB database definitions
#######################################################################
database ldif
#maxsize 1073741824
suffix "dc=example,dc=com"
rootdn "cn=admin,dc=example,dc=com"
# Cleartext passwords, especially for the rootdn, should
# be avoid. See slappasswd(8) and slapd.conf(5) for details.
# Use of strong authentication encouraged.
rootpw {SSHA}6FTOTIITpkP9IAf22VjHqu4JisyBmW5A
# The database directory MUST exist prior to running slapd AND
# should only be accessible by the slapd and slap tools.
# Mode 700 recommended.
directory /usr/local/var/openldap-data
# Indices to maintain
#index objectClass eq
```
Which is the default configuration with an admin user under the DN `cn=admin,dc=example,dc=com` and password `Password1!`, BaseDN `"dc=example,dc=com` and database set to `ldif`.
In addition, there are some schemas included which can be used to create the users.
### Example users
For a basic structure and an example user you can use this structure in a `.ldif` file:
```
dn: dc=example,dc=com
dc: example
description: Company
objectClass: dcObject
objectClass: organization
o: Example, Inc.
dn: ou=people, dc=example,dc=com
ou: people
description: All people in organisation
objectclass: organizationalunit
dn: cn=test,ou=people,dc=example,dc=com
objectclass: inetOrgPerson
cn: testuser
sn: test
uid: test
userpassword: {SHA}qUqP5cyxm6YcTAhz05Hph5gvu9M=
mail: [email protected]
description: Person
ou: Human Resources
```
Which in essence creates a user with DN `cn=test,ou=people,dc=example,dc=com`, uid `test` and password `test`.
The user can be applied after OpenLDAP is running with
```bash
ldapadd -x -h localhost -D "cn=admin,dc=example,dc=com" -f example.ldif -w 'Password1!'
```
## ZITADEL Configuration
### Go to the IdP Providers Overview
<IDPsOverview components={props.components} templates="Active Directory / LDAP"/>
### Create a new LDAP Provider
Fill in the template fields with the exact values listed below. The fields are described in the [LDAP guide](./ldap#create-a-new-ldap-provider).
**Name**: OpenLDAP
**Servers**: "ldap://localhost:389"
**BaseDN**: "dc=example,dc=com"
**BindDn**: "cn=admin,dc=example,dc=com"
**BindPassword**: "Password1!"
**Userbase**: "dn"
**User filters**: "uid"
**User Object Classes**: "inetOrgPerson"
**LDAP Attributes**: id attributes = "uid"
**StartTLS**: For this example should be left untouched, if this setting is enabled after the initial connection ZITADEL tries to build a TLS connection.
**Timeout**: Can be left empty, if this setting is set all connection run with a set timeout, if it is 0s the default timeout of 60s is used.
<GeneralConfigDescription components={props.components} provider_account="LDAP user" />
![](../../../../public/img/guides/zitadel_ldap_create_provider.png)
### Activate IdP
<Activate components={props.components} />
![](../../../../public/img/guides/zitadel_activate_ldap.png)
### Ensure your Login Policy allows External IDPs
<CustomLoginPolicy components={props.components} />
## Test the setup
<TestSetup components={props.components} loginscreen="ZITADELs LDAP login"/>
![](../../../../public/img/guides/zitadel_login_ldap.png)
![](../../../../public/img/guides/zitadel_login_ldap_input.png)
@@ -0,0 +1,345 @@
---
title: Configure PingFederate as a SAML Identity Provider in ZITADEL
sidebar_label: PingFederate SAML
id: pingfederate-saml
---
import GeneralConfigDescription from './_general_config_description.mdx';
import Intro from './_intro.mdx';
import CustomLoginPolicy from './_custom_login_policy.mdx';
import IDPsOverview from './_idps_overview.mdx';
import Activate from './_activate.mdx';
import TestSetup from './_test_setup.mdx';
<Intro components={props.components} provider="PingFederate"/>
## PingFederate SAML configuration
You need access to a PingFederate administrative console with permission to create **SP Connections**.
We’ll first configure PingFederate sufficiently to **export IdP metadata**, which we then import into ZITADEL.
After the ZITADEL SAML provider exists, we’ll return to PingFederate and finish the SP configuration using the ZITADEL URLs.
### Create a new SP Connection for ZITADEL
1. Sign in to the **PingFederate admin console**.
2. Go to **Applications > SP Connections**.
3. Select **Create New** (or **Add Connection**).
4. When asked how to create the connection, choose **Manually Configure Connection** (do not import metadata yet).
5. On the **Connection Type** screen, choose:
- **Browser SSO** as the connection type.
- **SAML 2.0** as the protocol.
6. On **General Settings**:
- Set a **Connection Name**, for example: `ZITADEL SAML`.
- Optionally set an ID or description to identify the connection.
### Configure Browser SSO
1. In the new connection wizard, go to **Browser SSO**.
2. Make sure **SAML 2.0** Browser SSO is enabled for this connection.
3. Proceed to **Assertion Creation**:
- Keep the default **NameID** attribute.
- Add additional attributes you want to send to ZITADEL (you need at least `email`, `given_name` (first name of the user), `family_name` (last name of the user), and you can send optional attributes like `display_name`).
4. In **Attribute Mapping**, map the contract attributes to your directory/user store fields.
### Export PingFederate IdP metadata
Now export IdP metadata for this connection so ZITADEL can trust PingFederate.
1. In PingFederate, go to **System > Protocol Metadata > Metadata Export**.
2. Select **SAML 2.0**.
3. Set the **Role** to **IdP** (Identity Provider).
4. Choose to generate **connection-specific metadata** and select the SP Connection you created for ZITADEL.
5. Choose a transfer method:
- **Metadata URL** – PingFederate publishes metadata at a URL you can copy; or
- **File download** – export the metadata as an XML file.
Keep either:
- the **Metadata URL**, or
- the **XML file** contents,
ready for the ZITADEL configuration steps.
## ZITADEL configuration
### Go to the IdP providers overview
<IDPsOverview components={props.components} templates="SAML"/>
### Create a new SAML Service Provider
The SAML provider template has everything you need preconfigured.
Add the metadata.xml or the URL to the metadata which are accessible by your ZITADEL instance.
All the necessary configuration is contained in the metadata which has to be exchanged by the ServiceProvider and the IdentityProvider.
<GeneralConfigDescription components={props.components} provider_account="PingFederate" />
![SAML Provider](../../../../public/img/guides/zitadel_saml_create_provider.png)
After saving, open the newly created PingFederate SAML provider in ZITADEL.
In the details view you will find the **ZITADEL Service Provider** URLs, such as:
- **ZITADEL Metadata URL**
- **ZITADEL ACS Login Form URL** (for Login V1)
- **ZITADEL Single Logout URL**
- **ZITADEL ACS Intent API URL** (for Login V2 and programmatic SAML flows)
You’ll need these in the next step.
### Import ZITADEL metadata into PingFederate (recommended)
Now that ZITADEL is configured, you can let PingFederate **consume ZITADEL’s SP metadata URL**.
This allows PingFederate to:
- Auto-fill the ZITADEL **ACS URL**, **entity ID**, and **SLO URLs**.
- Import and keep up-to-date the **verification/encryption certificates** from the metadata.
#### Create a Partner Metadata URL for ZITADEL
1. In the PingFederate admin console, go to:
**System > Protocol Metadata > Partner Metadata URLs**.
2. Click **Add New URL**.
3. On the **URL** tab:
- **Name** – `ZITADEL SP Metadata` (or similar).
- **URL** – paste the **ZITADEL Metadata URL** you copied from ZITADEL.
- Keep **Validate Metadata Signature** enabled (recommended) unless you have a reason to disable it.
4. Click **Load Metadata**.
5. On the **Certificate Summary** tab, review the certificate information associated with the metadata.
6. On the **Summary** tab, click **Done**, then **Save**.
PingFederate can now use this metadata URL to configure and automatically update the SP connection.
### Import SP metadata into the ZITADEL SP Connection
With the partner metadata URL defined, apply it to your existing SP Connection for ZITADEL.
1. Go to **Applications > SP Connections**.
2. Edit the SP Connection you created for ZITADEL.
3. Go to the **Import Metadata** tab.
4. Select **URL**.
5. From the **Metadata URL** drop-down, select the **ZITADEL SP Metadata** entry you created.
- If you don’t see it, click **Manage Partner Metadata URLs** and verify it exists and loads correctly.
6. Ensure **Enable Automatic Reloading** is checked if you want PingFederate to periodically refresh metadata (recommended).
7. Click **Load Metadata**.
PingFederate will:
- Read the SP metadata from ZITADEL,
- Populate the **ACS URL**, SLO URLs, and other protocol settings for the connection, and
- Import the SP’s **signing/encryption certificates** for signature verification and encryption.
You can review the updated values on:
- **Browser SSO > Protocol Settings** (ACS URL, bindings, SLO URLs), and
- **Credentials > Signature Verification / Encryption** (certificates imported from metadata).
If needed, you can still override specific settings manually, but using the metadata URL is the preferred way to keep endpoints and certificates in sync.
#### If you need to update the SP Connection with ZITADEL URLs
1. In PingFederate, open the **SP Connection** you created for ZITADEL.
2. Go to the **Browser SSO / Protocol Settings** (exact naming may vary by version).
3. Update the SP settings:
- **Assertion Consumer Service (ACS) URL**
Set this to the **ZITADEL ACS Intent API URL** from the SAML provider details.
- **SP Entity ID / Audience / Partner Entity ID**
Set this to the **ZITADEL Metadata URL**.
- **Single Logout (optional)**
If you want SAML Single Logout, configure PingFederate’s logout behavior to call the **ZITADEL Single Logout URL** using the appropriate binding (typically HTTP-Redirect).
4. Save and **activate** the connection changes in PingFederate.
### Activate IdP
<Activate components={props.components} />
![Activate PingFederate SAML provider in ZITADEL](../../../../public/img/guides/zitadel-activate-pingfederate-saml.png)
### Ensure your Login Policy allows External IdPs
<CustomLoginPolicy components={props.components} />
## Configure an action to autofill user data
### Required for Login V2
The creation of users in ZITADEL will fail if the required fields to create a user are not set.
**Actions V2** can be used to map the SAML attributes returned by the IdP to the required fields in ZITADEL.
See the [Actions V2 Response Manipulation](/guides/integrate/actions/testing-response-manipulation)
guide for more information on setting up a Target and an Execution. In short,
* Create an Actions V2 Target of type `REST Call`
* Create an Execution of type `Response` on the method `/zitadel.user.v2.UserService/RetrieveIdentityProviderIntent`
The following minimal example modifies the response of `/zitadel.user.v2.UserService/RetrieveIdentityProviderIntent` to set the required fields
for user creation. This is just an example, please adjust the attributes according to your IdP.
``` go
package main
import (
"encoding/json"
"io"
"log"
"net/http"
"github.com/muhlemmer/gu"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/user/v2"
"google.golang.org/protobuf/encoding/protojson"
)
type contextResponse struct {
Request *retrieveIdentityProviderIntentRequestWrapper `json:"request"`
Response *retrieveIdentityProviderIntentResponseWrapper `json:"response"`
}
// RetrieveIdentityProviderIntentRequestWrapper necessary to marshal and unmarshal the JSON into the proto message correctly
type retrieveIdentityProviderIntentRequestWrapper struct {
user.RetrieveIdentityProviderIntentRequest
}
func (r *retrieveIdentityProviderIntentRequestWrapper) MarshalJSON() ([]byte, error) {
data, err := protojson.Marshal(r)
if err != nil {
return nil, err
}
return data, nil
}
func (r *retrieveIdentityProviderIntentRequestWrapper) UnmarshalJSON(data []byte) error {
return protojson.Unmarshal(data, r)
}
// RetrieveIdentityProviderIntentResponseWrapper necessary to marshal and unmarshal the JSON into the proto message correctly
type retrieveIdentityProviderIntentResponseWrapper struct {
user.RetrieveIdentityProviderIntentResponse
}
func (r *retrieveIdentityProviderIntentResponseWrapper) MarshalJSON() ([]byte, error) {
data, err := protojson.Marshal(r)
if err != nil {
return nil, err
}
return data, nil
}
func (r *retrieveIdentityProviderIntentResponseWrapper) UnmarshalJSON(data []byte) error {
return protojson.Unmarshal(data, r)
}
// call HandleFunc to read the response body, manipulate the content and return the response
func call(w http.ResponseWriter, req *http.Request) {
// read the body content
sentBody, err := io.ReadAll(req.Body)
if err != nil {
// if there was an error while reading the body return an error
http.Error(w, "error", http.StatusInternalServerError)
return
}
defer req.Body.Close()
// read the response into the expected structure
request := new(contextResponse)
if err := json.Unmarshal(sentBody, request); err != nil {
http.Error(w, "error", http.StatusInternalServerError)
return
}
// build the response from the received response
resp := request.Response
// manipulate the received response to send back as response
if err = manipulateResponse(resp); err != nil {
http.Error(w, "error modifying response", http.StatusInternalServerError)
return
}
// marshal the response into json
data, err := json.Marshal(resp)
if err != nil {
// if there was an error while marshalling the json
http.Error(w, "error marshaling response", http.StatusInternalServerError)
return
}
// return the manipulated response
w.Write(data)
}
type rawInformation struct {
Attributes struct {
Email []string `json:"email"`
FirstName []string `json:"firstName"`
Id []string `json:"id"`
LastName []string `json:"lastName"`
} `json:"attributes"`
Id string `json:"id"`
}
func manipulateResponse(resp *retrieveIdentityProviderIntentResponseWrapper) error {
if resp == nil || resp.IdpInformation == nil || resp.IdpInformation.RawInformation == nil {
log.Println("missing IdP/User information in the IdP response")
return nil
}
// retrieve raw information from the IdP
var rawInfoBytes []byte
var err error
if rawInfoBytes, err = resp.IdpInformation.RawInformation.MarshalJSON(); err != nil {
return err
}
// NOTE: the raw information struct used here is just an example.
// Please adapt this to your IdP.
var rawInfo rawInformation
if err = json.Unmarshal(rawInfoBytes, &rawInfo); err != nil {
return err
}
if len(rawInfo.Attributes.Email) == 0 ||
len(rawInfo.Attributes.FirstName) == 0 ||
len(rawInfo.Attributes.Id) == 0 ||
len(rawInfo.Attributes.LastName) == 0 {
log.Println("missing required attributes in the IdP response")
return nil
}
// to create a new user.
if resp.AddHumanUser != nil {
// set the required fields to create a new user in Zitadel based on the information returned by the IdP
resp.IdpInformation.UserName = rawInfo.Attributes.Email[0]
resp.AddHumanUser.Profile = &user.SetHumanProfile{
GivenName: rawInfo.Attributes.FirstName[0],
FamilyName: rawInfo.Attributes.LastName[0],
}
resp.AddHumanUser.Email = &user.SetHumanEmail{
Email: rawInfo.Attributes.Email[0],
Verification: &user.SetHumanEmail_IsVerified{IsVerified: true},
}
resp.AddHumanUser.Username = gu.Ptr(rawInfo.Attributes.Email[0])
resp.AddHumanUser.IdpLinks = []*user.IDPLink{
{
UserName: rawInfo.Attributes.Email[0],
},
}
}
return nil
}
func main() {
// handle the HTTP call under "/call"
http.HandleFunc("/call", call)
// start an HTTP server with the before defined function to handle the endpoint under "http://localhost:8090"
http.ListenAndServe(":8090", nil)
}
```
## Test the setup
<TestSetup components={props.components} loginscreen="your PingFederate login"/>
![Image: PingFederate button on ZITADEL login](../../../../public/img/guides/zitadel-login-pingfederate-button.png)
@@ -0,0 +1,19 @@
---
title: "Integrate"
description: "Integrate your users and application with ZITADEL. In this section you will find resource on how to authenticate your users, configure external identity providers, access the ZITADEL APIs to manage resources, and integrate with third party services and tools."
---
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
import { Card, Cards } from 'fumadocs-ui/components/card';
import { FileText, Folder, Link as LinkIcon } from 'lucide-react';
<Cards>
<Card title="OIDC & OAuth Flows" href="/guides/integrate/login/oidc" icon={<Folder />} />
<Card title="SCIM" href="/guides/manage/user/scim2" icon={<Folder />} />
<Card title="Token Introspection" href="/guides/integrate/token-introspection" icon={<Folder />} />
<Card title="Back Channel Logout" href="/guides/integrate/back-channel-logout" icon={<FileText />} />
<Card title="Build your own Login UI" href="/guides/integrate/login-ui" icon={<Folder />} />
</Cards>
@@ -0,0 +1,12 @@
Now that you have started the registration within ZITADEL, you have to register the credentials in the browser.
This requires a call to the browser api and looks something like the following.
Make sure to send the public key credential creation options you got in the previous request from ZITADEL.
```bash
const credential = await navigator.credentials.create({
publicKey: publicKeyCredentialCreationOptions
});
```
For more information about WebAuthN and registering credential flow, read the following guide:
[Registering a WebAuthN Credentials](https://webauthn.guide/#registration)
@@ -0,0 +1,11 @@
After starting the WebAuthN authentication on the side of ZITADEL you have to challenge the browser.
To do this you need to call the browser API to get the credentials.
Make sure to send the public key credential request options you got from ZITADEL.
```javascript
const credential = await navigator.credentials.get({
publicKey: publicKeyCredentialRequestOptions
});
```
Read the [WebAuthN Guide](https://webauthn.guide/#authentication) for more information about "Authenticating with a WebAuthN Credential".
@@ -0,0 +1,46 @@
Your user has successfully authenticated, and now you ask them if they want to setup MFA to have a more secure account.
When the user starts the configuration, first you want to show them the possible methods.
You can either list it implicitly or call the settings service from ZITADEL to get what is configured on the login settings.
More detailed information about the API: [Get Login Settings Documentation](/reference/api/settings/zitadel.settings.v2.SettingsService.GetLoginSettings)
Request Example:
```bash
curl --request GET \
--url https://${CUSTOM_DOMAIN}/v2/settings/login \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''
```
Response Example:
The relevant part for the list is the second factor and multi-factor list.
```bash
{
"details": {
"sequence": "293",
"changeDate": "2023-03-29T14:16:55.570482Z",
"resourceOwner": "163840776835432705"
},
"settings": {
"allowUsernamePassword": true,
"allowRegister": true,
"allowExternalIdp": true,
"passkeysType": "PASSKEYS_TYPE_ALLOWED",
"passwordCheckLifetime": "864000s",
"externalLoginCheckLifetime": "864000s",
"mfaInitSkipLifetime": "2592000s",
"secondFactorCheckLifetime": "64800s",
"multiFactorCheckLifetime": "43200s",
"secondFactors": [
"SECOND_FACTOR_TYPE_OTP",
"SECOND_FACTOR_TYPE_U2F"
],
"multiFactors": [
"MULTI_FACTOR_TYPE_U2F_WITH_VERIFICATION"
],
"resourceOwnerType": "RESOURCE_OWNER_TYPE_ORG"
}
}
```
@@ -0,0 +1,37 @@
When your user is done using your application and clicks on the logout button, you have to send a request to the terminate session endpoint.
[Terminate Session Documentation](/reference/api/session/zitadel.session.v2.SessionService.DeleteSession)
Sessions can be terminated by either:
- the authenticated user
- a manager, who is granted `session.delete` (e.g. ORG_OWNER) on the authenticated users organisation
- providing the current session_token in the body.
Terminating a session means to delete it.
If you try to read or update the session afterward, you will get an error that the Session does not exist or was terminated.
### Request for authenticated users or managers
Make sure that the provided token is from the authenticated user, resp. the manager:
```bash
curl --request DELETE \
--url https://${CUSTOM_DOMAIN}/v2/sessions/218480890961985793 \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json'
```
### Request with session token
Send the session token in the body of the request:
```bash
curl --request DELETE \
--url https://${CUSTOM_DOMAIN}/v2/sessions/218480890961985793 \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"sessionToken": "blGKerGQPKv8jN21p6E9GB1B-vl6_EyKlvTd5UALu8-aQmjucgZxHSXJx3XMFTwT9_Y3VnbOo3gC_Q"
}'
```
@@ -0,0 +1,79 @@
If you want to build your own select account/account picker, you have to cache the session IDs.
We recommend storing a list of the session Ids with the corresponding session token in a cookie.
The list of session IDs can be sent in the “search sessions” request to get a detailed list of sessions for the account selection.
[Search Sessions Documentation](/reference/api/session/zitadel.session.v2.SessionService.ListSessions)
### Request
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/sessions/search \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"query": {
"offset": "0",
"limit": 100,
"asc": true
},
"queries": [
{
"idsQuery": {
"ids": [
"218380657934467329", "218480890961985793"
]
}
}
]
}'
```
### Response
```bash
{
"details": {
"totalResult": "2",
"processedSequence": "582",
"timestamp": "2023-06-14T05:42:11.644220Z"
},
"sessions": [
{
"id": "218380657934467329",
"creationDate": "2023-06-13T12:56:56.683629Z",
"changeDate": "2023-06-13T12:56:56.724450Z",
"sequence": "574",
"factors": {
"user": {
"verifiedAt": "2023-06-13T12:56:55.440850Z",
"id": "218380659823356328",
"loginName": "[email protected]",
"displayName": "Minnie Mouse"
},
"password": {
"verifiedAt": "2023-06-13T12:56:56.675359Z"
}
}
},
{
"id": "218480890961985793",
"creationDate": "2023-06-14T05:32:38.977954Z",
"changeDate": "2023-06-14T05:42:11.631901Z",
"sequence": "582",
"factors": {
"user": {
"verifiedAt": "2023-06-14T05:32:38.972712Z",
"id": "218380659823356328",
"loginName": "[email protected]",
"displayName": "Minnie Mouse"
},
"password": {
"verifiedAt": "2023-06-14T05:42:11.619484Z"
}
}
}
]
}
```
@@ -0,0 +1,21 @@
Now that you have successfully authenticated in the browser, you can update the session of the user.
Fill the webAuthN checks with the credential assertion data you get from the browser.
More detailed information about the API: [Update Session Documentation](/reference/api/session/zitadel.session.v2.SessionService.SetSession)
Example Request:
```bash
curl --request PATCH \
--url https://${CUSTOM_DOMAIN}/v2/sessions/218480890961985793 \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"checks": {
"webAuthN": {
"credentialAssertionData": {}
}
}
}'
```
@@ -0,0 +1,165 @@
---
title: Support for the Device Authorization Grant in a Custom Login UI
sidebar_label: Device Authorization
---
In case one of your applications requires the [OAuth2 Device Authorization Grant](/guides/integrate/login/oidc/device-authorization) this guide will show you how to implement
this in your application as well as the custom login UI.
The following flow shows you the different components you need to enable OAuth2 Device Authorization Grant for your login.
![](../../../../public/img/guides/login-ui/device-auth-flow.png)
1. Your application makes a device authorization request to your login UI
2. The login UI proxies the request to ZITADEL.
3. ZITADEL parses the request and does what it needs to interpret certain parameters (e.g., organization scope, etc.)
4. ZITADEL returns the device authorization response
5. Your application presents the `user_code` and `verification_uri` or maybe even renders a QR code with the `verification_uri_complete` for the user to scan
6. Your application starts a polling mechanism to check if the user has approved the device authorization request on the token endpoint
7. When the user opens the browser at the verification_uri, he can enter the user_code, or it's automatically filled in, if they scan the QR code
8. Request the device authorization request from the ZITADEL API using the user_code
9. Your login UI allows to approve or deny the device request
10. In case they approved, authenticate the user in your login UI by creating and updating a session with all the checks you need.
11. Inform ZITADEL about the decision:
1. Authorize the device authorization request by sending the session and the previously retrieved id of the device authorization request to the ZITADEL API
2. In case they denied, deny the device authorization from the ZITADEL API using the previously retrieved id of the device authorization request
12. Notify the user that they can close the window now and return to the application.
13. Your applications request to the token endpoint now receives the tokens or an error if the user denied the request.
## Example
Let's assume you host your login UI on the following URL:
```
https://login.example.com
```
## Device Authorization Request
A user opens your application and is unauthenticated, the application will create the following request:
```http
POST /oauth/v2/device_authorization HTTP/1.1
Host: login.example.com
Content-type: application/x-www-form-urlencoded
client_id=170086824411201793&
scope=openid%20email%20profile
```
The request includes all the relevant information for the OAuth2 Device Authorization Grant and in this example we also have some scopes for the user.
You now have to proxy the auth request from your own UI to the device authorization Endpoint of ZITADEL.
For more information, see [OIDC Proxy](./login-app#oidc-proxy) for the necessary headers.
<Callout>
The version and the optional custom URI for the available login UI is configurable under the application settings.
</Callout>
The endpoint will return the device authorization response:
```json
{
"device_code": "0jbAZbU3ClK-Mkt0li4U1A",
"user_code": "FWRK-JGWK",
"verification_uri": "https://login.example.com/device",
"verification_uri_complete": "https://login.example.com/device?user_code=FWRK-JGWK",
"expires_in": 300,
"interval": 5
}
```
The device presents the `user_code` and `verification_uri` or maybe even render a QR code with the `verification_uri_complete` for the user to scan.
Your login will have to provide a page on the `verification_uri` where the user can enter the `user_code`, or it's automatically filled in, if they scan the QR code.
### Get the Device Authorization Request by User Code
With the user_code entered by the user you will now be able to get the information of the device authorization request.
[Get Device Authorization Request Documentation](/reference/api/oidc/zitadel.oidc.v2.OIDCService.GetDeviceAuthorizationRequest)
```bash
curl --request GET \
--url https://${CUSTOM_DOMAIN}/v2/oidc/device_authorization/FWRK-JGWK \
--header 'Authorization: Bearer '"$TOKEN"''
```
Response Example:
```json
{
"deviceAuthorizationRequest": {
"id": "XzNejv6NxqVU8Qur5uxEh7f_Wi1p0qUu4PJTJ6JUIx0xtJ2uqmU",
"clientId": "170086824411201793",
"scope": [
"openid",
"profile"
],
"appName": "TV App",
"projectName": "My Project"
}
}
```
Present the user with the information of the device authorization request and allow them to approve or deny the request.
### Perform Login
After you have initialized the OIDC flow you can implement the login.
Implement all the steps you like the user the go trough by [creating](/reference/api/session/zitadel.session.v2.SessionService.CreateSession) and [updating](/reference/api/session/zitadel.session.v2.SessionService.SetSession) the user-session.
Read the following resources for more information about the different checks:
- [Username and Password](./username-password)
- [External Identity Provider](./external-login)
- [Passkeys](./passkey)
- [Multi-Factor](./mfa)
### Authorize the Device Authorization Request
To finalize the auth request and connect an existing user session with it, you have to update the auth request with the session token.
On the create and update user session request you will always get a session token in the response.
The latest session token has to be sent to the following request:
Read more about the [Authorize or Deny Device Authorization Request Documentation](/reference/api/oidc/zitadel.oidc.v2.OIDCService.AuthorizeOrDenyDeviceAuthorization)
Make sure that the authorization header is from an account which is permitted to finalize the Auth Request through the `IAM_LOGIN_CLIENT` role.
```bash
curl --request POST \
--url ${CUSTOM_DOMAIN}/v2/oidc/device_authorization/XzNejv6NxqVU8Qur5uxEh7f_Wi1p0qUu4PJTJ6JUIx0xtJ2uqmU \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"session": {
"sessionId": "225307381909694508",
"sessionToken": "7N5kQCvC4jIf2OuBjwfyWSX2FUKbQqg4iG3uWT-TBngMhlS9miGUwpyUaN0HJ8OcbSzk4QHZy_Bvvv"
}
}'
```
If you don't get any error back, the request succeeded, and you can notify the user that they can close the window now and return to the application.
### Deny the Device Authorization Request
If the user denies the device authorization request, you can deny the request by sending the following request:
```bash
curl --request POST \
--url ${CUSTOM_DOMAIN}/v2/oidc/device_authorization/ \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"deny": {}
}'
```
If you don't get any error back, the request succeeded, and you can notify the user that they can close the window now and return to the application.
### Device Authorization Endpoints
All OAuth2 Device Authorization Grant endpoints are provided by ZITADEL. In your login UI you just have to proxy them through and send them directly to the backend.
These endpoints are:
- Well-known
- Device Authorization Endpoint
- Token
Additionally, we recommend you to proxy all the other [OIDC relevant endpoints](./oidc-standard#oidc-endpoints).
@@ -0,0 +1,223 @@
---
title: Handle External Logins in a Custom Login UI
sidebar_label: External Identity Provider
---
## Flow
The prerequisite for adding an external login (social and enterprise) to your user account is a registered identity provider on your ZITADEL instance or the organization of the user.
If you haven’t added a provider yet, have a look at the following guide first: [Identity Providers](/guides/integrate/identity-providers/introduction)
![](../../../../public/img/guides/login-ui/external-login-flow.png)
## Start the Provider Flow
ZITADEL will handle as much as possible from the authentication flow with the external provider.
This requires you to initiate the flow with your desired provider.
Send the following two URLs in the request body:
1. SuccessURL: Page that should be shown when the login was successful
2. ErrorURL: Page that should be shown when an error happens during the authentication
In the response, you will get an authentication URL of the provider you like.
[Start Identity Provider Intent Documentation](/reference/api/user/zitadel.user.v2.UserService.StartIdentityProviderIntent)
### Request
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/idp_intents \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"idpId": "$IDP_ID",
"urls": {
"successUrl": "https://custom.com/login/idp/success",
"failureUrl": "https://custom.com/login/idp/fail"
}
}'
```
### Response
```bash
{
"details": {
"sequence": "592",
"changeDate": "2023-06-14T12:51:29.654819Z",
"resourceOwner": "163840776835432705"
},
"authUrl": "https://accounts.google.com/o/oauth2/v2/auth?client_id=Test&prompt=select_account&redirect_uri=https%3A%2F%2F${CUSTOM_DOMAIN}%2Fidps%2Fcallback&response_type=code&scope=openid+profile+email&state=218525066445455617"
}
```
## Call Provider
The next step is to call the auth URL you got in the response from the previous step.
This will open up the login page of the given provider. In this guide, it is Google Login.
```bash
https://accounts.google.com/o/oauth2/v2/auth?client_id=Test&prompt=select_account&redirect_uri=https%3A%2F%2F${CUSTOM_DOMAIN}%2Fidps%2Fcallback&response_type=code&scope=openid+profile+email&state=218525066445455617
```
After the user has successfully authenticated, a redirect to the ZITADEL backend /idps/callback will automatically be performed.
<Callout type="warn">
Note that the redirect URL is `https://${CUSTOM_DOMAIN}/idps/callback` when using the new V2 hosted login compared to the V1 hosted login, which was `https://${CUSTOM_DOMAIN}/ui/login/login/externalidp/callback`.
</Callout>
## Get Provider Information
ZITADEL will take the information of the provider. After this, a redirect will be made to either the success page in case of a successful login or to the error page in case of a failure will be performed. In the parameters, you will provide the IDP intentID, a token, and optionally, if a user could be found, a user ID.
To get the information of the provider, make a request to ZITADEL.
[Retrieve Identity Provider Intent Documentation](/reference/api/user/zitadel.user.v2.UserService.RetrieveIdentityProviderIntent)
### Request
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/idp_intents/$INTENT_ID \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"idpIntentToken": "k50WQmDaPIazQDJsyKaEPaQPwgsytxqgQ3K1ifQeQtAmeQ"
}'
```
### Response
```bash
{
"details": {
"sequence": "599",
"changeDate": "2023-06-15T06:44:26.039444Z",
"resourceOwner": "163840776835432705"
},
"idpInformation": {
"oauth": {
"accessToken": "ya29...",
"idToken": "ey..."
},
"idpId": "218528353504723201",
"rawInformation": {
"User": {
"email": "[email protected]",
"email_verified": true,
"family_name": "Mouse",
"given_name": "Minnie",
"hd": "mouse.com",
"locale": "de",
"name": "Minnie Mouse",
"picture": "https://lh3.googleusercontent.com/a/AAcKTtf973Q7NH8KzKTMEZELPU9lx45WpQ9FRBuxFdPb=s96-c",
"sub": "111392805975715856637"
}
}
}
}
```
## Handle Provider Information
After successfully authenticating using your identity provider, you have three possible options.
1. Login
2. Register user
3. Add social login to existing user
### Login
If you did get a user ID in the parameters when calling your success page, you know that a user is already linked with the used identity provider and you are ready to perform the login.
Create a new session and include the IDP intent ID and the token in the checks.
This check requires that the previous step ended on the successful page and didn't’t result in an error.
#### Request
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/sessions \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"checks": {
"user": {
"userId": "218662596918640897"
},
"idpIntent": {
"idpIntentId": "219647325729980673",
"idpIntentToken": "k86ihn-VLMMUGKy1q1b5i_foECspKYqei1l4mS8LT7Xzjw"
}
}
}'
```
### Register
If you didn't get a user ID in the parameters of your success page, you know that there is no existing user in ZITADEL with that provider, and you can register a new user or link it to an existing account (read the next section).
Fill the IdP links in the create user request to add a user with an external login provider.
The idpId is the ID of the provider in ZITADEL, the userId is the ID of the user in the external identity provider; usually, this is sent in the “sub”.
The display name is used to list the linkings on the users.
[Create User API Documentation](/reference/api/user/zitadel.user.v2.UserService.AddHumanUser)
#### Request
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/users/human \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"username": "[email protected]",
"profile": {
"givenName": "Minnie",
"familyName": "Mouse",
"nickName": "Mini",
"displayName": "Minnie Mouse",
"preferredLanguage": "en",
"gender": "GENDER_FEMALE"
},
"email": {
"email": "[email protected]",
"isVerified": true
},
"idpLinks": [
{
"idpId": "218528353504723201",
"userId": "111392805975715856637",
"userName": "Minnie Mouse"
}
]
}'
```
### Add External Login to Existing User
If you didn't get a user ID in the parameters to your success page, you know that there is no existing user in ZITADEL with that provider and you can register a new user (read previous section), or link it to an existing account.
If you want to link/connect to an existing account you can perform the add identity provider link request.
[Add IDP Link to existing user documentation](/reference/api/user/zitadel.user.v2.UserService.AddIDPLink)
#### Request
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/users/users/218385419895570689/links \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"idpLink": {
"idpId": "218528353504723201",
"userId": "1113928059757158566371",
"userName": "Minnie Mouse"
}
}'
```
@@ -0,0 +1,26 @@
---
title: "Build your own Login UI"
description: "In the following guides you will learn how to create your own login UI with our APIs. The different scenarios like username/password, external identity provider, etc. will be shown."
---
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
import { Card, Cards } from 'fumadocs-ui/components/card';
import { FileText, Folder, Link as LinkIcon } from 'lucide-react';
<Cards>
<Card title="Session Validation" href="/guides/integrate/login-ui/session-validation" icon={<FileText />} />
<Card title="Username Password" href="/guides/integrate/login-ui/username-password" icon={<FileText />} />
<Card title="External Login" href="/guides/integrate/login-ui/external-login" icon={<FileText />} />
<Card title="Passkey" href="/guides/integrate/login-ui/passkey" icon={<FileText />} />
<Card title="Mfa" href="/guides/integrate/login-ui/mfa" icon={<FileText />} />
<Card title="Select Account" href="/guides/integrate/login-ui/select-account" icon={<FileText />} />
<Card title="Password Reset" href="/guides/integrate/login-ui/password-reset" icon={<FileText />} />
<Card title="Logout" href="/guides/integrate/login-ui/logout" icon={<FileText />} />
<Card title="Oidc Standard" href="/guides/integrate/login-ui/oidc-standard" icon={<FileText />} />
<Card title="Saml Standard" href="/guides/integrate/login-ui/saml-standard" icon={<FileText />} />
<Card title="Device Auth" href="/guides/integrate/login-ui/device-auth" icon={<FileText />} />
<Card title="Login App" href="/guides/integrate/login-ui/login-app" icon={<FileText />} />
</Cards>
@@ -0,0 +1,147 @@
---
title: Login App
sidebar_label: Login App
---
To replace the old embedded Login built with Golang and to showcase the use of our session and OIDC APIs, we've created the new [Login app](https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#contribute-to-login).
## Used Apps And Packages
- **Login app**: The Login app used by ZITADEL Cloud, powered by Next.js
- `@zitadel/proto`: Typescript implementation of Protocol Buffers, suitable for web browsers and Node.js.
- `@zitadel/client`: Core components for establishing a client connection
## Login App
The new implementation of the Login app is based on Next.js and implements the [OIDC standard](./oidc-standard) by proxying requests to the ZITADEL backend and using the session API.
The UI uses the branding and behaves based on the policies and settings of your instance or organization.
It is also allows users to authenticate directly (without an OIDC context) and self service their account.
Its set of features will increase with time and its progress will be tracked [here](./login-app#implemented-features).
### Architectural Overview
The new Login app consists of a confidential (backend) part and a browser (client side) part.
In addition to that, the OIDC specification builds upon a proxy which is set up in the Next.js middleware. It rewrites requests of the paths `/.well-known/:path*`, `/oauth/:path*`, `/oidc/:path*` to ZITADEL.
The backend side of the application is authorized using a machine user's personal access token.
This token is used to fetch policies and settings from your instances or organization which define how users are authenticated and how the Login app is displayed.
The frontend side of the application communicates to the backend side by using a cookie which is set by the server.
The cookie consists of an id and a token and is bound to a session which is updated continuously.
The following illustration shows the architecture of the Login app and a potential authentication code flow starting from an application which implements the OIDC specification respectively.
![](../../../../public/img/typescript-login-architecture.png)
_Note that the illustration is just a representation of the architecture and may not specify actual endpoints._
The flow starts by the `/authorize` endpoint which is intercepted by the Login app and forwarded to ZITADEL.
ZITADEL interprets the request and specifically redirects back to the Login app.
It does so by redirecting to `/login`.
The Login app is then able to load an [AuthRequest](/reference/api/oidc/zitadel.oidc.v2.OIDCService.GetAuthRequest).
The Auth Request defines how users proceed to authenticate. If no special prompts or scopes are set, the Login app brings up the `/loginname` page.
The /loginname page allows to enter loginname or email of a user which is then used to search for a user.
If the user is found, a session is created and set as cookie, then the user is redirected to the available authentication method page.
While the users continues and provides more information, the cookie is hydrated with this information until a final state is reached.
The communication from the browser to the server is done by [NextJS Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
The OIDC Auth request is always passed in the url to have a context to the ongoing authentication flow.
If enough user information is retrieved and the user is authenticated according to the policies, the flow is finalized by [requesting a the callback url](/reference/api/oidc/zitadel.oidc.v2.OIDCService.CreateCallback) for the auth request and the user is redirected back to the application.
The application can then request a token calling the /token endpoint of the Login app which is proxied to the ZITADEL API.
### Implemented features
#### OIDC Standard
- [x] Authorization Code Flow with PKCE
- [x] AuthRequest `hintUserId`
- [x] AuthRequest `loginHint`
- [x] AuthRequest `prompt`
- [x] Login
- [x] Select Account
- [x] Create
- [x] None
- [ ] Consent
- Scopes
- [x] `openid email profile address`
- [x] `offline access`
- [x] `urn:zitadel:iam:org:idp:id:{idp_id}`
- [x] `urn:zitadel:iam:org:project:id:zitadel:aud`
- [x] `urn:zitadel:iam:org:id:{orgid}`
- [x] `urn:zitadel:iam:org:domain:primary:{domain}`
- [ ] AuthRequest UI locales
- Multifactor
- [x] Passkeys
- [x] TOTP
- [x] OTP via email
- [x] OTP via SMS
- Passwordless
- [x] Passkeys
- Security Prompts
- [x] Setup Passkey as Passwordless method
- [x] Setup TOTP as Multifactor
- [ ] Password Change
- Login
- [x] Email Password
- [x] Passkey
- [x] IDPs
- [x] Google
- [x] GitHub
- [x] GitLab
- [x] Azure
- [ ] Apple
- Register
- [x] Email Password
- [x] Passkey
#### Self service
The self service part of the application allows to load the Login app without an OIDC flow.
Authenticated users are directly able to self service their account.
- [ ] Change user information
- [ ] Password Change
- [x] Setup Passkey
- [x] Setup Multifactor
- [x] Passkeys
- [x] TOTP
- [x] OTP via email
- [x] OTP via SMS
- [x] Setup Multifactor Passkey (U2F)
- [x] Validate Account (email verification)
### Setup
In order to run the new Login app, make sure to follow the instructions in the [contribution guide](https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#contribute-to-login).
#### How to setup domains for OIDC
When setting up the new Login app for OIDC, the domain must be registered on your instance and use https.
To register your login domain on your instance, [add](/apis/resources/admin/admin-service-add-instance-trusted-domain) your domain on your instances Trusted Domains.
#### OIDC Proxy
When setting up the new Login app for OIDC, ensure it meets the following requirements:
- The OIDC Proxy is deployed and running on HTTPS
- The OIDC Proxy sets `x-zitadel-public-host` which is the host, your Login app is deployed to `ex. login.example.com`.
- The OIDC Proxy sets `x-zitadel-instance-host` which is the host of your instance `ex. test-hdujwl.zitadel.cloud`.
You can review an example implementation of a middleware [here](https://github.com/zitadel/zitadel/blob/main/apps/login/src/middleware.ts).
#### Deploy to Vercel
<a href="https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fzitadel%2Fzitadel&env=ZITADEL_API_URL,ZITADEL_SERVICE_USER_ID,ZITADEL_SERVICE_USER_TOKEN&root-directory=apps/login&envDescription=Setup%20a%20service%20account%20with%20IAM_LOGIN_CLIENT%20membership%20on%20your%20instance%20and%20provide%20its%20id%20and%20personal%20access%20token.&project-name=zitadel-login&repository-name=zitadel-login" target="_blank" rel="noopener noreferrer" style={{cursor: "pointer", display: "inline-block", padding: "2px"}}>
<img src="https://vercel.com/button" alt="Deploy with Vercel" style={{pointerEvents: "none", display: "block"}}/>
</a>
To deploy your own version on Vercel, navigate to your instance and create a service user.
Create a personal access token (PAT) for the user and copy and set it as `ZITADEL_SERVICE_USER_TOKEN`, then navigate to Default settings and make sure it gets `IAM_LOGIN_CLIENT` permissions.
Finally set your instance url as `ZITADEL_API_URL`. Make sure to set it without trailing slash.
Also ensure your login domain is registered on your instance by adding it as a [Trusted Domain](/apis/resources/admin/admin-service-add-instance-trusted-domain).
If you want to enforce users to have their email verified, you can set the optional `EMAIL_VERIFICATION` variable to `true` in your environment and your users will be enforced to verify their email address before they can log in.
![](../../../../public/img/deploy-to-vercel.png)
@@ -0,0 +1,8 @@
---
title: Logging Out via a Custom Login UI
sidebar_label: Logout
---
import Logout from './_logout.mdx';
<Logout components={props.components} />
@@ -0,0 +1,539 @@
---
title: Multi-Factor Authentication (MFA) in a Custom Login UI
sidebar_label: Multi-Factor Authentication (MFA)
---
import MfaOptions from './_list-mfa-options.mdx';
import BrowserRegisterWebAuthN from './_browser_register_webauthn.mdx';
import BrowserSigninWebAuthN from './_browser_signin_webauthn.mdx';
import UpdateSessionWebAuthN from './_update_session_webauthn.mdx';
Multi-factor authentication (MFA) is a multi-step account authentication which requires to user to enter more than only the password.
It is highly recommended to use MFA or [Passkeys](./passkey) to make your user accounts more secure.
ZITADEL supports different Methods:
- Time-based one time password (TOTP), which are Authenticator apps like Google/Microsoft Authenticator, Authy, etc
- One-time password sent as SMS
- One-time password sent as E-Mail
- Universal Second Factor (U2F), which is authentication with your device like Windows Hello, Apple FaceID, Fingerprint, FIDO2 keys, Yubikey, etc.
## TOTP Registration
### Flow
![](../../../../public/img/guides/login-ui/register-totp-flow.png)
### List the Possible Methods
<MfaOptions components={props.components} />
### Start TOTP Registration
The user has selected to setup Time-based One-Time Password (TOTP).
To show the user the QR to register TOTP with his Authenticator App like Google/Microsoft Authenticator or Authy you have to start the registration on the ZITADEL API.
Generate the QR Code with the URI from the response.
For users that do not have a QR Code reader make sure to also show the secret, to enable manual configuration.
More detailed information about the API: [Start TOTP Registration Documentation](/reference/api/user/zitadel.user.v2.UserService.RegisterTOTP)
Request Example:
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/users/$USER_ID/totp \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''
--header 'Content-Type: application/json' \
--data '{}'
```
Response Example:
```bash
{
"details": {
"sequence": "2",
"changeDate": "2023-06-28",
"resourceOwner": "69629023906488334"
},
"uri": "otpauth://totp/ZITADEL:[email protected]?algorithm=SHA1&digits=6&issuer=ZITADEL&period=30&secret=TJOPWSDYILLHXFV4MLKNNJOWFG7VSDCK",
"secret": "TJOPWSDYILLHXFV4MLKNNJOWFG7VSDCK"
}
```
### Verify TOTP Registration
When the user has added the account to the authenticator app, the code from the App has to be entered to finish the registration.
This code has to be sent to the verify endpoint in ZITADEL.
More detailed information about the API: [Verify TOTP Documentation](/reference/api/user/zitadel.user.v2.UserService.VerifyTOTPRegistration)
Request Example:
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/users/$USER_ID/totp/verify \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''
--header 'Content-Type: application/json' \
--data '{
"code": "123456"
}'
```
## TOTP Authentication
### Flow
![](../../../../public/img/guides/login-ui/authenticate-totp-flow.png)
### Check User
To be able to check the TOTP you need a session with a checked user. This can either happen before the TOTP check or at the same time.
In this example we do two separate requests. So the first step is to create a new Sessions.
More detailed information about the API: [Create new session Documentation](/reference/api/session/zitadel.session.v2.SessionService.CreateSession)
Example Request
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/sessions \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"'' \
--header 'Content-Type: application/json' \
--data '{
"checks": {
"user": {
"loginName": "[email protected]"
}
}
}'
```
Example Response
```bash
{
"details": {
"sequence": "580",
"changeDate": "2023-06-14T05:32:39.007096Z",
"resourceOwner": "163840776835432705"
},
"sessionId": "218480890961985793",
"sessionToken": "yMDi6uVPJAcphbbz0LaxC07ihWkNTe7m0Xqch8SzfM5Cz3HSIQIDZ65x1f5Qal0jxz0MEyo-_zYcUg"
}
```
### Check TOTP
Now you can show the code field to the user, where the code needs to be entered from the Authenticator App.
With that code you have to update the existing session with a totp check.
More detailed information about the API: [Update session Documentation](/reference/api/session/zitadel.session.v2.SessionService.SetSession)
Example Request
```bash
curl --request PATCH \
--url https://${CUSTOM_DOMAIN}/v2/sessions/$SESSION-ID \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
"checks": {
"totp": {
"code": "323764"
},
}
}'
```
## SMS Code Registration
### Flow
![](../../../../public/img/guides/login-ui/register-phone-otp-flow.png)
### List the Possible Methods
<MfaOptions components={props.components} />
### Add Phone Number
When the user has decided to register the phone number to get a code as a second factor, the first step is to add a verified phone number to the user.
If the user already has a verified phone number you can skip this step.
When adding a new phone number, you can choose if you want ZITADEL to send the verification code to the number, or if you want to send it by yourself.
If ZITADEL should do it, make sure that you have registered an [SMS Provider](/guides/manage/console/default-settings#sms) and send an empty sendCode object in the request.
With an empty returnCode object in the request, ZITADEL will not send the code, but return it in the response.
If you don't want the user to verify the phone number, you can also create it directly as verified, by sending the isVerified attribute.
More detailed information about the API: [Add phone](/reference/api/user/zitadel.user.v2.UserService.SetPhone)
Example Request:
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/users/$USER-ID/phone \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"'' \
--header 'Content-Type: application/json' \
--data '{
"phone": "+41791234567",
"sendCode": {}
}'
```
### Verify Phone Number
The next step is to show a screen, so the user is able to enter the code for verifying the phone number.
Send a verify phone request with the code in the body.
More detailed information about the API: [Verify phone](/reference/api/user/zitadel.user.v2.UserService.VerifyPhone)
Example Request:
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/users/$USER-ID/phone/verify \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"'' \
--header 'Content-Type: application/json' \
--data '{
"verificationCode": "VBQREB"
}'
```
### Add OTP SMS to the user
Now that the user has a verified phone number you can enable SMS OTP on the user.
More detailed information about the API: [Add OTP SMS for a user](/reference/api/user/zitadel.user.v2.UserService.AddOTPSMS)
Example Request:
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/users/$USER-ID/otp_sms \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"'' \
--header 'Content-Type: application/json'
```
## SMS Code Authentication
### Flow
![](../../../../public/img/guides/login-ui/authenticate-phone-otp-flow.png)
### Check User
To be able to check the SMS Code you need a session with a checked user.
When creating the session you can already start the sms challenge, this will only be executed if the user check was successful.
You can tell the challenge, if the code should be returned (returnCode: true) or if ZITADEL should send it (returnCode: false).
More detailed information about the API: [Create new session Documentation](/reference/api/session/zitadel.session.v2.SessionService.CreateSession)
Example Request
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/sessions \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"'' \
--header 'Content-Type: application/json' \
--data '{
"checks": {
"user": {
"loginName": "[email protected]"
}
},
"challenges": {
"otpSms": {
"returnCode": false
}
}
}'
```
### Check SMS Code
In the next step you should prompt the user to enter the SMS verification code in the provided field.
The update session request has a check otpSMS where you should send the code, the user has entered.
Example Request
```bash
curl --request PATCH \
--url https://${CUSTOM_DOMAIN}/v2/sessions/225307381909694507 \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"'' \
--header 'Content-Type: application/json' \
--data '{
"checks": {
"otpSms": {
"code": "3237642"
},
}
}'
```
## Email Code Registration
### Flow
![](../../../../public/img/guides/login-ui/register-email-otp-flow.png)
### List the Possible Methods
<MfaOptions components={props.components} />
### Verified Email
As ZITADEL required all users to have a verified email address, you do not need to add a new email and verify it for setting up the second factor.
For the Email second factor the already verified email address will be taken.
### Add OTP Email to the user
As the user already has a verified E-Mail address you can enable E-Mail OTP on the user.
More detailed information about the API: [Add OTP Email for a user](/reference/api/user/zitadel.user.v2.UserService.AddOTPEmail)
Example Request:
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/users/$USER-ID/otp_email \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"'' \
--header 'Content-Type: application/json'
```
## Email Code Authentication
### Flow
![](../../../../public/img/guides/login-ui/authenticate-email-otp-flow.png)
### Check User
To be able to check the Email Code you need a session with a checked user.
When creating the session you can already start the sms challenge, this will only be executed if the user check was successful.
You can tell the challenge, if the code should be returned (returnCode: true) or if ZITADEL should send it (returnCode: false).
More detailed information about the API: [Create new session Documentation](/reference/api/session/zitadel.session.v2.SessionService.CreateSession)
Example Request
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/sessions \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"'' \
--header 'Content-Type: application/json' \
--data '{
"checks": {
"user": {
"loginName": "[email protected]"
}
},
"challenges": {
"otpEmail": {
"returnCode": false
}
}
}'
```
### Check Email Code
Next, display a code field for the user to enter the verification code they received via email.
The update session request has a check otpEmail where you should send the code, the user has entered.
Example Request
```bash
curl --request PATCH \
--url https://${CUSTOM_DOMAIN}/v2/sessions/225307381909694507 \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"'' \
--header 'Content-Type: application/json' \
--data '{
"checks": {
"otpEmail": {
"code": "3237642"
},
}
}'
```
## U2F Registration
### Flow
![](../../../../public/img/guides/login-ui/register-u2f-flow.png)
### List the Possible Methods
<MfaOptions components={props.components} />
### Start U2F Registration
The user has selected to setup Universal Second Factor (U2F).
To be able to authenticate in the browser you have to start the u2f registration within ZITADEL.
More detailed information about the API: [Start U2F Registration Documentation](/reference/api/user/zitadel.user.v2.UserService.RegisterU2F)
Request Example:
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/users/$USER_ID/u2f \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''
--header 'Content-Type: application/json' \
--data '{
"domain": "acme.com"
}'
```
Response Example:
```bash
{
"details": {
"sequence": "2",
"changeDate": "2023-07-03",
"resourceOwner": "69629023906488334"
},
"u2fId": "163840776835432705",
"publicKeyCredentialCreationOptions": {
"publicKey": {
"attestation": "none",
"authenticatorSelection": {
"userVerification": "required"
},
"challenge": "XaMYwWOZ5hj6pwtwJJlpcI-ExkO5TxevBMG4R8DoKQQ",
"excludeCredentials": [
{
"id": "tVp1QfYhT8DkyEHVrv7blnpAo2YJzbZgZNBf7zPs6CI",
"type": "public-key"
}
],
"pubKeyCredParams": [
{
"alg": -7,
"type": "public-key"
}
],
"rp": {
"id": "localhost",
"name": "ZITADEL"
},
"timeout": 300000,
"user": {
"displayName": "Minie Mouse",
"id": "MjE1NTk4MDAwNDY0OTk4OTQw",
"name": "minie-mouse"
}
}
}
}
```
### Register new U2F on current device
<BrowserRegisterWebAuthN components={props.components} />
### Verify U2F Registration
In the next request you have to verify the U2F within ZITADEL.
Include the public key credential you got from the browser in your request.
You can give the U2F a name, which makes it easier for the user to identify the registered authentication methods.
Example: Google Pixel, iCloud Keychain, Yubikey, etc
More detailed information about the API: [Verify U2F Documentation](/reference/api/user/zitadel.user.v2.UserService.VerifyU2FRegistration)
Example Request:
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/users/$USER_ID/u2f/$PASSKEY_ID \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"publicKeyCredential": {
"type": "public-key",
"id": "pawVarF4xPxLFmfCnRkwXWeTrKGzabcAi92LEI1WC00",
"rawId": "pawVarF4xPxLFmfCnRkwXWeTrKGzabcAi92LEI1WC00",
"response": {
"attestationObject": "o2NmbXRmcGFja2VkZ2F0dFN0bXSiY2FsZyZjc2lnWEcwRQIgRKS3VpeE9tfExXRzkoUKnG4rQWPvtSSt4YtDGgTx32oCIQDPey-2YJ4uIg-QCM4jj6aE2U3tgMFM_RP7Efx6xRu3JGhhdXRoRGF0YVikSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAADju76085Yhmlt1CEOHkwLQAIKWsFWqxeMT8SxZnwp0ZMF1nk6yhs2m3AIvdixCNVgtNpQECAyYgASFYIMGUDSP2FAQn2MIfPMy7cyB_Y30VqixVgGULTBtFjfRiIlggjUGfQo3_-CrMmH3S-ZQkFKWKnNBQEAMkFtG-9A4zqW0",
"clientDataJSON": "eyJ0eXBlJjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiQlhXdHh0WGxJeFZZa0pHT1dVaUVmM25zby02aXZKdWw2YmNmWHdMVlFIayIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWxob3N0OjgwODAifQ"
}
},
"tokenName": "Google Pixel"
}'
```
You have successfully registered a new U2F to the user.
## U2F Authentication
### Flow
![](../../../../public/img/guides/login-ui/authenticate-u2f-flow.png)
### Check User
To be able to check the Universal Second Factor (U2F) you need a user check and a webAuthN challenge.
In the create session request you can check for the user and directly initiate the webAuthN challenge.
For U2F you can choose between "USER_VERIFICATION_REQUIREMENT_PREFERRED" and "USER_VERIFICATION_REQUIREMENT_DISCOURAGED" for the challenge.
Best practice is using discouraged, as this doesn't require the user to enter a PIN. With `preferred` the user might be prompted for the PIN, but it is not necessary.
More detailed information about the API: [Create new session Documentation](/reference/api/session/zitadel.session.v2.SessionService.CreateSession)
Example Request
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/sessions \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"'' \
--header 'Content-Type: application/json' \
--data '{
"checks": {
"user": {
"loginName": "[email protected]"
}
},
"metadata": {},
"challenges": {
"webAuthN": {
"domain": "CUSTOM_DOMAIN",
"userVerificationRequirement": "USER_VERIFICATION_REQUIREMENT_DISCOURAGED"
}
}
}'
```
Example Response
```bash
{
"details": {
"sequence": "580",
"changeDate": "2023-06-14T05:32:39.007096Z",
"resourceOwner": "163840776835432705"
},
"sessionId": "218480890961985793",
"sessionToken": "yMDi6uVPJAcphbbz0LaxC07ihWkNTe7m0Xqch8SzfM5Cz3HSIQIDZ65x1f5Qal0jxz0MEyo-_zYcUg"
}
```
### Signin in Browser
<BrowserSigninWebAuthN components={props.components} />
### Update Session with WebAuthN
<UpdateSessionWebAuthN components={props.components} />
@@ -0,0 +1,146 @@
---
title: Support for the OpenID Connect(OIDC) Standard in a Custom Login UI
sidebar_label: OIDC Standard
---
To build your own login ui for your own application it is not necessary to have the OIDC standard included or any additional work that has to be done.
However, it might make sense, if you want to connect your login to different applications especially if they are not in your control and they rely on the standard.
The following flow shows you the different components you need to enable OIDC for your login.
![](../../../../public/img/guides/login-ui/oidc-flow.png)
1. Your application makes an authorization request to your login UI
2. The login UI proxies the request to the ZITADEL API.
3. The ZITADEL API parses the request and does what it needs to interpret certain parameters (e.g., organization scope, etc.)
4. Redirect to a predefined, relative URL of the login UI that includes the authrequest ID ("/login?authRequest="), configurable per application.
5. Request to ZITADEL API to get all the information from the auth request. This is optional and only needed if you like to get all the parsed information from the authrequest-
6. Authenticate the user in your login UI by creating and updating a session with all the checks you need.
7. Finalize the auth request by sending the session to the request, you will get the callback URL in the response
8. Redirect to your application with the callback URL you got in the previous request
9. All OIDC-specific endpoints have to be accepted in the Login UI and should be proxied and sent to the ZITADEL API
## Example
Let's assume you host your login UI on the following URL:
```
https://login.example.com
```
## Authorize Request
A user opens your application and is unauthenticated, the user will then be redirected to your login with the following auth Request:
```
https://login.example.com/oauth/v2/authorize?client_id=170086824411201793%40yourapp&redirect_uri=https%3A%2F%2Fyourapp.example.com%2Fauth%2Fcallback&response_type=code&scope=openid%20email%20profile&code_challenge=9az09PjcfuENS7oDK7jUd2xAWRb-B3N7Sr3kDoWECOY&code_challenge_method=S256&login_hint=minnie-mouse```
```
The auth request includes all the relevant information for the OIDC standard and in this example we also have a login hint for the login name "minnie-mouse".
You now have to proxy the auth request from your own UI to the authorize Endpoint of ZITADEL.
For more information, see [OIDC Proxy](./login-app#oidc-proxy) for the necessary headers.
<Callout>
The version and the optional custom URI for the available login UI is configurable under the application settings.
</Callout>
Read more about the [Authorize Endpoint Documentation](/apis/openidoauth/endpoints#authorization-endpoint)
The endpoint will redirect you to the domain of your UI on the path /login and add the auth Request ID as parameter.
```https://login.example.com/login?authRequest=V2_224908753244265546```
### Get Auth Request by ID
With the ID from the redirect before you will now be able to get the information of the auth request.
[Get Auth Request By ID Documentation](/reference/api/oidc/zitadel.oidc.v2.OIDCService.GetAuthRequest)
```bash
curl --request GET \
--url https://${CUSTOM_DOMAIN}/v2/oidc/auth_requests/V2_224908753244265546 \
--header 'Authorization: Bearer '"$TOKEN"''
```
Response Example:
```json
{
"authRequest": {
"id": "V2_224908753244265546",
"creationDate": "2023-07-28T13:47:43.471505Z",
"clientId": "224901977648260028@mytestproject",
"scope": [
"openid",
"profile"
],
"redirectUri": "https://myapp.example.com/auth/callback",
"loginHint": "[email protected]"
}
}
```
### Perform Login
After you have initialized the OIDC flow you can implement the login.
Implement all the steps you like the user the go trough by [creating](/reference/api/session/zitadel.session.v2.SessionService.CreateSession) and [updating](/reference/api/session/zitadel.session.v2.SessionService.SetSession) the user-session.
Read the following resources for more information about the different checks:
- [Username and Password](./username-password)
- [External Identity Provider](./external-login)
- [Passkeys](./passkey)
- [Multi-Factor](./mfa)
### Finalize Auth Request
To finalize the auth request and connect an existing user session with it, you have to update the auth request with the session token.
On the create and update user session request you will always get a session token in the response.
The latest session token has to be sent to the following request:
Read more about the [Finalize Auth Request Documentation](/reference/api/oidc/zitadel.oidc.v2.OIDCService.CreateCallback)
Make sure that the authorization header is from an account which is permitted to finalize the Auth Request through the `IAM_LOGIN_CLIENT` role.
```bash
curl --request POST \
--url ${CUSTOM_DOMAIN}/v2/oidc/auth_requests/V2_224908753244265546 \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"session": {
"sessionId": "225307381909694508",
"sessionToken": "7N5kQCvC4jIf2OuBjwfyWSX2FUKbQqg4iG3uWT-TBngMhlS9miGUwpyUaN0HJ8OcbSzk4QHZy_Bvvv"
}
}'
```
In the response you will get a callback URL to which you have to redirect from your login UI.
Example Response:
```bash
{
"details": {
"sequence": "686",
"changeDate": "2023-07-31T08:09:19.314537Z",
"resourceOwner": "163840776801878273"
},
"callbackUrl": "https://myapp.example.com/auth/callback?code=k98NBLrdjVbwQQI-oM_rR_cYHv0k3dqpkqlQX8UXTWVnYSQL9g&state=testd"
}
```
### OIDC Endpoints
All OIDC relevant endpoints are provided by ZITADEL. In your login UI you just have to proxy them through and send them directly to the backend.
These are endpoints like:
- Userinfo
- Well-known
- Introspection
- Token
- etc
### End Session / Logout
The end session endpoint has to be implemented as all the other OIDC endpoints. This means you have to proxy the request from you UI to the ZITADEL.
In case the ZITADEL backend is not able to determine which session to terminate directly or requires additional approval from the user, it will redirect the browser to the following endpoint:
```/logout?post_logout_redirect=```
Prompt the user to select a session, terminate it using the [corresponding endpoint](/reference/api/session/zitadel.session.v2.SessionService.DeleteSession) and send the user to the `post_logout_redirect` URL.
@@ -0,0 +1,283 @@
---
title: Using Passkeys in a Custom Login UI
sidebar_label: Passkeys
---
import BrowserRegisterWebAuthN from './_browser_register_webauthn.mdx';
import BrowserSigninWebAuthN from './_browser_signin_webauthn.mdx';
import UpdateSessionWebAuthN from './_update_session_webauthn.mdx';
Passkeys are a replacement for passwords that provide faster, easier, and more secure sign-ins to websites and apps even across multiple devices.
Unlike passwords, passkeys are phishing-resistant and can improve the user experience and security at the same time.
Passkeys and there underlying protocols are a standard defined by the [FIDO Standard](https://fidoalliance.org/) .
## Register Passkey
### Flow
![](../../../../public/img/guides/login-ui/passkey-registration-flow.png)
There are two options to onboard users with passkeys:
1. You send the user a link (email, sms, ...) with an embedded code, so the user is able to register passkey on any capable device
2. You start the passkey registration directly on the current device used by a user
### Send Registration Link
When you want to send a link to your user, that enables them to register a new passkey device, you can choose if ZITADEL sends the code or ZITADEL can return the code in the API response. This way you can send a link through any channel of your choice (email, sms, phone, in-person, postal, ...).
If you asked ZITADEL to send the link to the user please make sure to populate the link with the needed values that point towards your registration UI.
More detailed information about the API: [Send Registration Link Documentation](/reference/api/user/zitadel.user.v2.UserService.CreatePasskeyRegistrationLink)
Request Example:
Send either the sendLink or the returnCode (empty message) in the request body, depending on the use case you have.
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/users/$USER_ID/passkeys/registration_link \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"sendLink": {
"urlTemplate": "https://example.com/passkey/register?userID={{.UserID}}&orgID={{.OrgID}}&codeID={{.CodeID}}&code={{.Code}}"
},
"returnCode": {}
}'
```
Response Example:
The code is only filled if returnCode has been requested in the Request.Passkey
```bash
{
"details": {
"sequence": "632",
"changeDate": "2023-06-28T08:09:51.257699Z",
"resourceOwner": "163840776835432705"
},
"code": {
"id": "220526087715684609",
"code": "2KEpIeQGSBBd"
}
}
```
### Start Passkey Registration
When starting the passkey registration you can optionally send the registration code from the step above to ZITADEL to pair it with a specific user.
By specifying the authenticator type you can choose if the passkey should be cross platform or not. Per default all types are allowed:
- PASSKEY_AUTHENTICATOR_UNSPECIFIED
- PASSKEY_AUTHENTICATOR_PLATFORM
- PASSKEY_AUTHENTICATOR_CROSS_PLATFORM
The API response will provide you the public key credential options, this will be used by the browser to obtain a signed challenge.
More detailed information about the API: [Start Passkey Registration Documentation](/reference/api/user/zitadel.user.v2.UserService.RegisterPasskey)
Request Example:
The code only has to be filled if the user did get a registration code.
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/users/$USER_ID/passkeys \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"code": {
"id": "220526087715684609",
"code": "2KEpIeQGSBBd"
},
"authenticator": "PASSKEY_AUTHENTICATOR_UNSPECIFIED"
}'
```
Response Example:
```bash
{
"details": {
"sequence": "633",
"changeDate": "2023-06-28T08:10:26.725981Z",
"resourceOwner": "163840776835432705"
},
"passkeyId": "220526147258024193",
"publicKeyCredentialCreationOptions": {
"publicKey": {
"attestation": "none",
"authenticatorSelection": {
"userVerification": "required"
},
"challenge": "JM1uLbVQR2xZJ210DA7E-3j0Cd9rHKUSmc8NyIJBtAY",
"pubKeyCredParams": [
{
"alg": -7,
"type": "public-key"
},
{
"alg": -35,
"type": "public-key"
},
{
"alg": -36,
"type": "public-key"
},
{
"alg": -257,
"type": "public-key"
},
{
"alg": -258,
"type": "public-key"
},
{
"alg": -259,
"type": "public-key"
},
{
"alg": -37,
"type": "public-key"
},
{
"alg": -38,
"type": "public-key"
},
{
"alg": -39,
"type": "public-key"
},
{
"alg": -8,
"type": "public-key"
}
],
"rp": {
"id": "example.domain.com",
"name": "ZITADEL"
},
"timeout": 300000,
"user": {
"displayName": "Minnie Mouse",
"id": "MjE4NjYyNTk2OTE4NjQwODk3",
"name": "[email protected]"
}
}
}
}
```
### Register new Passkey on current device
<BrowserRegisterWebAuthN components={props.components} />
### Verify Passkey in ZITADEL
In the next request you have to verify the Passkey within ZITADEL.
Include the public key credential you got from the browser in your request.
You can give the Passkey a name, which makes it easier for the user to identify the registered authentication methods.
Example: Google Pixel, iCloud Keychain, Yubikey, etc
More detailed information about the API: [Verify Passkey Registration Documentation](/reference/api/user/zitadel.user.v2.UserService.VerifyPasskeyRegistration)
Example Request:
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/users/$USER_ID/passkeys/$PASSKEY_ID \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"publicKeyCredential": {
"type": "public-key",
"id": "pawVarF4xPxLFmfCnRkwXWeTrKGzabcAi92LEI1WC00",
"rawId": "pawVarF4xPxLFmfCnRkwXWeTrKGzabcAi92LEI1WC00",
"response": {
"attestationObject": "o2NmbXRmcGFja2VkZ2F0dFN0bXSiY2FsZyZjc2lnWEcwRQIgRKS3VpeE9tfExXRzkoUKnG4rQWPvtSSt4YtDGgTx32oCIQDPey-2YJ4uIg-QCM4jj6aE2U3tgMFM_RP7Efx6xRu3JGhhdXRoRGF0YVikSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAADju76085Yhmlt1CEOHkwLQAIKWsFWqxeMT8SxZnwp0ZMF1nk6yhs2m3AIvdixCNVgtNpQECAyYgASFYIMGUDSP2FAQn2MIfPMy7cyB_Y30VqixVgGULTBtFjfRiIlggjUGfQo3_-CrMmH3S-ZQkFKWKnNBQEAMkFtG-9A4zqW0",
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiQlhXdHh0WGxJeFZZa0pHT1dVaUVmM25zby02aXZKdWw2YmNmWHdMVlFIayIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWxob3N0OjgwODAifQ"
}
},
"passkeyName": "Google Pixel"
}'
```
You have successfully registered a new passkey to the user.
Next step is to authenticate the user with the new registered passkey.
## Login with Passkey
### Flow
![](../../../../public/img/guides/login-ui/passkey-login-flow.png)
### Create Session
First step is to ask the user for his username and create a new session with the ZITADEL API.
When creating the new session make sure to include the challenge for passkey, resp. webAuthN with a required user verification and the domain of your login UI.
The response will include the public key credential request options for the passkey in the challenges.
More detailed information about the API: [Create Session Documentation](/reference/api/session/zitadel.session.v2.SessionService.CreateSession)
Example Request:
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/sessions \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"checks": {
"user": {
"loginName": "[email protected]"
}
},
"metadata": {},
"challenges": {
"webAuthN": {
"domain": "example.domain.com",
"userVerificationRequirement": "USER_VERIFICATION_REQUIREMENT_REQUIRED"
}
}
}'
```
Example Response:
```bash
{
"details": {
"sequence": "2",
"changeDate": "2023-06-27",
"resourceOwner": "69629023906488334"
},
"sessionId": "d654e6ba-70a3-48ef-a95d-37c8d8a7901a",
"sessionToken": "string",
"challenges": {
"webAuthN": {
"publicKeyCredentialRequestOptions": {
"publicKey": {
"allowCredentials": [
{
"id": "ATmqBg-99qyOZk2zloPdJQyS2R7IkFT7v9Hoos_B_nM",
"type": "public-key"
}
],
"challenge": "GAOHYz2jE69kJMYo6Laij8yWw9-dKKgbViNhfuy0StA",
"rpId": "example.domain.com",
"timeout": 300000,
"userVerification": "required"
}
}
}
}
}
```
### Signing in Browser
<BrowserSigninWebAuthN components={props.components} />
### Update Session with WebAuthN
<UpdateSessionWebAuthN components={props.components} />
@@ -0,0 +1,109 @@
---
title: Password Reset/Change in a Custom Login UI
sidebar_label: Password Reset/Change
---
When a user is on the password screen and has forgotten the password you will probably want them to be able to reset it by themselves.
## Flow
![](../../../../public/img/guides/login-ui/password-reset-flow.png)
## Request Password Reset
First you will have to make a request, to ask for a password reset.
The goal is to send the user a verification code, which can be entered to verify the password reset request.
There are two possible ways: You can either let ZITADEL send the notification with the verification code, or you can ask ZITADEL for returning the code and send the email by yourself.
[Request Password Reset Documentation](/reference/api/user/zitadel.user.v2.UserService.PasswordReset)
### ZITADEL sends the verification message
When you want ZITADEL to send the verification code you can define the notification channel.
Per default the verification code will be sent to the email address of the user.
Make sure to also include the URL Template to customize the reset link in the email sent to the user.
### Request
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/users/$USER_ID/password_reset \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"'' \
--header 'Content-Type: application/json' \
--data '{
"sendLink": {
"notificationType": "NOTIFICATION_TYPE_Email",
"urlTemplate": "https://example.com/password/changey?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}"
}
}'
```
### ZITADEL returns the code
Send the request with asking for the return Code in the body of the request.
#### Request
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/users/$USER_ID/password_reset \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"'' \
--header 'Content-Type: application/json' \
--data '{
"returnCode": {}
}'
```
#### Response
You will get the verification code in the response:
```bash
{
"details": {
"sequence": "625",
"changeDate": "2023-06-27T15:02:10.321773Z",
"resourceOwner": "163840776835432705"
},
"verificationCode": "IBJMUC"
}
```
## Send Verification Code
The verification code is generated and ZITADEL has sent it with the defined channel (email or sms) to your users.
If you have chosen to get the code back in the response, you should now send the code to your user.
## Change Password
The next screen should allow the user to enter the verification code and a new password.
From a user experience perspective it is nice to prefill the verification code, so the user doesn't have to do manually.
As soon as the user has typed the new password, you can send the change password request.
The change password request allows you to set a new password for the user.
[Change Password Documentation](/reference/api/user/zitadel.user.v2.UserService.SetPassword)
<Callout>
This request can be used in the password reset flow as well as to let your user change the password manually.
In this case it requires additionally the current password instead of the verification code.
</Callout>
### Request
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/users/$USER_ID/password \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"'' \
--header 'Content-Type: application/json' \
--data '{
"newPassword": {
"password": "Secr3tP4ssw0rd!",
"changeRequired": false
},
"verificationCode": "48CDAP"
}'
```
@@ -0,0 +1,167 @@
---
title: Support for the SAML Standard in a Custom Login UI
sidebar_label: SAML Standard
---
To build your own login ui for your own application it is not necessary to have the SAML standard included or any additional work that has to be done.
However, it might make sense, if you want to connect your login to different applications especially if they are not in your control and they rely on the standard.
The following flow shows you the different components you need to enable SAML for your login.
![](../../../../public/img/guides/login-ui/saml-flow.png)
1. Your application makes an SAML request to your login UI
2. The login UI proxies the request to the ZITADEL API.
3. The ZITADEL API parses the request and does what it needs to interpret certain parameters (e.g., binding, nameID policy, etc.)
4. Redirect to a predefined, relative URL of the login UI that includes the samlrequest ID ("/login?authRequest="), configurable per application.
5. Request to ZITADEL API to get all the information from the SAML request. This is optional and only needed if you like to get all the parsed information from the samlrequest-
6. Authenticate the user in your login UI by creating and updating a session with all the checks you need.
7. Finalize the SAML request by sending the session to the request, you will get the URL to redirect to or the body in the response
8. Redirect to your application with the URL or call the application with HTTP Post with the content you got in the previous request
9. All SAML-specific endpoints have to be accepted in the Login UI and should be proxied and sent to the ZITADEL API
## Example
Let's assume you host your login UI on the following URL:
```
https://login.example.com
```
## SAML Request
A user opens your application and is unauthenticated, the user will then be redirected to your login with the following SAML Request:
```
https://login.example.com/saml/v2/SSO?SAMLRequest=nJLRa9swEMb%2FFXHvjmVTY0fUhqxhLNCtoc72sLerdFkEspTpzt3634%2BkGXQw8tBX6X76vk%2F33TJO4WhWsxziI%2F2ciUX9nkJkc7roYc7RJGTPJuJEbMSacfX53tQLbZCZsvgU4Q1yvM4cc5JkUwC1WffgXdHW1c2%2BclVtK6yc68hVnWvssqVm79p23za01E%2BuA%2FWNMvsUe6gXGtSGeaZNZMEoPdS6bgpdFbrd6aW50abuFk3Tfge1JhYfUc7kQeRoyjIki%2BGQWEynO12ebJfPdTmOD6BWf0PdpcjzRHmk%2FOwtfX28%2Fy%2BvLzxaBrW9pPvgo%2FPxx%2FWveHodYvNpt9sW24dxB8N5HeacLauPKU8o1x85nXhX7M%2BjhqJ4eYHhis%2BJBB0K3pZvpIZLDb7gRJv1NgVvX94hLxkje4oCahVC%2BnWXCYV6kDwTlMOr5L9lG%2F4EAAD%2F%2Fw%3D%3D&RelayState=CncN92gdF6is7bak63thXOsn0MmJn7CLQeGKWaXZo2L8nJN0sPEHbb4I
```
The SAML request includes all the relevant information for the SAML standard, which includes the RelayState, the used binding and other information.
You now have to proxy the SAML request from your own UI to the SSO Endpoint of ZITADEL.
For more information, see [OIDC Proxy](./login-app#oidc-proxy) for the necessary headers.
<Callout>
The version and the optional custom URI for the available login UI is configurable under the application settings.
</Callout>
Read more about the [SSO Endpoint Documentation](/apis/saml/endpoints#sso-endpoint)
The endpoint will redirect you to the domain of your UI on the path /login and add the SAML Request ID as parameter.
```https://login.example.com/login?authRequest=V2_224908753244265546```
### Get SAML Request by ID
With the ID from the redirect before you will now be able to get the information of the SAML request.
[Get SAML Request By ID Documentation](/reference/api/saml/zitadel.saml.v2.SAMLService.GetSAMLRequest)
```bash
curl --request GET \
--url https://${CUSTOM_DOMAIN}/v2/saml/saml_requests/V2_224908753244265546 \
--header 'Authorization: Bearer '"$TOKEN"''
```
Response Example:
```json
{
"samlRequest": {
"id": "V2_224908753244265546",
"creationDate": "2023-07-28T13:47:43.471505Z",
"issuer": "https://myapp.example.com",
"assertionConsumerService": "https://myapp.example.com/acs",
"relayState": "CncN92gdF6is7bak63thXOsn0MmJn7CLQeGKWaXZo2L8nJN0sPEHbb4I",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
}
}
```
### Perform Login
After you have initialized the SAML flow you can implement the login.
Implement all the steps you like the user to go trough by [creating](/reference/api/session/zitadel.session.v2.SessionService.CreateSession) and [updating](/reference/api/session/zitadel.session.v2.SessionService.SetSession) the user-session.
Read the following resources for more information about the different checks:
- [Username and Password](./username-password)
- [External Identity Provider](./external-login)
- [Passkeys](./passkey)
- [Multi-Factor](./mfa)
### Finalize SAML Request
To finalize the SAML request and connect an existing user session with it, you have to update the SAML Request with the session token.
On the create and update user session request you will always get a session token in the response.
The latest session token has to be sent to the following request:
Read more about the [Finalize SAML Request Documentation](/reference/api/saml/zitadel.saml.v2.SAMLService.CreateResponse)
Make sure that the authorization header is from an account which is permitted to finalize the SAML Request through the `IAM_LOGIN_CLIENT` role.
```bash
curl --request POST \
--url ${CUSTOM_DOMAIN}/v2/saml/saml_requests/V2_224908753244265546 \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"session": {
"sessionId": "225307381909694508",
"sessionToken": "7N5kQCvC4jIf2OuBjwfyWSX2FUKbQqg4iG3uWT-TBngMhlS9miGUwpyUaN0HJ8OcbSzk4QHZy_Bvvv"
}
}'
```
In the response you will get content which describe how to handle further steps from your login UI.
#### POST-Binding
Example Response POST-binding:
```json
{
"details": {
"sequence": "686",
"changeDate": "2023-07-31T08:09:19.314537Z",
"resourceOwner": "163840776801878273"
},
"url": "https://myapp.example.com/acs",
"binding": {
"post": {
"relayState": "CncN92gdF6is7bak63thXOsn0MmJn7CLQeGKWaXZo2L8nJN0sPEHbb4I",
"samlResponse": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPFJlc3Bv..."
}
}
}
```
Which expects your client to do a POST call to the URL `https://myapp.example.com/acs` with the payload containing the following attributes:
```
RelayState: "CncN92gdF6is7bak63thXOsn0MmJn7CLQeGKWaXZo2L8nJN0sPEHbb4I",
SAMLResponse: "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPFJlc3Bv..."
```
#### Redirect-Binding
Example Response Redirect-binding:
```json
{
"details": {
"sequence": "686",
"changeDate": "2023-07-31T08:09:19.314537Z",
"resourceOwner": "163840776801878273"
},
"url": "https://myapp.example.com/acs?RelayState=CncN92gdF6is7bak63thXOsn0MmJn7CLQeGKWaXZo2L8nJN0sPEHbb4I&SAMLResponse=PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPFJlc3Bv...",
"binding": {
"redirect": {}
}
}
```
Which expects your client to redirect to the url 'https://myapp.example.com/acs?RelayState=...&SAMLResponse=...' with all information as URL parameters.
### SAML Endpoints
All SAML relevant endpoints are provided by ZITADEL. In your login UI you just have to proxy them through and send them directly to the backend.
These are endpoints like:
- Metadata
- Certificate
- ACS
- etc
@@ -0,0 +1,7 @@
---
title: Select Account in a Custom Login UI
---
import SelectAccount from './_select-account.mdx';
<SelectAccount components={props.components} />
@@ -0,0 +1,110 @@
---
title: How to Handle Session Validation
sidebar_label: Session Validation
---
Sessions represent the state of a user session in ZITADEL. They can be aggregated and updated over time to reflect
the changes.
For example, a session could be started by checking a username and ZITADEL will return a new session
with some information about the user like their id and loginName as well as details about the session itself.
Later on, if the user's password was checked successfully, ZITADEL will update the session and provide the date and time
when the password was verified.
## Verification
Since sessions just represent the current state, it's the responsibility of the client to check whether that state is
sufficient. This also gives you some opportunities, e.g. if you have different requirements for different use cases.
Let's jump into the example from above and assume you have a simple login UI, which just requires the user to verify
their [username and password](./username-password#create-session-with-user-check).
If a user successfully authenticated using username and password, a session could look like:
```json
{
"session": {
"id": "218480890961985793",
"creationDate": "2023-06-14T05:32:38.977954Z",
"changeDate": "2023-06-14T05:42:11.631901Z",
"sequence": "582",
"factors": {
"user": {
"verifiedAt": "2023-06-14T05:32:38.972712Z",
"id": "d654e6ba-70a3-48ef-a95d-37c8d8a7901a",
"loginName": "[email protected]",
"displayName": "Minnie Mouse"
},
"password": {
"verifiedAt": "2023-06-14T05:42:11.619484Z"
}
}
}
}
```
Your application would then need to check whether these `factors` are enough, esp. if the `verifiedAt` of both are within
an acceptable time range.
To get the current state of the session, you can call the [GetSession endpoint](/reference/api/session/zitadel.session.v2.SessionService.GetSession),
resp. you can get several by [searching sessions](/reference/api/session/zitadel.session.v2.SessionService.ListSessions).
## Expiration
The session API allows you to expire sessions. Like previous with the factor's `verfifiedAt`, you can check the
`expirationDate` and decide if you want to accept the session or not.
```json
{
"session": {
"id": "218480890961985793",
"creationDate": "2023-06-14T05:32:38.977954Z",
"changeDate": "2023-06-14T05:42:11.631901Z",
"sequence": "582",
"factors": {
"user": {
"verifiedAt": "2023-06-14T05:32:38.972712Z",
"id": "d654e6ba-70a3-48ef-a95d-37c8d8a7901a",
"loginName": "[email protected]",
"displayName": "Minnie Mouse"
},
"password": {
"verifiedAt": "2023-06-14T05:42:11.619484Z"
}
},
"expirationDate": "2023-06-14T10:42:11.619484Z"
}
}
```
You may have some cases where you even want to make sure the session does not expire within few seconds or other cases,
where you might use some information of an expired session like the user's `loginName` to automatically create a new
session in your Login UI and let the user only provide their password.
<Callout>
Note that ZITADEL will reject any expired sessions automatically and that they cannot be updated anymore.
</Callout>
### Set a lifetime
In order to expire a session, a `lifetime` duration can be set when creating or updating a session.
Note that each time a session is updated with a `lifetime` attribute set, the `expirationDate` will newly be calculated
from this point in time. This give you the opportunity to change the expiration, based on the successful `factors` of
the session.
```json
{
"lifetime": "18000.000000000s"
}
```
<Callout>
Note that if the `lifetime` was not set, the session will never expire.
</Callout>
## Token Introspection
If you're relying on OAuth Token Introspection in your API, Session Tokens can't be used directly, since they
do not include all the necessary information for that. You'll need to exchange it first into an access token.
You can do so by guiding your user through the Login UI, where a session token will be required to finalize the
auth request. Check out the guide on [support for OIDC](./oidc-standard).
@@ -0,0 +1,243 @@
---
title: Register and Login User with Password in a Custom Login UI
sidebar_label: Username and Password
---
import SelectAccount from './_select-account.mdx';
import Logout from './_logout.mdx';
## Flow
![](../../../../public/img/guides/login-ui/username-password-flow.png)
## Register
First, we create a new user with a username and password. In the example below we add a user with profile data, a verified email address, and a password.
[Create User Documentation](/reference/api/user/zitadel.user.v2.UserService.AddHumanUser)
### Custom Fields
If you have custom fields you like to add to your users that are not provided by ZITADEL, you can add them to the metadata.
Metadata are key value pairs you can use for additional user data.
These fields can also be included in the token of the user, so you have access to it all the time.
Read more about the metadata [here](/guides/manage/customize/user-metadata)
### Request
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/users/human \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"'' \
--header 'Content-Type: application/json' \
--data '{
"userId": "d654e6ba-70a3-48ef-a95d-37c8d8a7901a",
"username": "minnie-mouse",
"profile": {
"givenName": "Minnie",
"familyName": "Mouse",
"nickName": "Mini",
"displayName": "Minnie Mouse",
"preferredLanguage": "en",
"gender": "GENDER_FEMALE"
},
"email": {
"email": "[email protected]",
"isVerified": true
},
"metadata": [
{
"key": "my-key",
"value": "VGhpcyBpcyBteSB0ZXN0IHZhbHVl"
}
],
"password": {
"password": "Secr3tP4ssw0rd!",
"changeRequired": false
}
}'
```
### Response
```bash
{
"userId": "d654e6ba-70a3-48ef-a95d-37c8d8a7901a",
"details": {
"sequence": "570",
"changeDate": "2023-06-13T12:44:36.967851Z",
"resourceOwner": "163840776835432705"
}
}
```
If you want the user to verify the email address you can either choose that ZITADEL sends a verification email to the user by adding a urlTemplate into the sendCode, or ask for a return code to send your own emails.
Send Email by ZITADEL:
```bash
"sendCode": {
"urlTemplate": "https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}"
},
```
Return Code:
```bash
"email": {
"email": "[email protected]",
"returnCode": {}
},
```
To check what is allowed on your instance, call the settings service for more information.
The following requests can be useful for registration:
- [Get Login Settings](/reference/api/settings/zitadel.settings.v2.SettingsService.GetLoginSettings) To find out which authentication possibilities are enabled (password, identity provider, etc.)
- [Get Password Complexity Settings](/reference/api/settings/zitadel.settings.v2.SettingsService.GetPasswordComplexitySettings) to find out how the password should look like (length, characters, etc.)
## Create Session with User Check
After you have created a new user, you can redirect them to your login screen.
You can either create a new empty session as soon as the first login screen is shown or update it with each piece of information you get throughout the process.
Or you can create a new session with the first credentials a user enters.
In the following example, we assume that the login flow asks for the username in the first step, and in the second for the password.
In API requests, this means creating a new session with a username and updating it with the password.
If you already have the userId from a previous register, you can send it directly to skip the username and show the password screen directly.
The create and update session endpoints will always return a session ID and an opaque session token.
If you do not rely on the OIDC standard you can directly use the token.
Send it to the Get Session Endpoint to find out how the user has authenticated.
- [Create new session Documentation](/reference/api/session/zitadel.session.v2.SessionService.CreateSession)
- [Update an existing session Documentation](/reference/api/session/zitadel.session.v2.SessionService.SetSession)
- [Get Session Documentation](/reference/api/session/zitadel.session.v2.SessionService.GetSession)
### Request
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/v2/sessions \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"'' \
--header 'Content-Type: application/json' \
--data '{
"checks": {
"user": {
"loginName": "[email protected]"
}
}
}'
```
### Response
```bash
{
"details": {
"sequence": "580",
"changeDate": "2023-06-14T05:32:39.007096Z",
"resourceOwner": "163840776835432705"
},
"sessionId": "218480890961985793",
"sessionToken": "yMDi6uVPJAcphbbz0LaxC07ihWkNTe7m0Xqch8SzfM5Cz3HSIQIDZ65x1f5Qal0jxz0MEyo-_zYcUg"
}
```
### Session State
If you read the newly created session, it will look like the following.
You can see the creation and change date.
In the factors, you will see all the checks that have been made.
In this case, the user has been checked.
```bash
{
"session": {
"id": "218480890961985793",
"creationDate": "2023-06-14T05:32:38.977954Z",
"changeDate": "2023-06-14T05:32:39.007096Z",
"sequence": "580",
"factors": {
"user": {
"verifiedAt": "2023-06-14T05:32:38.972712Z",
"id": "d654e6ba-70a3-48ef-a95d-37c8d8a7901a",
"loginName": "[email protected]",
"displayName": "Minnie Mouse"
}
}
}
}
```
Checkout how to handle [session validation](./session-validation).
## Update Session with Password
Your session already has a username check.
The next step is to check the password.
To update an existing session, add the session ID you got in the previous step to the URL.
### Request
```bash
curl --request PATCH \
--url https://${CUSTOM_DOMAIN}/v2/sessions/$SESSION_ID \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"checks": {
"password": {
"password": "Secr3tP4ssw0rd!"
}
}
}'
```
### Response
The response of the create and update session token looks the same.
Make sure to always use the session token of the last response you got, as the values may be updated.
```bash
{
"details": {
"sequence": "582",
"changeDate": "2023-06-14T05:42:11.631901Z",
"resourceOwner": "163840776835432705"
},
"sessionToken": "blGKerGQPKv8jN21p6E9GB1B-vl6_EyKlvTd5UALu8-aQmjucgZxHSXJx3XMFTwT9_Y3VnbOo3gC_Q"
}
```
### Session State
If you read your session after the password check, you will see that the check has been added to the factors with the verification date.
```bash
{
"session": {
"id": "218480890961985793",
"creationDate": "2023-06-14T05:32:38.977954Z",
"changeDate": "2023-06-14T05:42:11.631901Z",
"sequence": "582",
"factors": {
"user": {
"verifiedAt": "2023-06-14T05:32:38.972712Z",
"id": "d654e6ba-70a3-48ef-a95d-37c8d8a7901a",
"loginName": "[email protected]",
"displayName": "Minnie Mouse"
},
"password": {
"verifiedAt": "2023-06-14T05:42:11.619484Z"
}
}
}
}
```
## List the Sessions (Account Chooser)
<SelectAccount components={props.components} />
## Logout User
<Logout components={props.components} />
@@ -0,0 +1,4 @@
<Callout type="warn" title="Don't Lock Yourself Out of Your Instance">
Before you change your Zitadel configuration, we highly recommend you to create a service user with a personal access token (PAT) and the IAM_OWNER role.
In case something breaks, you can use this PAT to revert your changes or fix the configuration so you can use a login UI again.
</Callout>
@@ -0,0 +1,199 @@
---
title: Login users into your application with a hosted login UI
sidebar_label: Hosted Login UI
---
import PreventLockout from './_prevent-lockout.mdx';
ZITADEL provides a hosted single-sign-on page to securely sign-in users to your applications.
ZITADEL's hosted login page serves as a centralized authentication interface provided for applications that integrate ZITADEL.
As a developer, understanding the hosted login page is essential for seamlessly integrating authentication into your application.
## Centralized authentication endpoint
ZITADEL's hosted login page acts as a centralized authentication endpoint where users are redirected to authenticate themselves.
When users attempt to access a protected resource within your application, you can redirect them to the hosted login page to authenticate using their login methods and credentials or through Single-sign-on (SSO).
After successful authentication, the user will be redirected back to the originating application.
## Security and compliance
ZITADEL's hosted login page prioritizes security and compliance with industry standards and regulations.
It employs best practices for securing authentication processes, such as encryption, token-based authentication, and adherence to protocols like OAuth 2.0, [OpenID Connect](/guides/integrate/login/oidc), and [SAML](/guides/integrate/login/saml).
We make sure to harden the login UI and minimize the attack surface.
One of the measures we apply is setting the necessary security heads thus minimizing the risk of common vulnerabilities in login pages, such as XSS vulnerabilities.
Put your current login to the test and compare the results with our hosted login page.
Tools like [Mozilla's Observatory](https://observatory.mozilla.org/) can give you a good first impression about the security posture.
## Developer-friendly integration
Integrating the hosted login page into your application is straightforward, thanks to ZITADEL's developer-friendly documentation, SDKs, and APIs. Developers can easily implement authentication flows, handle authentication callbacks, and customize the user experience to seamlessly integrate authentication with their application's workflow.
Overall, ZITADEL's hosted login page simplifies the authentication process for developers by providing a secure, customizable, and developer-friendly authentication interface. By leveraging this centralized authentication endpoint, developers can enhance their application's security, user experience, and compliance with industry standards and regulations.
## Key features of the hosted login
### Flexible usernames
Different login name formats can be used on ZITADEL's hosted login page to select a user.
Login methods can be a user's username, containing the username and an [organization domain](/guides/manage/console/organizations-overview#usernames-and-domains), their email addresses, or their phone numbers.
By default, all of these login methods are allowed and can be adjusted by [Managers](/concepts/structure/managers) to meet their requirements.
### Support for multiple authentication methods
The hosted login page supports various authentication methods, including traditional username/password authentication, social login options, multi-factor authentication (MFA), and passkey authentication method.
The second factor (2FA) and multi-factor authentication methods (MFA) available in ZITADEL include OTP via an authenticator app, TOTP via SMS, OTP via email, and U2F.
Developers can configure the authentication methods offered on the login page based on their application's security and usability requirements.
### Enterprise single-sign-on
![Screenshot of ZITADEL management console showing different identity provider templates](../../../../public/img/guides/integrate/login/login-external-idp-templates.png)
With the hosted login page from ZITADEL developers will get the best support for multi-tenancy single-sign-on with third-party identity providers.
ZITADEL acts as an [identity broker](/concepts/features/identity-brokering) between your applications and different external identity providers, reducing the implementation effort for developers.
External Identity providers can be configured for the whole instance or for each organization that represents a group of users such as a B2B customer or organizational unit.
ZITADEL offers various [identity provider templates](/guides/integrate/identity-providers/introduction) to integrate providers such as [Okta](/guides/integrate/identity-providers/okta-oidc), [Entra ID](/guides/integrate/identity-providers/azure-ad-oidc) or on-premise [LDAP](/guides/integrate/identity-providers/ldap).
### Multi-tenancy authentication
ZITADEL simplifies multi-tenancy authentication by securely managing authentication for multiple tenants, called [Organizations](/guides/manage/console/organizations-overview), within a single [instance](/guides/manage/console/organizations-overview), within a single [instance](/concepts/structure/instance).
Key features include:
1. **Secure Tenant Isolation**: Ensures robust security measures to prevent unauthorized access between tenants, maintaining data privacy and compliance. [Managers](/concepts/structure/managers) for an organization have only access to data and configuration within their Organization.
2. **Custom Authentication Configurations**: Allows tailored [authentication settings](/guides/manage/console/default-settings#login-behavior-and-access), [branding](/guides/manage/customize/branding), and policies for each tenant.
3. **Centralized Management**: Provides [centralized administration](/guides/manage/console/managers) for efficient management across all tenants.
4. **Scalability and Flexibility**: Scales seamlessly to accommodate growing organizations of all sizes.
5. **Domain Discovery**: Starting on a central login page, route users to their tenant based on their email address or other user attributes. Authentication settings will be applied automatically based on the organization's policies, this includes routing users seamlessly to third party identity providers like [Entra ID](/guides/integrate/identity-providers/azure-ad-oidc).
### Customization options
While the hosted login page provides a default authentication interface out-of-the-box, ZITADEL offers [customization options](/guides/manage/customize/branding) to tailor the login page to match your application's branding and user experience requirements.
Developers can customize elements such as logos, colors, and messaging to ensure a seamless integration with their application's user interface.
<Callout type="info">
Customization and Branding
The login page can be changed by customizing different branding aspects and you can define a Custom Domain for the login (eg, login.acme.com).
By default, the displayed branding is defined [based on the user's domain](/guides/solution-scenarios/domain-discovery). In case you want to show the branding of a specific organization by default, you need to either pass a primary domain scope (`urn:zitadel:iam:org:domain:primary:{domainname}`) with the authorization request, or define the behavior on your Project's settings.
</Callout>
### Fast account switching
The hosted login page remembers users who have previously authenticated.
In case a user has used multiple accounts, for example, a private account and a work account, to authenticate, then all accounts will be shown on the Account Picker.
Users can still login with a different user that is not on the list.
This allows users to quickly switch between users and provide a better user experience.
<Callout>
This behavior can be changed with the authorization request. Please refer to our [guide](/guides/integrate/login/oidc/login-users).
</Callout>
### Self-service for users
ZITADEL's hosted login page offers [many self-service flows](/concepts/features/selfservice) that allow users to set up authentication methods or recover their login information.
Developers use the self-service functionalities to reduce manual tasks and improve user experience.
Key features include:
### Password reset
Unauthenticated users can request a password reset after providing the loginname during the login flow.
- User selects reset password
- An email will be sent to the verified email address
- User opens a link and has to provide a new password
#### Prompt users to set up multifactor authentication
Users are automatically prompted to provide a second factor, when
- Instance or organization [login policy](/guides/manage/console/default-settings#login-behavior-and-access) is set
- Requested by the client
- A multi-factor is set up for the user
When a multi-factor is required, but not set up, then the user is requested to set up an additional factor.
<Callout title="Disabling multifactor prompt">
You can disable the prompt, in case multifactor authentication is not enforced by setting the [**Multifactor Init Lifetime**](/guides/manage/console/default-settings#login-lifetimes) to 0.
</Callout>
#### Enroll passkeys
Users can select a button to initiate passkey login or use a fall-back method (ie. login with username/password), if available.
The [passkeys](/concepts/features/passkeys) login flow follows the FIDO2 / WebAuthN standard.
With the introduction of passkeys the gesture can be provided on ANY of the user's devices.
This is not strictly the device where the login flow is being executed (e.g., on a mobile device).
The user experience depends mainly on the operating system and browser.
## Hosted Login Version 2
We have worked on a new, self-hostable implementation of our hosted MIT licensed login built with Next.js and leveraging our [Session API](/guides/integrate/login/login-users#zitade-ls-session-api).
This solution empowers you to easily customize the login experience to perfectly match your brand and needs.
### Limitations
For the first implementation we have excluded the following features:
- Generic JWT IDP
- LDAP IDP
- Device Authorization Grants
- Force MFA on external authenticated users
- Passkey/U2F Setup
- As passkey and u2f is bound to a domain, it is important to notice, that setting up the authentication possibility in the ZITADEL management console (Self-service), will not work if the login runs on a different domain
- Custom Login Texts
### Important Changes
- **Create Users:** The new TypeScript Login app is built with the session and the user V2 API, the users V2 API does have some differences to the v1 API, so make sure you create users through the new API.
- **External IDPs:** If you want to use external identity provider login, such as Login with Google or Apple. You can follow our existing setup guides, just make sure to use the following redirect url: `${CUSTOM_DOMAIN}/idps/callback`
- **Passkey/U2F:** Those authentication methods are bound to a domain. As your new login runs on a different domain than the previous login, existing passkey authentication (fingerprint, face id, etc.) can’t be used. Also when they are managed through the management console of ZITADEL, they are added on a different domain.
<br />
*Note: If you run the login on a subdomain of your current instance, this problem
can be avoided. E.g myinstance.zitadel.cloud and login.myinstance.zitadel.cloud*
#### Step-by-step Guide
**Using the new login:** To use the new login without changing your current setup, the easiest way is to visit `https://${CUSTOM_DOMAIN}/ui/v2/login` on your Zitadel Cloud Custom Domain.
You can also activate the v2 login for your apps, so users are redirected to `/ui/v2/login` for authentication.
**Customizing the new login:** The easiest way to actually customizing it is to fork the https://github.com/zitadel/zitadel repo and use the [Deploy with Vercel button](https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#login-deploy) to run your login code on Vercel.
<Tabs queryString="deployment">
<Tab value="zitadel_cloud" label="Activate Zitadel Cloud V2 Login" default="true">
Zitadel Cloud provides the V2 login at the fixed path `/ui/v2/login`.
<PreventLockout components={props.components} />
To activate it to authenticate on your Zitadel Cloud apps, you can follow one of these steps:
1. Enable the new login on your application configuration. Leave the field **Custom base URL for the new Login UI** empty to use the default. With these settings, Zitadel will automatically redirect you to the new login if you call the old one.
![](../../../../public/img/guides/integrate/login/login-v2-app-config.png)
2. Enable the [loginV2 feature](/reference/api/feature/zitadel.feature.v2.FeatureService.SetInstanceFeatures) on the instance. Leave the base URI empty to use the default. If you enable this feature, the login will be used for every application configured in your Zitadel instance. (Example: https://your-zitadel-instance.zitadel.cloud/ui/v2/login)
</Tab>
<Tab value="vercel_custom" label="Vercel Custom Login Deployment">
The simplest way to deploy the new login for yourself is by using the [Deploy with Vercel button](https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#login-deploy) to deploy the login directly to your Vercel.
1. [Create a service user](/guides/integrate/service-users/personal-access-token#create-a-service-user-with-a-pat) with a PAT in your instance
2. Give the user IAM_LOGIN_CLIENT Permissions in the default settings (CUSTOM_DOMAIN/ui/console/instance?id=organizations)
Note: [Zitadel Manager Guide](/guides/manage/console/managers)
3. Deploy login to Vercel: You can do so by directly clicking the [“Deploy” button](https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#login-deploy) in our [repository](https://github.com/zitadel/zitadel)
4. If you have used the deploy button in the steps before, you will automatically be asked for this step. Enter the environment variables in Vercel
- PAT
- ZITADEL_API_URL (Example: https://my-domain.zitadel.cloud, no trailing slash)
5. Add the domain where your login UI is hosted to the [Trusted Domains](https://zitadel.com/docs/apis/resources/admin/admin-service-add-instance-trusted-domain) in Zitadel. (Example: my-new-zitadel-login.vercel.app)
6. Use the new login in your application.
<PreventLockout components={props.components} />
You have three different options on how to achieve this:
1. Enable the new login on your application configuration and add the URL of your login UI. With these settings, Zitadel will automatically redirect you to the new login if you call the old one.
![](../../../../public/img/guides/integrate/login/login-v2-app-config.png)
2. Enable the [loginV2 feature](/reference/api/feature/zitadel.feature.v2.FeatureService.SetInstanceFeatures) on the instance and add the URL of your login. If you enable this feature, the login will be used for every application configured in your Zitadel instance. (Example: https://my-new-zitadel-login.vercel.app)
3. Change the issuer in the code of your application to the new domain of your login
7. Enforce users to have their email verified. By setting `EMAIL_VERIFICATION` to `true` in your environment variables, your users will be enforced to verify their email address before they can log in.
</Tab>
</Tabs>
@@ -0,0 +1,17 @@
---
title: "Login users with ZITADEL"
description: "Sign-in users and application with ZITADEL. In this section you will find resources on how to authenticate your users by using the hosted login via OpenID Connect and SAML. Follow our dedicated guides to build your custom login user interface, if you want to customize the login behavior further."
---
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
import { Card, Cards } from 'fumadocs-ui/components/card';
import { FileText, Folder, Link as LinkIcon } from 'lucide-react';
<Cards>
<Card title="Login Users" href="/guides/integrate/login/login-users" icon={<FileText />} />
<Card title="Hosted Login" href="/guides/integrate/login/hosted-login" icon={<FileText />} />
<Card title="Logout" href="/guides/integrate/login/oidc/logout" icon={<FileText />} />
</Cards>
@@ -0,0 +1,127 @@
---
title: Log users into your application with different authentication options
sidebar_label: Authentication Options
---
ZITADEL is a comprehensive identity and access management platform designed to streamline user authentication, authorization, and management processes for your application. It offers a range of features, including single sign-on (SSO), multi-factor authentication (MFA), and centralized user management.
With ZITADEL, you can securely authenticate users using industry-standard protocols such as OpenID Connect and SAML. This enables seamless integration with various applications and services, providing a unified authentication experience for your users.
Besides federated authentication with OpenID Connect and SAML, ZITADEL offers an API to authenticate users allowing you to create your own login process and user interface.
In this guide, we will walk through the different protocols, features and concepts that can be used to login users securely into your applications.
![](../../../../public/img/guides/integrate/login/login-start.png)
## Using industry-standard protocols
### Authenticate users with OpenID Connect 1.0
OpenID Connect (OIDC) offers a modern and lightweight authentication protocol built on top of OAuth 2.0, providing flexible authentication flows and easy integration with web and mobile applications.
ZITADEL offers a certified compliant implementation of the OpenID Connect Standard, ensuring compliance with proven security best practices.
Authenticating users through the OpenID Connect protocol typically requires an application to redirect the user with an [Auth Request](https://zitadel.com/playgrounds/oidc) to the identity provider that contains information such as the requesting application, [scopes](/apis/openidoauth/scopes), and redirect url.
The identity provider is not part of the original application, but a standalone service like ZITADEL that may run under the [same domain](/concepts/features/custom-domain)
The user will authenticate using their credentials.
After successful authentication, the user will be redirected back to the original application.
If you want to read more about authenticating with OIDC, head over to our comprehensive [OpenID Connect Guide](/guides/integrate/login/oidc).
### Authenticate users with SAML
SAML (Security Assertion Markup Language) is a widely adopted standard for exchanging authentication and authorization data between identity providers and service providers.
Authentication with SAML (Security Assertion Markup Language) involves a series of exchanges between a service provider (SP), an identity provider (IdP), and the user. Here's an overview of how the process typically works:
1. **User Attempts to Access a Service**: The user tries to access a service or application that requires authentication, such as logging into a web application.
2. **Service Provider Redirects to Identity Provider**: The service provider (SP) detects that the user needs to be authenticated and redirects the user's browser to the designated identity provider (IdP) for authentication. This redirection often occurs via a SAML request, which includes information about the service provider and a request for authentication.
3. **User Authenticates with Identity Provider**: The user is presented with the identity provider's login page, where they enter their credentials (username and password). Alternatively, depending on the IdP's configuration, the user might be authenticated using single sign-on (SSO) mechanisms such as a pre-existing session or multi-factor authentication.
4. **Identity Provider Generates SAML Assertion**: Upon successful authentication, the identity provider creates a SAML assertion containing information about the user, such as their identity and attributes. This assertion is digitally signed by the identity provider to ensure its integrity and authenticity.
5. **SAML Assertion Sent to Service Provider**: The identity provider sends the SAML assertion back to the user's browser as a response to the original SAML request. The browser then forwards the assertion to the service provider.
6. **Service Provider Validates SAML Assertion**: The service provider receives the SAML assertion and validates it to ensure its integrity and authenticity. This typically involves verifying the digital signature of the assertion and checking that it originated from a trusted identity provider.
7. **User Granted Access**: If the SAML assertion is successfully validated, the service provider considers the user authenticated and grants them access to the requested service or application. The user can now interact with the service without needing to reauthenticate until their session expires or they log out.
Overall, authentication with SAML provides a secure and standardized method for enabling single sign-on (SSO) and federated identity management across different applications and systems.
It allows users to access multiple services with a single set of credentials, streamlining the authentication process while maintaining strong security measures.
Note that SAML might not be suitable for mobile applications.
In case you want to integrate a mobile application, use OpenID Connect or our Session API.
There are more [differences between SAML and OIDC](https://zitadel.com/blog/saml-vs-oidc) that you might want to consider.
If you want to read more about authenticating with SAML, head over to our comprehensive [SAML Guide](/guides/integrate/login/saml).
## ZITADEL's Session API
ZITADEL's [Session API](/reference/api/session) provides developers with a straightforward method to manage user sessions within their applications.
The Session API is not an industry-standard and can be used instead of OpenID Connect or SAML to authenticate users by [building your own custom login user interface](/guides/integrate/login-ui).
### Tokens in the Session API
The session API will return a session token that can be used to authenticate users from your application.
This token should not be confused with am access or id tokens in opaque or JWT form that is issued during OpenID connect flows.
<Callout title="Token exchange">
Token exchange between Session API and OIDC / SAML tokens is not possible at this moment.
</Callout>
### Key features of the Session API
These are some key features of the API:
1. **Session Management**: The Session API allows you to create, retrieve, update, and delete sessions for users within your application. Sessions represent a user's active login session, providing temporary access to your application's resources.
2. **Creating Sessions**: With the Session API, you can initiate a new session for a user after they successfully authenticate through ZITADEL. This typically involves generating a session token or ID, which is then associated with the user's identity and used to track their session activity.
3. **Retrieving Sessions**: You can retrieve information about existing sessions using the Session API, such as session ID, user ID, creation time, and expiration time. This allows you to monitor active user sessions and retrieve session details as needed for auditing or troubleshooting purposes.
4. **Updating Sessions**: The Session API enables you to update session attributes or extend session lifetimes based on specific application requirements. Each authentication step, such as username / password check or multi-factor verification, will result in an update session. Also you might want to refresh a session token periodically to prevent it from expiring prematurely or update session metadata to reflect changes in user permissions or settings.
5. **Deleting Sessions**: When a user logs out or their session expires, you can use the Session API to delete the corresponding session from your application's records. This helps maintain data integrity and security by ensuring that inactive sessions are properly removed from the system.
Overall, ZITADEL's Session API simplifies session management within your application, providing a convenient and secure way to track user sessions, enforce session policies, and maintain a seamless user experience. By integrating the Session API into your application, you can effectively manage user authentication and access control while ensuring data privacy and security.
## Use the Hosted Login to sign-in users
ZITADEL provides a hosted single-sign-on page for secure user authentication within your applications.
This centralized authentication interface simplifies application integration by offering a ready-to-use login experience.
For a comprehensive understanding of the hosted login page and its capabilities, please refer to our [dedicated guide](/guides/integrate/login/hosted-login)
The hosted login is particularly well-suited for scenarios where:
- **Minimal branding is required:** If your primary focus is on functionality over a highly customized look and feel.
- **Standard authentication flows suffice:** Your application doesn't necessitate complex or unique authentication processes.
- **OIDC or SAML are suitable:** Your application integrates seamlessly with industry-standard protocols.
- **Time-to-market is critical:** You need a rapid and efficient authentication solution to accelerate your development timeline.
- **Embedding the login UI is unnecessary:** You prefer a separate, hosted login page for user authentication.
## Build a custom Login UI to authenticate users
In certain cases, you want to build your own login UI to optimize your user experience.
We have dedicated guides on [how to build your custom login UI](/guides/integrate/login-ui) with ZITADEL.
When building your own login UI, you will leverage the [Session API](#zitadel-session-api) to authenticate users and manage user sessions.
<Callout title="Session API support for OIDC and SAML">
At the moment developers can only integrate with ZITADEL's Session API and not with standard compliant OpenID Connect or SAML flows.
</Callout>
### When to build your custom Login UI
Main reasons why developers might want to build their own login UI include:
1. **Embedding Login in your application**: From a security standpoint you should follow the best practice recommendation to open a browser and then redirect to your application. For certain applications, such as games or mobile apps, you might want to embed the login for a better user experience
2. **Customize business process**: You might want to change existing flows that are provided by the hosted login to fit your business process. Make sure to validate that your customization can't be handled by [Actions](/guides/manage/customize/behavior) instead.
3. **Customize branding**: We designed the hosted login with security in mind, which limits the [customization capabilities](/guides/manage/customize/branding). Besides that we might not be able to handle all requirements for custom branding. If you believe your customization should be part of the hosted login instead, please open an [improvement idea](https://github.com/zitadel/zitadel/issues/new/choose).
4. **Independence of standards** You don't want or need to rely on industry standards for authentication such as OpenID Connect or SAML for authenticating users.
## Further reference
- Learn how to [register and onboard users](/guides/integrate/onboarding)
- Learn how to [build your own login UI](/guides/integrate/login-ui) based on ZITADEL
- Learn how to [configure ZITADEL](/guides/manage/console/console-overview) through the Management Console
@@ -0,0 +1,58 @@
import PKCE from "./authmethods/_pkce.mdx";
import Basic from "./authmethods/_basic.mdx";
import Implicit from "./authmethods/_implicit.mdx";
import PKCENative from "./authmethods/_pkcenative.mdx";
import JWTPrivateKey from "./authmethods/_jwtpk.mdx";
export default function AuthMethods(props) {
const { Tab, Tabs } = props.components || {};
return props.selected == "web" ? (
<div>
<Tabs
groupId="auth-type"
default="pkce"
values={[
{ label: "PKCE", value: "pkce" },
{ label: "Basic Auth", value: "basic" },
{ label: "JWT with Private Key", value: "jwtpk" },
]}
>
<Tab value="pkce">
<PKCE components={props.components} />
</Tab>
<Tab value="basic">
<Basic components={props.components} />
</Tab>
<Tab value="jwtpk">
<JWTPrivateKey components={props.components} />
</Tab>
</Tabs>
</div>
) : props.selected == "native" ? (
<Tabs
groupId="auth-type"
default="pkce"
values={[{ label: "PKCE", value: "pkce" }]}
>
<Tab value="pkce">
<PKCE components={props.components} Native />
</Tab>
</Tabs>
) : props.selected == "spa" ? (
<Tabs
groupId="auth-type"
default="pkce"
values={[
{ label: "PKCE", value: "pkce" },
{ label: "Implicit", value: "implicit" },
]}
>
<Tab value="pkce">
<PKCE components={props.components} />
</Tab>
<Tab value="implicit">
<Implicit components={props.components} />
</Tab>
</Tabs>
) : null;
}
@@ -0,0 +1,9 @@
---
title: Authentication Methods
---
import AuthMethods from './_authmethods.mdx';
ZITADEL supports various authentication methods for OIDC clients.
<AuthMethods components={props.components} />
@@ -0,0 +1,69 @@
#### redirect_uri
After selecting the authentication method, you can register a redirect_uri and post_logout_redirect_uri.
The redirect_uri will be called after user authentication for code exchange.
You can even register multiple, but typically one will be enough. If you need to distinguish between different scenarios
or environments we recommend using the `state` parameter for the former and multiple projects for the latter.
## Auth Request
To initialize the user authentication, you will have to create an authorization request using HTTP GET in the user agent (browser)
on /authorize with at least the following parameters:
- `client_id`: this tells the authorization server which application it is, copy from Management Console
- `redirect_uri`: where the authorization code is sent to after the user authentication, must be one of the registered in the previous step
- `response_type`: if you want to have a code (authorization code flow) or directly a token (implicit flow), so when ever possible use `code`
- `scope`: what scope you want to grant to the access_token / id_token, minimum is `openid`, if you're unsure what you need you might start with `openid profile email`
We recommend always using two additional parameters `state` and `nonce`. The former enables you to transfer a state through
the authentication process. The latter is used to bind the client session with the id_token and to mitigate replay attacks.
You don't need any additional parameter for this request. We're identifying the app by the `client_id` parameter.
Try out the request in our [OIDC Authentication Request Playground](https://zitadel.com/playgrounds/oidc?auth_method=Client%20Secret%20Basic).
### Additional parameters and customization
There are additional parameters and values you can provide to satisfy your use case and to customize the user's authentication flow.
Please check the [authorization_endpoint reference](/apis/openidoauth/endpoints#authorization-endpoint) in the OAuth / OIDC documentation.
## Callback
Regardless of a successful or error response from the authorization_endpoint, the authorization server will call your
callback endpoint you provided by the `redirect_uri`.
<Callout>
If the redirect_uri is not provided, was not registered or anything other prevents the auth server form returning the response to the client,
the error will be display directly to the user on the auth server.
</Callout>
Upon successful authentication you'll be given a `code` and if provided the unmodified `state` parameter.
You will need this `code` in the token request.
If a parameter was missing, malformed or any other error occurred, your answer will contain an `error` stating the error type,
possibly an `error_description` providing some information about the error and its reason and the `state` parameter.
Check the [error response section](/apis/openidoauth/endpoints#error-response) in the authorization_endpoint reference.
## Token request
Next you will have to exchange the given `code` for the tokens. For this HTTP POST request (form-urlencoded) you will need to provide the following:
- code: the code that was issued from the authorization request
- grant_type: must be `authorization_code`
- redirect_uri: callback uri where the code was sent to. Must match exactly the redirect_uri of the authorization request
Depending on your authentication method you'll need additional headers and parameters:
Send your `client_id` and `client_secret` as Basic Auth Header. Note that OAuth2 requires client_id and client_secret to be form url encoded.
So check [Client Secret Basic Auth Method](/apis/openidoauth/authn-methods#client-secret-basic) on how to build it correctly.
```curl
curl --request POST \
--url ${CUSTOM_DOMAIN}/oauth/v2/token \
--header 'Authorization: Basic ${basic}' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=authorization_code \
--data code=${code} \
--data redirect_uri=${redirect_uri}
```
@@ -0,0 +1,49 @@
<Callout type="warn" title="Security Notice">
In contrast to the Code Flow, where you'll receive a code for token exchange, with the implicit flow you'll receive
the tokens directly from the authorization endpoint. This is unsecure and might lead to token leakage and replay attacks.
It will further be removed in OAuth 2.1 for the exact same reason.
We therefore discourage the use of Implicit Flow and do not cover the flow in this guide.
</Callout>
If you still need to rely on the implicit flow, simply keep in mind that the response on the authorization_endpoint is
the same you would be given on the token_endpoint and check the [OAuth / OIDC endpoint documentation](/apis/openidoauth/endpoints) for more information.
#### redirect_uri
After selecting the authentication method, you can register a redirect_uri and post_logout_redirect_uri.
The redirect_uri will be called after user authentication for code exchange.
You can even register multiple, but typically one will be enough. If you need to distinguish between different scenarios
or environments we recommend using the `state` parameter for the former and multiple projects for the latter.
## Auth Request
To initialize the user authentication, you will have to create an authorization request using HTTP GET in the user agent (browser)
on /authorize with at least the following parameters:
- `client_id`: this tells the authorization server which application it is, copy from Management Console
- `redirect_uri`: where the authorization code is sent to after the user authentication, must be one of the registered in the previous step
- `response_type`: if you want to have a code (authorization code flow) or directly a token (implicit flow), so when ever possible use `code`
- `scope`: what scope you want to grant to the access_token / id_token, minimum is `openid`, if you're unsure what you need you might start with `openid profile email`
When using the Implicit Flow you will also have to provide a `nonce` parameter to bind the client session to the id_token and to mitigate replay attacks. Furthermore, we recommend using a `state` parameter, which enables you to transfer a state through the authentication process.
### Additional parameters and customization
There are additional parameters and values you can provide to satisfy your use case and to customize the user's authentication flow.
Please check the [authorization_endpoint reference](/apis/openidoauth/endpoints#authorization-endpoint) in the OAuth / OIDC documentation.
## Callback
Regardless of a successful or error response from the authorization_endpoint, the authorization server will call your callback endpoint you provided by the `redirect_uri`.
<Callout>
If the redirect_uri is not provided, was not registered or anything other prevents the auth server form returning the response to the client, the error will be display directly to the user on the auth server.
</Callout>
Upon successful authentication you'll be given the `access_token`, `id_token`, `expires_in` and if provided the unmodified `state` parameter, as you would be given from the token_endpoint when using Authorization Code Flow.
If a parameter was missing, malformed or any other error occurred, your answer will contain an `error` stating the error type,
possibly an `error_description` providing some information about the error and its reason and the `state` parameter.
Check the [error response section](/apis/openidoauth/endpoints#error-response) in the authorization_endpoint reference.
@@ -0,0 +1,72 @@
#### redirect_uri
After selecting the authentication method, you can register a redirect_uri and post_logout_redirect_uri.
The redirect_uri will be called after user authentication for code exchange.
You can even register multiple, but typically one will be enough. If you need to distinguish between different scenarios
or environments we recommend using the `state` parameter for the former and multiple projects for the latter.
## Auth Request
To initialize the user authentication, you will have to create an authorization request using HTTP GET in the user agent (browser)
on /authorize with at least the following parameters:
- `client_id`: this tells the authorization server which application it is, copy from Management Console
- `redirect_uri`: where the authorization code is sent to after the user authentication, must be one of the registered in the previous step
- `response_type`: if you want to have a code (authorization code flow) or directly a token (implicit flow), so when ever possible use `code`
- `scope`: what scope you want to grant to the access_token / id_token, minimum is `openid`, if you're unsure what you need you might start with `openid profile email`
We recommend always using two additional parameters `state` and `nonce`. The former enables you to transfer a state through
the authentication process. The latter is used to bind the client session with the id_token and to mitigate replay attacks.
You don't need any additional parameter for this request. We're identifying the app by the `client_id` parameter.
So your request might look like this (linebreaks and whitespace for display reasons):
Try out the request in our [OIDC Authentication Request Playground](https://zitadel.com/playgrounds/oidc?auth_method=Client%20Secret%20Basic).
### Additional parameters and customization
There are additional parameters and values you can provide to satisfy your use case and to customize the user's authentication flow.
Please check the [authorization_endpoint reference](/apis/openidoauth/endpoints#authorization-endpoint) in the OAuth / OIDC documentation.
## Callback
Regardless of a successful or error response from the authorization_endpoint, the authorization server will call your
callback endpoint you provided by the `redirect_uri`.
<Callout>
If the redirect_uri is not provided, was not registered or anything other prevents the auth server form returning the response to the client,
the error will be display directly to the user on the auth server.
</Callout>
Upon successful authentication you'll be given a `code` and if provided the unmodified `state` parameter.
You will need this `code` in the token request.
If a parameter was missing, malformed or any other error occurred, your answer will contain an `error` stating the error type,
possibly an `error_description` providing some information about the error and its reason and the `state` parameter.
Check the [error response section](/apis/openidoauth/endpoints#error-response) in the authorization_endpoint reference.
## Token request
Next you will have to exchange the given `code` for the tokens. For this HTTP POST request (form-urlencoded) you will need to provide the following:
- code: the code that was issued from the authorization request
- grant_type: must be `authorization_code`
- redirect_uri: callback uri where the code was sent to. Must match exactly the redirect_uri of the authorization request
Depending on your authentication method you'll need additional headers and parameters:
Send a JWT in the `client_assertion` and set the `client_assertion_type` to `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`
for us to validate the signature against the registered public key:
```curl
curl --request POST \
--url ${CUSTOM_DOMAIN}/oauth/v2/token \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=authorization_code \
--data code=${code} \
--data redirect_uri=${redirect_uri} \
--data client_assertion=${client_assertion} \
--data client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
```
@@ -0,0 +1,79 @@
#### redirect_uri
After selecting the authentication method, you can register a redirect_uri and post_logout_redirect_uri.
The redirect_uri will be called after user authentication for code exchange.
You can even register multiple, but typically one will be enough. If you need to distinguish between different scenarios
or environments we recommend using the `state` parameter for the former and multiple projects for the latter.
## Auth Request
To initialize the user authentication, you will have to create an authorization request using HTTP GET in the user agent (browser)
on /authorize with at least the following parameters:
- `client_id`: this tells the authorization server which application it is, copy from Management Console
- `redirect_uri`: where the authorization code is sent to after the user authentication, must be one of the registered in the previous step
- `response_type`: if you want to have a code (authorization code flow) or directly a token (implicit flow), so when ever possible use `code`
- `scope`: what scope you want to grant to the access_token / id_token, minimum is `openid`, if you're unsure what you need you might start with `openid profile email`
We recommend always using two additional parameters `state` and `nonce`. The former enables you to transfer a state through
the authentication process. The latter is used to bind the client session with the id_token and to mitigate replay attacks.
### PKCE
PKCE stands for Proof Key for Code Exchange. So other than "normal" code exchange, the PKCE does not authenticate using
client_id and client_secret but an additional code. You will have to generate a random string, hash it and send this hash
on the authorization_endpoint. On the token_endpoint you will then send the plain string for the authorization to compute
the hash as well and to verify it's correct. In order to do so you're required to send the following two parameters as well:
- `code_challenge`: the base64url representation of the (sha256) hash of your random string
- `code_challenge_method`: must always be `S256` standing for sha256, this is the only algorithm we support
For example for `random-string` the code_challenge would be `9az09PjcfuENS7oDK7jUd2xAWRb-B3N7Sr3kDoWECOY`
Try out the request in our [OIDC Authentication Request Playground](https://zitadel.com/playgrounds/oidc).
### Additional parameters and customization
There are additional parameters and values you can provide to satisfy your use case and to customize the user's authentication flow.
Please check the [authorization_endpoint reference](/apis/openidoauth/endpoints#authorization-endpoint) in the OAuth / OIDC documentation.
## Callback
Regardless of a successful or error response from the authorization_endpoint, the authorization server will call your
callback endpoint you provided by the `redirect_uri`.
<Callout>
If the redirect_uri is not provided, was not registered or anything other prevents the auth server form returning the response to the client,
the error will be display directly to the user on the auth server.
</Callout>
Upon successful authentication you'll be given a `code` and if provided the unmodified `state` parameter.
You will need this `code` in the token request.
If a parameter was missing, malformed or any other error occurred, your answer will contain an `error` stating the error type,
possibly an `error_description` providing some information about the error and its reason and the `state` parameter.
Check the [error response section](/apis/openidoauth/endpoints#error-response) in the authorization_endpoint reference.
## Token request
Next you will have to exchange the given `code` for the tokens. For this HTTP POST request (form-urlencoded) you will need to provide the following:
- code: the code that was issued from the authorization request
- grant_type: must be `authorization_code`
- redirect_uri: callback uri where the code was sent to. Must match exactly the redirect_uri of the authorization request
Depending on your authentication method you'll need additional headers and parameters:
Send your `client_id` and the previously generated string as `code_verifier` for us to recompute the `code_challenge` of the authorization request:
```curl
curl --request POST \
--url ${CUSTOM_DOMAIN}/oauth/v2/token \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=authorization_code \
--data code=${code} \
--data redirect_uri=${redirect_uri} \
--data client_id=${client_id} \
--data code_verifier=${code_verifier}
```
@@ -0,0 +1,63 @@
#### redirect_uri
Native clients might have to register multiple redirect_uris as operating system have different requirements.
Typically, you register a redirect_uri starting with a custom protocol, e.g. `ch.zitadel.app://callback`.
You're also allowed to use http://localhost, http://127.0.0.1 and http:[::1] without specifying a port: `http://locahost/callback`.
#### post creation actions
After the application creation, you might want to set additional options like `refresh_token` and `additional origins`.
If you want to request refresh_tokens and use them to renew the user's access_tokens without their interaction,
enable them in the OIDC Configuration section by ticking the checkbox.
When calling the userinfo_endpoint or any ZITADEL API, we will check if an origin header is sent. This is automatically done
by the user agent. If one is sent we will check if the origin is allowed for your application. By default, all computed
origins of the redirect_uri list are allowed.
So if your native app is built with a JavaScript base framework like ReactNative and you only specified redirect_uris
with a custom protocol, you will need to add the origin where the app is served from, e.g. `http://localhost:8100`.
### Additional parameters and customization
There are additional parameters and values you can provide to satisfy your use case and to customize the user's authentication flow.
Please check the [authorization_endpoint reference](/apis/openidoauth/endpoints#authorization-endpoint) in the OAuth / OIDC documentation.
## Callback
Regardless of a successful or error response from the authorization_endpoint, the authorization server will call your
callback endpoint you provided by the `redirect_uri`.
<Callout>
If the redirect_uri is not provided, was not registered or anything other prevents the auth server form returning the response to the client,
the error will be display directly to the user on the auth server.
</Callout>
Upon successful authentication you'll be given a `code` and if provided the unmodified `state` parameter.
You will need this `code` in the token request.
If a parameter was missing, malformed or any other error occurred, your answer will contain an `error` stating the error type,
possibly an `error_description` providing some information about the error and its reason and the `state` parameter.
Check the [error response section](/apis/openidoauth/endpoints#error-response) in the authorization_endpoint reference.
## Token request
Next you will have to exchange the given `code` for the tokens. For this HTTP POST request (form-urlencoded) you will need to provide the following:
- code: the code that was issued from the authorization request
- grant_type: must be `authorization_code`
- redirect_uri: callback uri where the code was sent to. Must match exactly the redirect_uri of the authorization request
Depending on your authentication method you'll need additional headers and parameters:
Send your `client_id` and the previously generated string as `code_verifier` for us to recompute the `code_challenge` of the authorization request:
```curl
curl --request POST \
--url ${CUSTOM_DOMAIN}/oauth/v2/token \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=authorization_code \
--data code=${code} \
--data redirect_uri=${redirect_uri} \
--data client_id=${client_id} \
--data code_verifier=${code_verifier}
```
@@ -0,0 +1,125 @@
---
title: Integrating Your Application with ZITADEL using RFC 8628 OAuth 2.0 Device Authorization Flow
sidebar_label: Device Authorization Flow
---
ZITADEL implements device authorization as per [RFC 8628](https://datatracker.ietf.org/doc/html/rfc8628). This document demonstrates its use.
This documentation aims to guide you through the process of seamlessly integrating your application with ZITADEL, leveraging the capabilities of [RFC 8628](https://datatracker.ietf.org/doc/html/rfc8628). ZITADEL is a powerful identity and access management (IAM) solution that provides robust authentication and authorization services for your applications. RFC 8628, also known as "OAuth 2.0 Device Authorization Grant", offers a standardized protocol for devices with limited input capabilities to obtain user authorization. By following the steps outlined here, you'll be able to empower your application with secure authentication and access control mechanisms provided by ZITADEL, ensuring a smooth and secure user experience.
## What is RFC 8628?
[RFC 8628](https://datatracker.ietf.org/doc/html/rfc8628), or the OAuth 2.0 Device Authorization Grant, defines a protocol for OAuth 2.0 clients that have limited input capabilities, such as devices with no browser or low-power sensors. It allows such devices to obtain user consent for authorizing access to protected resources by directing the user to a device-friendly authentication endpoint.
## Why Integrate with Zitadel?
ZITADEL offers a comprehensive identity and access management solution, providing features like single sign-on (SSO), multi-factor authentication (MFA), user management, and more. By integrating your application with ZITADEL, you can leverage these powerful features to enhance the security and usability of your application without reinventing the wheel.
## Getting Started
To begin integrating your application with Zitadel using RFC 8628, follow the steps outlined in this documentation. We'll walk you through the process, from setting up your application in the Zitadel management console to handling user authentication and access tokens in your application code.
1. Go to “Organization”/”Projects”.
2. Under “Applications” click the “New” button.
<img src="/docs/img/device-auth/device-auth-01.png" alt="Device Authorization Flow in ZITADEL"/>
3. Select “Native” and enter a name for the application, and click “Continue”.
<img src="/docs/img/device-auth/device-auth-02.png" alt="Device Authorization Flow in ZITADEL"/>
4. Select “Device Code”. Click “Continue”.
<img src="/docs/img/device-auth/device-auth-03.png" alt="Device Authorization Flow in ZITADEL"/>
5. Check the details and click “Create”.
<img src="/docs/img/device-auth/device-auth-04.png" alt="Device Authorization Flow in ZITADEL"/>
6. Copy the “Client ID” and store it for later use.
<img src="/docs/img/device-auth/device-auth-05.png" alt="Device Authorization Flow in ZITADEL"/>
## Device Client Example
The [ZITADEL OpenID Connect client and server library](https://github.com/zitadel/oidc/) written for Go has a device client example, which can behave and authenticate as a device. In order to run this client, you need a recent version of Go (>=1.20) installed on your device.
The example requires two environment variables to be set:
- `ISSUER`: server address of your instance or domain. You can find the issuer URL in the “URLs” section. In this example, it will be set to [https://test-0o6zvq.zitadel.cloud](https://test-0o6zvq.zitadel.cloud). **Do not use a trailing slash!**
<img src="/docs/img/device-auth/device-auth-06.png" alt="Device Authorization Flow in ZITADEL"/>
- `CLIENT_ID`: the Client ID we obtained earlier.
Replace `ISSUER` and `CLIENT_ID` values with the ones you obtained and run the example as shown below:
``` bash
ISSUER="https://test-0o6zvq.zitadel.cloud" \
CLIENT_ID="232685602728952637@device_auth" \
go run github.com/zitadel/oidc/v2/example/client/device@latest
```
You should see some info-level log output with response data and a line with login instructions as given below:
``` bash
Please browse to https://test-0o6zvq.zitadel.cloud/device and enter code GQWC-FWFK
INFO[0002] start polling
```
At this point, the device app starts polling the token endpoint at 5-second intervals until the request is allowed, denied, or times out (currently 5 minutes).
## Authenticate
When you browse to the given URL and the device code is entered, the authentication flow for a user is started. If you are already logged in, it skips right ahead to the final screen where you can choose to allow or deny the request. If you are not logged in, you will have to enter your credentials for login.
<img src="/docs/img/device-auth/device-auth-07.png" alt="Device Authorization Flow in ZITADEL"/>
<img src="/docs/img/device-auth/device-auth-08.png" alt="Device Authorization Flow in ZITADEL"/>
<img src="/docs/img/device-auth/device-auth-09.png" alt="Device Authorization Flow in ZITADEL"/>
<img src="/docs/img/device-auth/device-auth-10.png" alt="Device Authorization Flow in ZITADEL"/>
<img src="/docs/img/device-auth/device-auth-11.png" alt="Device Authorization Flow in ZITADEL"/>
When “allow” is clicked, the device (the CLI in this case) will receive a token on the next poll. A log line will be shown as given below:
``` bash
INFO[0165] successfully obtained token: &{Um2W5Od0yBU0KHfhP0AD0726rXrpxlepOR7yyftMvocgMWr25pVCuca1oiSSLiQjcXQqCEA Bearer 43199 }
```
At this point, the program terminates as it doesn’t have any other useful purpose. Regular device apps would of course use the token to consume the actual service.
## Code URL
During the start of the authorization flow ,there is a log line printed with the complete response object from ZITADEL that looks like this:
``` bash
resp&{dOcbPeysDhT26ZatRh9n7Q KPVZ-DJGG https://test-0o6zvq.zitadel.cloud/device https://test-0o6zvq.zitadel.cloud/device?user_code=GQWC-FWFK 300 5}
```
From this object, we used the `verification_uri` and `user_code` fields to print the information to the user to go to an address and enter the code. We also return a `verification_uri_complete` field, which already includes the code and allows skipping manual entry of the code.
``` bash
https://test-0o6zvq.zitadel.cloud/device?user_code=GQWC-FWFK
```
In real-life applications, this could be used to create a QR code on the device screen, and users can use their mobile phones to scan the code to go to the required URL for authentication. In the context of this example, you can restart the device example program and copy/paste the complete link instead of manually entering it.
## Security considerations
The user uses a low entropy code to link a device to their account, which is easy to brute-force. Also, the code or QR might be displayed in environments where others could observe and use the code to authenticate the device (hijack the session) to their account.
The device authorization grant specification was written with the philosophy that hijacking such sessions is not beneficial for the attacker. For example, with an on-demand streaming service, the attacker would be paying for the services the user is benefiting from. Hence, economically, the attack would not make sense.
See [here](https://datatracker.ietf.org/doc/html/rfc8628#section-5) for more information.
## Client library
For Go clients, the ZITADEL OIDC repository already includes device authorization in the `rp` package.
Use [rp.DeviceAuthorization](https://pkg.go.dev/github.com/zitadel/oidc/v2/pkg/client/rp#DeviceAuthorization) to start the flow and receive the URL and user code.
[rp.DeviceAccessToken](https://pkg.go.dev/github.com/zitadel/oidc/v2/pkg/client/rp#DeviceAccessToken) polls the token endpoint and returns the access token once it succeeds.
The client implementation is rather simple and can easily be recreated in other languages. Go can be used as an example, or simply follow the [RFC](https://datatracker.ietf.org/doc/html/rfc8628) as a guide.
@@ -0,0 +1,25 @@
---
title: "Authenticate users with OpenID Connect (OIDC)"
description: "This guide explains how to utilize ZITADEL for user authentication within your applications using OpenID Connect (OIDC). Here, we offer comprehensive guidance on seamlessly integrating ZITADEL's authentication features, ensuring both security and user experience excellence. Throughout this documentation, we'll cover the setup process for ZITADEL authentication, including the recommended OIDC flows tailored to different application types. Additionally, we'll provide clear instructions on securely signing out or logging out users from your application, ensuring data security and user privacy. With our guidance, you'll be equipped to leverage ZITADEL's authentication capabilities effectively, enhancing your application's security posture while delivering a seamless login experience for your users."
---
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
import { Card, Cards } from 'fumadocs-ui/components/card';
import { FileText, Folder, Link as LinkIcon } from 'lucide-react';
<Cards>
<Card title="Oauth Recommended Flows" href="/guides/integrate/login/oidc/oauth-recommended-flows" icon={<FileText />} />
<Card title="Login Users" href="/guides/integrate/login/oidc/login-users" icon={<FileText />} />
<Card title="Device Authorization" href="/guides/integrate/login/oidc/device-authorization" icon={<FileText />} />
<Card title="Endpoints" href="/apis/openidoauth/endpoints" icon={<FileText />} />
<Card title="Authn Methods" href="/apis/openidoauth/authn-methods" icon={<FileText />} />
<Card title="Scopes" href="/apis/openidoauth/scopes" icon={<FileText />} />
<Card title="Claims" href="/apis/openidoauth/claims" icon={<FileText />} />
<Card title="Grant Types" href="/apis/openidoauth/grant-types" icon={<FileText />} />
<Card title="Authrequest" href="/apis/openidoauth/authrequest" icon={<FileText />} />
<Card title="Webkeys" href="/guides/integrate/login/oidc/webkeys" icon={<FileText />} />
<Card title="Token Exchange" href="/guides/integrate/token-exchange" icon={<FileText />} />
</Cards>
@@ -0,0 +1,93 @@
---
title: Authenticate users with OpenID Connect
sidebar_label: OIDC Code Flow + PKCE
---
import AuthMethods from "./_authmethods.mdx";
## Overview
This guide will show you how to use ZITADEL to login users into your application (authentication).
It will guide you step-by-step through the basics and point out on how to customize process.
## OIDC / OAuth Flow
OAuth and therefore OIDC know three different application types:
- Web (Server-side web applications such as java, .net, ...)
- Native (native, mobile or desktop applications)
- User Agent (single page applications / SPA, generally JavaScript executed in the browser)
Depending on the app type you're trying to register, there are small differences.
But regardless of the app type we recommend using Proof Key for Code Exchange (PKCE).
Please read the following guide about the [different-client-profiles](./oauth-recommended-flows#different-client-profiles) and why to use PKCE.
### Code Flow
For a basic understanding of OAuth and its flows, we'll briefly describe the most important flow: **Authorization Code**.
The following diagram demonstrates a basic authorization_code flow:
![](../../../../../public/img/guides/auth_flow.png)
1. When an unauthenticated user visits your application,
2. you will create an authorization request to the authorization endpoint.
3. The Authorization Server (ZITADEL) will send an HTTP 302 to the user's browser, which will redirect them to the login UI.
4. The user will have to authenticate using the demanded auth mechanics.
5. Your application will be called on the registered callback path (redirect_uri) and be provided an authorization_code.
6. This authorization_code must then be sent together with you applications authentication (client_id + client_secret) to the token_endpoint
7. In exchange the Authorization Server (ZITADEL) will return an access_token and if requested a refresh_token and in the case of OIDC an id_token as well
8. The access_token can then be used to call a Resource Server (API). The token will be sent as Authorization Header.
This flow is the same when using PKCE or JWT with Private Key for authentication.
## Create Application
To create an application, open your project in Management Console and start by clicking on the "New" button in the Application section.
#### Application type
This will start a wizard asking you for an application name and a type.
<Tabs
groupId="app-type"
default="web"
values={[
{'label': 'Web Application', 'value': 'web'},
{'label': 'Native Application', 'value': 'native'},
{'label': 'Single Page Application', 'value': 'spa'},
]}
>
<Tab value="web">
#### Authentication method
After selecting WEB, you'll next have to choose an authentication method. As mentioned before we recommend using PKCE.
For even better security you could switch to JWT or just rely on the standard Code Flow. For security reasons we don't
recommend using POST and will not cover it in this guide.
Please change the authentication method here as well, if you did so in the wizard, so we can better guide you through the process:
<AuthMethods components={props.components} selected="web" />
</Tab>
<Tab value="native">
#### Authentication method
When selecting Native the authentication method always needs to be PKCE.
<AuthMethods components={props.components} selected="native" />
</Tab>
<Tab value="spa">
#### Authentication method
When selecting SPA the recommended authentication method is again PKCE. All common Frameworks like Angular, React, Vue.js and so on
are able to successfully authenticate with PKCE. Our management console UI for instance uses PKCE as well.
<AuthMethods components={props.components} selected="spa" />
</Tab>
</Tabs>
@@ -0,0 +1,75 @@
---
title: Log Out Users from an Application with ZITADEL
sidebar_label: Logout
---
This guide shows you the different concepts and use cases of the logout process and how to use it in ZITADEL.
## OpenID Connect Single Logout
### Single Sign On (SSO) vs Single Logout (SLO)
Single Sign On (SSO) allows a user to login once without the need for authentication across multiple applications.
Single Logout (SLO) is the counterpart to SSO. With SLO a user can logout and terminate sessions across many applications, without actively logging out from them.
The purpose of a logout is to terminate a user session.
Depending on how the session handling is implemented, there are different mechanisms that can be used.
There are two possibilities where sessions are stored:
- The User Agent (e.g the Browser or Mobile App) stores the session information (e.g. in a cookie)
- A Server stores the session information (e.g. in a database or api)
### OpenID Connect Logout
OpenID Connect defines three logout mechanisms to address the different architectures:
- [OpenID Connect Session Management 1.0](https://openid.net/specs/openid-connect-session-1_0.html)
- [OpenID Connect Front-Channel Logout 1.0](https://openid.net/specs/openid-connect-frontchannel-1_0.html)
- [OpenID Connect Back-Channel Logout 1.0](https://openid.net/specs/openid-connect-backchannel-1_0.html)
#### Session Management
Session Management in OpenID Connect defines a mechanism for a client (Relying Party, RP) to monitor the state of the user session from a identity provider (OP, e.g ZITADEL).
When a user logs out of the provider, the user's session is terminated and the client can in turn reflect that in its behavior.
#### RP initiated Logout
With the RP initiated flow all logout processes are triggered by a request from the client (e.g your application) through a well defined standard API by redirecting the user-agent to the [end_session_endpoint](/apis/openidoauth/endpoints#end-session-endpoint).
If you have specified some post_logout_redirect_uris on your client you have to send either the id_token_hint or the client_id as param in your request.
So ZITADEL is able to read the configured redirect uris.
```
GET ${CUSTOM_DOMAIN}/oidc/v1/end_session
?id_token_hint={id_token}
&post_logout_redirect_uri=https://rp.example.com/logged_out
&state=random_string
```
#### Front-Channel Logout
The user agent handles the front-channel logout.
Each client with an OpenID Session of the user that supports front-channel renders an iframe so the logout request is performed on all clients parallel.
<Callout>
This is not yet implemented in ZITADEL
</Callout>
#### Back-Channel Logout
The back-channel logout is a mechanism on the server-side and the user agent does not have to do anything.
The user will logout from all clients even in the case the user agent was closed.
<Callout>
This is not yet implemented in ZITADEL
</Callout>
## Scenarios
1. Logout all users from the current user-agent/browser (current implementation of ZITADEL end_session_endpoint)
2. Logout my user from the current user-agent/browser
3. Logout my user from the all devices
## Session Handling in ZITADEL
The session management in ZITADEL is done on the server side.
As soon as a user authenticates the first time, ZITADEL generates a user-agent cookie.
All open sessions on that user-agent (browser) will be stored to the same cookie.
If you delete the cookie in your browser, we will not be able to find out which sessions belong to your user-agent.
@@ -0,0 +1,103 @@
---
title: Recommended authorization flows with OpenID Connect (OIDC) and OAuth 2.x
sidebar_label: Recommended authorization flows
---
## Introduction
In this guide we will go over some basics on how to obtain an authorization with OpenID Connect 1.x and OAuth 2.x.
ZITADEL does not make assumptions about the application type you are about to integrate. Therefore you must qualify and define an appropriate method for your users to gain authorization to access your application (“authentication flow”). Your choice depends on the application’s ability to maintain the confidentiality of client credentials and the technological capabilities of your application. If you choose a deprecated or unfeasible flow to obtain authorization for your application, your users’ credentials may be compromised.
We invite you to further explore the different authorization flows in the OAuth 2.x standard. For the start we assume that you have a brand-new application (ie. without legacy requirements) and you found an reliable SDK/Library that does the heavy lifting for you.
So this module will only go over the basics and explain why we recommend the flow “Authorization Flow with PKCE” as default for most applications. We will also cover the case of machine-to-machine communication, ie. where there is no interactive login. Further we will guide you to further reading viable alternatives, if the default flow is not feasible.
## Basics of Federated Identity
Although Federated Identities are not a new concept ([RFC 6749](https://tools.ietf.org/html/rfc6749), “The OAuth 2.0 Authorization Framework” was released in 2012) it is important to highlight the difference between the traditional client-server authentication model and the concept of delegated authorization and authentication.
The aforementioned RFC provides us with some [problems and limitations](https://tools.ietf.org/html/rfc6749#section-1) of the client-server authentication, where a client requests a protected resource on the server by authenticating with the user’s credentials:
- Applications need to store users credentials (eg, password) for future use; compromise of any application results in compromise of the end-users credentials
- Servers are required to support password authentication
- Without means of limiting scope when providing the user’s credentials, the application gains overly broad access to protected resources
- Users cannot revoke access for a single application, but only for all by changing credentials
So what do we want to achieve with delegated authentication?
- Instead of implementing authentication on each server and trusting each server
- Users only **authenticate** with a trusted server (ie. ZITADEL), that validates the user’s identity through a challenge (eg, multiple authentication factors) and issues an **ID token** (OpenID Connect 1.x)
- Applications have means of **validating the integrity** of presented access and ID tokens
- Instead of sending around the user’s credentials
- Clients may access protected resources with an **access token** that is only valid for specific scope and limited lifetime (OAuth 2.x)
- Users have to **authorize** applications to access certain [**scopes**](/apis/openidoauth/scopes) (eg, email address or custom roles). Applications can request [**claims**](/apis/openidoauth/claims) (key:value pairs, eg email address) for the authorized scopes with the access token or ID token from ZITADEL
- Access tokens are bearer tokens, meaning that possession of the token provides access to a resource. But the tokens expire frequently and the application must request a new access token via **refresh token** or the user must reauthenticate
![](../../../../../public/img/guides/consulting_federated_identities_basics.png)
This is where the so-called “flows” come into play: There are a number of different flows on how to handle the process from authentication, over authorization, getting tokens and requesting additional information about the user.
Maybe interesting to mention is that we are mostly concerned with choosing the right OAuth 2.x flows (alas “authorization”). OpenID Connect extends the OAuth 2.x flow with useful features like endpoint discovery (where to ask), ID Token (who is the user, when and how did she authenticate), and UserInfo Endpoint (getting additional information about the user).
## Different client profiles
As mentioned in the beginning of this module, there are two main determinants for choosing the optimal authorization flow:
1. Client’s ability to maintain the confidentiality of client credentials
2. Technological limitations
OAuth 2.x defines two [client types](https://tools.ietf.org/html/rfc6749#section-2.1) based on their ability to maintain the confidentiality of their client credentials:
- Confidential: Clients capable of maintaining the confidentiality of their credentials (e.g., client implemented on a secure server with restricted access to the client credentials), or capable of secure client authentication using other means.
- Public: Clients incapable of maintaining the confidentiality of their credentials (e.g., clients executing on the device used by the resource owner, such as an installed native application or a web browser-based application), and incapable of secure client authentication via any other means.
The following table gives you a brief overview of different client profiles.
<table className="table-wrapper">
<tbody>
<tr>
<th>Confidentiality of client credentials</th>
<th>Client profile</th>
<th>Examples of clients</th>
</tr>
<tr>
<td rowSpan="2">Public</td>
<td>User-Agent</td>
<td>Single-page web applications (SPA), generally JavaScript executed in Browser</td>
</tr>
<tr>
<td>Native</td>
<td>Native, Mobile, or Desktop applications</td>
</tr>
<tr>
<td rowSpan="2">Confidential</td>
<td>Web</td>
<td>Server-side web applications such as java, .net, …</td>
</tr>
<tr>
<td>Machine-to-Machine</td>
<td>APIs, generally services without direct user-interaction at the authorization server</td>
</tr>
</tbody>
</table>
## Our recommended authorization flows
We recommend using the flow **“Authorization Code with Proof Key of Code Exchange (PKCE)”** ([RFC7636](https://tools.ietf.org/html/rfc7636)) for **User-Agent**, **Native**, and **Web** clients.
If you don’t have any technical limitations, you should favor the flow Authorization Code with PKCE over other methods. The PKCE part makes the flow resistant against authorization code interception attack as described well in RFC7636.
_So what about APIs?_
We recommend using **“JWT bearer token with private key”** ([RFC7523](https://tools.ietf.org/html/rfc7523)) for Machine-to-Machine clients.
What this means is that you have to send an JWT token, containing the [standard claims for access tokens](/apis/openidoauth/claims) and that is signed with your private key, to the token endpoint to request the access token. We will see how this works in another module about Service Accounts.
If you don’t have any technical limitations, you should prefer this method over other methods.
A JWT with a private key can also be used with client profile web to further enhance security.
In case you need alternative flows and their advantages and drawbacks, there will be a module to outline more methods and our recommended fallback strategy per client profile that are available in ZITADEL.
@@ -0,0 +1,357 @@
---
title: OpenID Connect and OAuth2 web keys
sidebar_label: Web Keys
---
Web Keys in ZITADEL are used to sign and verify JSON Web Tokens (JWT).
ID tokens are created, signed and returned by ZITADEL when a OpenID connect (OIDC) or OAuth2
authorization flow completes and a user is authenticated.
Optionally, ZITADEL can return JWTs for access tokens if the OIDC Application is configured for it.
## Introduction
ZITADEL uses asymmetric cryptography to sign and validate JWTs.
Keys are generated in pairs resulting in a private and public key.
Private keys are used to sign tokens.
Public keys are used to verify tokens.
OIDC clients need the public key to verify ID tokens.
OAuth2 API apps might need the public key if they want to client-side verification of a
JWT access tokens, instead of [introspection](/apis/openidoauth/endpoints#introspection-endpoint).
ZITADEL uses public key verification when API calls are made or when the userInfo or introspection
endpoints are called with a JWT access token.
### JSON Web Key
ZITADEL implements the [RFC7517 - JSON Web Key (JWK)](https://www.rfc-editor.org/rfc/rfc7517) format for storage and distribution of public keys.
Web keys in ZITADEL support a number of [JSON Web Algorithms (JWA)](https://www.rfc-editor.org/rfc/rfc7518) for digital signatures:
| Identifier | Description |
| ---------- | ------------------------------- |
| RS256 | RSASSA-PKCS1-v1_5 using SHA-256 |
| RS384 | RSASSA-PKCS1-v1_5 using SHA-384 |
| RS512 | RSASSA-PKCS1-v1_5 using SHA-512 |
| ES256 | ECDSA using P-256 and SHA-256 |
| ES384 | ECDSA using P-384 and SHA-384 |
| ES512 | ECDSA using P-512 and SHA-512 |
| EdDSA | EdDSA signature algorithms[^1] |
[^1]: EdDSA refers to both Ed25519 and Ed448 curves. ZITADEL only supports Ed25519 with a SHA-512 hashing algorithm. EdDSA is for JSON Object Signing is defined in [RFC8037](https://www.rfc-editor.org/rfc/rfc8037).
### Client Algorithm Support
Before customizing the algorithm the instance admin **MUST** make sure the complete app and API ecosystem
supports the chosen algorithm.
When all OIDC applications of an instance use opaque access tokens, and they call APIs which only use
introspection for token validation, only the OIDC applications will need to support the chosen algorithm.
If JWT access tokens are used and APIs do public key verification, those APIs need to support the chosen algorithm as well.
RS256 is widely considered the default algorithm and must be supported by all OIDC/Oauth2 providers, relying parties and resource servers.
This is also the default ZITADEL uses when [creating web keys](#creation).
It might be reasonable to assume RS384 and RS512 are just as supported, because those are just variations on RSA based keys.
The ES256, ES384 and ES512 might have reasonable support as well,
ECDSA is part of the same [JSON Web Algorithms (JWA)](https://www.rfc-editor.org/rfc/rfc7518) as RSA.
EdDSA usage is defined in the supplemental [RFC8037](https://www.rfc-editor.org/rfc/rfc8037),
and therefore may be less supported than the others.
Also, the `at_hash` claim in the ID token is a hashed string of the access token.
The hasher is usually defined by the keys `alg` header. For example:
- RS256 defines an RSA key and a SHA256 hasher.
- ES512 defines an elliptic curve key with the P-512 and SHA512 hasher.
Unfortunately, there is no published standard for the `at_hash` hasher used for EdDSA.
In fact, EdDSA may use different curves and internally uses different hashers:
- ed25519 uses SHA512;
- ed448 uses SHAKE256;
This resulted in a [proposal](https://bitbucket.org/openid/connect/issues/1125/_hash-algorithm-for-eddsa-id-tokens) at
the Open ID workgroup to follow suit and use the same hashing algorithms for the `at_hash` claim.
This means both signers and verifiers can't know the hasher by the `alg` value alone and need to inspect `crv` value as well.
Since the decision in the proposal isn't published yet,
there is a big change some OIDC client libraries don't have proper support for EdDSA / ed25519.
The ZITADEL back-end is written in Go. The Go developers have denied ed448 curve implementations to be included.
Therefore ZITADEL only uses ed25519 with a SHA512.
The same counts for [zitadel/oidc](https://github.com/zitadel/oidc) Go library.
## Web Key management
ZITADEL provides a resource based [web keys API](/reference/api/webkey).
The API allows the creation, activation, deletion and listing of web keys.
All public keys that are stored for an instance are served on the [JWKS endpoint](#json-web-key-set).
Applications need public keys for token verification and not all applications are capable of on-demand
key fetching when receiving a token with an unknown key ID (`kid` header claim).
Instead, those application may do a time-based refresh or only load keys at startup.
Using the web keys API, keys can be created first and activated for signing later.
This allows the keys to be distributed to the instance's apps and caches.
Once a key is deactivated, its public key will remain available for token verification until the web key is deleted.
Delayed deletion makes sure tokens that were signed before the key got deactivated remain valid.
When the `web_key` [feature](/reference/api/feature/zitadel.feature.v2.FeatureService.SetInstanceFeatures) is enabled the first time,
two web key pairs are created with one activated.
### Creation
The web key [create](/apis/resources/webkey_service_v2/zitadel-webkey-v-2-web-key-service-create-web-key) endpoint generates a new web key pair,
using the passed generator configuration from the request. This config is a one-of field of:
- RSA
- ECDSA
- ED25519
When the request does not contain any specific configuration,
[RSA](#rsa) is used as default with the default options as described below:
```bash
curl -L 'https://${CUSTOM_DOMAIN}/v2/web_keys' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
-d '{}'
```
#### RSA
The RSA generator config takes two enum values.
- The `bits` fields determines the size of the RSA key:
- `RSA_BITS_2048` (**default**)
- `RSA_BITS_3072`
- `RSA_BITS_4096`
- The `hasher` field sets the hash mode and
determines the `alg` header value of the web key:
- `RSA_HASHER_SHA256` results in the RS256 algorithm header. (**default**)
- `RSA_HASHER_SHA384` results in the RS384 algorithm header.
- `RSA_HASHER_SHA512` results in the RS512 algorithm header.
For example, to create a RSA web key with the size of 3072 bits and the SHA512 algorithm (RS512):
```bash
curl -L 'https://${CUSTOM_DOMAIN}/v2/web_keys' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
-d '{
"rsa": {
"bits": "RSA_BITS_3072",
"hasher": "RSA_HASHER_SHA512"
}
}'
```
#### ECDSA
The ECDSA generator config takes a single `curve` enum value which determines both the key's curve parameters and hashing algorithm:
- `ECDSA_CURVE_P256` uses the NIST P-256 curve and sets the ES256 algorithm header.
- `ECDSA_CURVE_P384` uses the NIST P-384 curve and sets the ES384 algorithm header.
- `ECDSA_CURVE_P512` uses the NIST P-512 curve and sets the ES512 algorithm header.
For example, to create a ECDSA web key with a P-256 curve and the SHA256 algorithm:
```bash
curl -L 'https://${CUSTOM_DOMAIN}/v2/web_keys' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
-d '{
"ecdsa": {
"curve": "ECDSA_CURVE_P256"
}
}'
```
#### ED25519
ED25519 is an EdDSA curve and currently the only EdDSA curve supported by ZITADEL.[^2]
No config is needed for ed25519 as its specification already includes the curve parameters.
ed25519 always uses the SHA512 hasher.
Note that the `alg` header for ed25519 is `EdDSA` and refers to both ed25519 and ed448 curves.
Both curves specify different hashers.
Clients which support both curves must inspect `crv` header value to assert the difference.
For example, to create a ed25519 web key:
```bash
curl -L 'https://${CUSTOM_DOMAIN}/v2/web_keys' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
-d '{
"ed25519": {}
}'
```
[^2]: The ZITADEL back-end is written in Go. The Go developers have denied ed448 curve implementations to be included.
Therefore ZITADEL won't support this either.
### Activation
When a generated web key is [activated](/apis/resources/webkey_service_v2/zitadel-webkey-v-2-web-key-service-activate-web-key),
its private key will be used to sign new tokens.
There can be only one active key on an instance.
Activating a key implies deactivation of the previously active key.
Public keys on the [JWKS](#json-web-key-set) endpoint may be [cached](#caching).
Therefore it is advised to delay activation after generating a key,
at least for the duration of the max-age setting plus any time it might take for client applications to refresh.
### Deletion
Non-active keys may be [deleted](/apis/resources/webkey_service_v2/zitadel-webkey-v-2-web-key-service-delete-web-key).
Deletion also means tokens signed with this key become invalid.
Active keys can't be deleted.
As each public key is available on the [JWKS](#json-web-key-set) endpoint,
it is important to cleanup old web keys that are no longer needed.
Otherwise the endpoint's response size will only grow over time, which might lead to performance issues.
Once a key was activated and deactivated (by activation of the next key) deletion should wait:
- Until access and ID tokens are expired. See [OIDC token lifetimes](/guides/manage/console/default-settings#oidc-token-lifetimes-and-expiration).
- ID tokens may be used as `id_token_hint` in authentication and end-session requests. The hint typically doesn't expire, but becomes invalid once the key is deleted.
It might be desirable to keep keys around long enough to minimize user impact.
### Rotation example
This section gives an example on a key rotation strategy.
This strategy aims to fulfill the following requirements:
1. Web keys are rotated monthly.
2. Applications have enough time to see the next activated web key on the [JWKS](#json-web-key-set) endpoint.
3. Web keys are kept long enough to cover the access and ID token validity of 24 hours.
4. Web keys are kept long enough to to allow usage of the `id_token_hint` for at least 3 months.
Users that haven't logged in / refreshed tokens with the client app for that period,
will need to re-enter their username.
When the instance was created, resp. the feature was rolled out, the instance got two keys with the first one activated. When this feature becomes generally available, instance creation will setup the first two keys in the same way. So the initial state always looks like this:
| id | created | changed | state |
| --- | ---------- | ---------- | --------------- |
| 1 | 2025-01-01 | 2025-01-01 | `STATE_ACTIVE` |
| 2 | 2025-01-01 | 2025-01-01 | `STATE_INITIAL` |
For the sake of this example we will use simplified IDs and restrict timestamps to dates.
After one month, on 2025-02-01, we wish to activate the next available key and create a new key to be available for activation next month. This fulfills requirements 1 and 2.
```bash
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/web_keys/2/_activate' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>'
curl -L 'https://${CUSTOM_DOMAIN}/v2/web_keys' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
-d '{}'
```
Key ID 2 became active, Key ID 1 became inactive and a new key with ID 3 was created:
| id | created | changed | state |
| --- | ---------- | ---------- | ---------------- |
| 1 | 2025-01-01 | 2025-02-01 | `STATE_INACTIVE` |
| 2 | 2025-01-01 | 2025-02-01 | `STATE_ACTIVE` |
| 3 | 2025-02-01 | 2025-02-01 | `STATE_INITIAL` |
No keys are deleted yet.
We continue like this monthly.
At one point (on 2025-05-01) we will have a web key with `STATE_INACTIVE` with a changed date of 3 months ago:
| id | created | changed | state |
| --- | ---------- | ---------- | ---------------- |
| 1 | 2025-01-01 | 2025-02-01 | `STATE_INACTIVE` |
| 2 | 2025-01-01 | 2025-03-01 | `STATE_INACTIVE` |
| 3 | 2025-02-01 | 2025-04-01 | `STATE_INACTIVE` |
| 4 | 2025-03-01 | 2025-05-01 | `STATE_INACTIVE` |
| 5 | 2025-04-01 | 2025-05-01 | `STATE_ACTIVE` |
| 6 | 2025-05-01 | 2025-05-01 | `STATE_INITIAL` |
In addition to the activate and create calls we made on this iteration,
we can now safely delete the oldest key, as both requirement 3 and 4 are now fulfilled:
```bash
curl -L -X DELETE 'https://${CUSTOM_DOMAIN}/v2/web_keys/1' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>'
```
The final state:
| id | created | changed | state |
| --- | ---------- | ---------- | ---------------- |
| 2 | 2025-01-01 | 2025-03-01 | `STATE_INACTIVE` |
| 3 | 2025-02-01 | 2025-04-01 | `STATE_INACTIVE` |
| 4 | 2025-03-01 | 2025-05-01 | `STATE_INACTIVE` |
| 5 | 2025-04-01 | 2025-05-01 | `STATE_ACTIVE` |
| 6 | 2025-05-01 | 2025-05-01 | `STATE_INITIAL` |
Next month, Key ID 6 will be activated, an new key added and Key ID 2 can be deleted.
## JSON web key set
The JSON web key set (JWKS) endpoint serves all available public keys for the instance on
`${CUSTOM_DOMAIN}/oauth/v2/keys`. This includes activated, newly non-activated and deactivated web keys. The response format is defined in [RFC7517, section 5: JWK Set Format](https://www.rfc-editor.org/rfc/rfc7517#section-5).
And looks like:
```json
{
"keys": [
{
"use": "sig",
"kty": "RSA",
"kid": "280543383892525058",
"alg": "RS384",
"n": "0pVcbjTEr-awBmvztGLbBJB_-_YwjCKKXURJRpoXrChlaqtAvbkxby7mu9wSKAibxnvaobfuxnQydlB4CoKObUr00ARVBNeP5HLzeQUEx3CZh3s1LsjiuYov_yyvK9D12WH1LikP4ZPS68j-DVoEOEcFAE6cNikXTeDyCKa-ixROALieRXUQXTlvVyA_s0FhevmH0-M6rEN4YcfQuIZACEv2nQ4AJo0sNnugwrrqNn595ONKMSh2XTVngxxAD3TGHXg9bELB-WmgnZamVbO-ObpDBp5Ov73HL60_UoBTzBDECM6ovl52fHusLFw6Vkdt9_W3QhuRFljNqTPnna6rB-bLptQltBpnSBV3TxmklBcQ1EO3qeGvgOJsmDwSRlr28Du_1pyFMFANnG174eX5XrYASqTgJ1Wq7AfMBmv7YwGU7PbMce1V_CAV9u_hNkMJf0xQ4AIqrQ98f9hC5VCdCoKSOH1-1d8icEu7UmDyJohWqvY7xGOM_0Abx8ekMRT2O9PulmQ22me_GI5zXh7iv9yaoNq8EUNP5bdtr-ZG4PG8mqpLDSLpCpobYRK5AynyJkf-7_6neSy-ihu604ADKsNzB-uO58V8MPFdSPncyuUeTPX4dAVajbFyMtoAjtI1k_HYMU8nojRUrLSCJae9b0KtcPm9s7dCIL1Zpa4B-YM",
"e": "AQAB"
},
{
"use": "sig",
"kty": "OKP",
"kid": "280998627474669570",
"crv": "Ed25519",
"alg": "EdDSA",
"x": "B51hFhRUHMHpqO1f-OThtnk3PfnRFaPFJWCLXSM_kuI"
},
{
"use": "sig",
"kty": "EC",
"kid": "282465789963927554",
"crv": "P-256",
"alg": "ES256",
"x": "X5s3tNoIXd5odp_-IwQq5oaAgMSoAxj0hwQ1DgHihmI",
"y": "JqmTlRjoOv5bY5E9tAZXHaUHUamAAAFshO8zLhEZ9ZM"
}
]
}
```
After the `web_key` feature is enabled, the response may still contain legacy keys, in order not to invalidate older sessions.
The legacy keys will disappear once they expire.
### Caching
As web keys can be created and distributed ahead of time, it is safe for JWKS responses to be cached at intermediate proxies.
Once the `web_key` feature is enabled, ZITADEL will send a `Cache-Control` header which allows caching.
By default and in ZITADEL Cloud we allow 5 minutes of caching:
```
Cache-Control: max-age=300, must-revalidate
```
Self-hosters can modify this setting through the `ZITADEL_OIDC_JWKSCACHECONTROLMAXAGE`
environment variable or in the configuration yaml:
```yaml
OIDC:
JWKSCacheControlMaxAge: 5m
```
Setting the value to `0` will result in a `no-store` value in the `Cache-Control` header.
@@ -0,0 +1,279 @@
---
title: Authenticate users with SAML
sidebar_label: SAML
---
SAML stands for Security Assertion Markup Language. It is a standard commonly used for identity federation and single sign-on (SSO). It is one of the original and most popular standards for SSO. Although it is prone to certain security flaws and exploits if not implemented correctly, it remains relevant and widely used.
## Why use SAML?
Here are some reasons why organizations might choose SAML:
**Legacy systems compatibility**
SAML has been in use since 2002 and is deeply integrated into many legacy systems and enterprise environments. Organizations with existing SAML infrastructure may prefer to continue using it to avoid costly and complex migrations.
**Enterprise use cases**
SAML is often favored in enterprise settings where detailed user attributes and complex authorization requirements are necessary. Its support for rich metadata and customizable assertions makes it suitable for intricate access control scenarios.
**Mature ecosystem**
The SAML ecosystem is mature, with extensive support from a wide range of enterprise applications and identity providers. This broad compatibility ensures that SAML can be used seamlessly across various platforms and services.
## Common SAML terms
- **Service Provider (SP)**: The application the user is trying to sign into.
- **Identity Provider (IdP)**: The centralized point of authentication.
- **SAML Request**: A communication from the SP to the IdP.
- **SAML Response**: A communication from the IdP to the SP, containing assertions about the user.
- **Assertions**: Statements within the SAML response about the user, signed using XML signatures.
- **Assertion Consumer Service (ACS)**: The endpoint at the SP responsible for processing the SAML response.
- **Attributes**: User information within the SAML response.
- **Relay State**: A way for the IdP to remember where a user was before authentication.
- **SAML Trust**: The configuration between the identity provider and the service provider
- **Metadata**: Trust information exchanged between the identity provider and service provider
## SAML explained
The **Service Provider (SP)** is the application that the user is trying to sign into. When the service provider sends a communication to the identity provider, it is called a **SAML request**. When the **Identity Provider (IdP)** responds or sends a communication to the service provider, it is called a SAML response.
Within the SAML response, there are multiple statements about the user, known as **assertions**. These assertions are all signed using XML signatures (also known as DSig) and are sent to be processed at the service provider's specific endpoint called an **Assertion Consumer Service (ACS)**. The ACS is responsible for receiving the SAML response from the identity provider, checking the assertions' signatures, and validating the entire document. This is a crucial part of implementing SAML from the service provider's perspective.
Additionally, the SAML response contains other pieces of information about the user, such as their first name, last name, and other profile information, referred to as **attributes**.
Another important concept in SAML is the **relay state**. The relay state allows the identity provider to remember where a user was before authentication. If a user is browsing anonymously through the service provider and triggers authentication, they will be redirected to the identity provider. After validating the user's identity, the identity provider will redirect them back to the service provider's ACS. The relay state makes sure that the user returns to their original location instead of being dropped on a generic home page.
**SAML trust** is the configuration between the identity provider and the service provider, involving shared information such as a signing certificate and an entity ID (also known as an issuer) from the identity provider. This shared information establishes a trust that allows both parties to validate communications, requests, and responses.
**Metadata** is another crucial term in SAML. Metadata allows for self-configuration between the identity provider and the service provider. Instead of manually exchanging certificates, endpoint URLs, and issuer information, metadata enables the sharing of an XML configuration file or URLs to these files. This allows the service provider and identity provider to self-configure based on the information within these configuration files, making the process less manual and more convenient.
## SAML workflow
One important aspect of SAML is that the user can initiate the authentication process in two primary workflows:
**1. IdP-Initiated: The user starts at the IdP, which sends a SAML response to the SP.**
Commonly, with workforce identity providers, there is a centralized user portal where a user can see a list of applications. Clicking on one of these applications initiates the authentication process without the user first going to the service provider. This method is called IdP-initiated (Identity Provider-initiated) authentication. In this flow, the user goes to the identity provider, which automatically kicks off the SAML response, directing them to the desired application.
**2. SP-Initiated: The user starts at the SP, which sends a SAML request to the IdP, followed by a SAML response from the IdP.**
The other method is SP-initiated (Service Provider-initiated) authentication. Here, the user starts at the service provider, which then sends a SAML request to the identity provider. The identity provider processes this request and sends back a SAML response.
In IdP-initiated flows, there is no SAML request, while in SP-initiated flows, the SAML request and response are both involved. The identity provider must understand how to receive a SAML request and create a SAML response. The service provider, particularly its Assertion Consumer Service (ACS), needs to validate the SAML response and potentially generate a SAML request if SP-initiated flow is supported.
It's important to note that not all service providers support both methods. Some only support IdP-initiated, while others only support SP-initiated. The choice between supporting IdP-initiated or SP-initiated authentication depends on the specific requirements of the product and the preferences of the developer implementing SAML. ZITADEL, for instance, supports only the SP-initiated flow.
## SAML requests and responses
SAML uses XML for both requests and responses. A typical SAML request from an SP to an IdP includes an ID, timestamp, destination URL, and issuer information. The IdP processes this request and returns a SAML response containing user assertions, which the SP validates.
Let's delve into what a SAML request and response look like with the following shortened examples.
**Sample SAML request**
```xml
<?xml version="1.0" encoding="utf-8"?>
<ns0:AuthnRequest
xmlns:ns0="urn:oasis:names:tc:SAML:2.0:protocol" ID="id-8LjuzBEUQFYFWjL55" Version="2.0" IssueInstant="2024-06-11T04:13:52Z" Destination="https://my-instance-xtzfbc.zitadel.cloud/saml/v2/SSO" AssertionConsumerServiceURL="http://127.0.0.1:5000/acs">
<ns1:Issuer
xmlns:ns1="urn:oasis:names:tc:SAML:2.0:assertion" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://zitadel-test.sp/metadata
</ns1:Issuer>
<ns2:Signature
xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<ns2:SignedInfo>
<ns2:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ns2:CanonicalizationMethod>
<ns2:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></ns2:SignatureMethod>
<ns2:Reference URI="#id-8LjuzBEUQFYFWjL55">
<ns2:Transforms>
<ns2:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ns2:Transform>
<ns2:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ns2:Transform>
</ns2:Transforms>
<ns2:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ns2:DigestMethod>
<ns2:DigestValue>WSz/EQ72RZ0DTh3DBSRCElpITqM=</ns2:DigestValue>
</ns2:Reference>
</ns2:SignedInfo>
<ns2:SignatureValue>HHjsNh0OLj7...</ns2:SignatureValue>
<ns2:KeyInfo>
<ns2:X509Data>
<ns2:X509Certificate>MIIDtzCCAp+gAwIBAgIUfITRQGue...</ns2:X509Certificate>
</ns2:X509Data>
</ns2:KeyInfo>
</ns2:Signature>
</ns0:AuthnRequest>
```
In this SAML request:
- The **ID** (id-8LjuzBEUQFYFWjL55) is generated by the service provider, and the identity provider will respond to this ID in the SAML response.
- **IssueInstant** (2024-05-11T04:13:52Z) is the timestamp for this request, used by the identity provider for verification to ensure the request is within an acceptable time frame.
- **Destination** (https://my-instance-xtzfbc.zitadel.cloud/saml/v2/SSO) points to the identity provider's URL, ensuring the request is sent to the correct recipient.
- **AssertionConsumerServiceURL** (http://127.0.0.1:5000/acs) indicates where the response should be sent after user authentication.
- **Issuer** (https://zitadel-test.sp/metadata) is a predefined string formatted as a URL, matching what the identity provider expects.
- **Signature** ensures the integrity and authenticity of the SAML request. It includes:
- **SignedInfo** contains details about the canonicalization and signature methods.
- **Reference** points to the signed data and includes a transform and digest method.
- **SignatureValue** provides the actual digital signature.
- **KeyInfo** containing the X.509 certificate, which holds the public key used to verify the signature.
These XMLs are stringified, encoded, and transmitted according to the SAML binding used. For instance, in HTTP Redirect Binding, the request is sent as URL parameters, while in HTTP POST Binding, it is included in the request body. The signature's transmission method also depends on the binding used, ensuring the integrity and authenticity of the SAML message across different communication channels.
**Sample SAML response**
```xml
<?xml version="1.0" encoding="utf-8"?>
<Response
xmlns="urn:oasis:names:tc:SAML:2.0:protocol" ID="_164ba12b-6711-40e0-8ddb-55aa810f1c92" InResponseTo="id-8LjuzBEUQFYFWjL55" Version="2.0" IssueInstant="2024-06-11T04:17:41Z" Destination="http://127.0.0.1:5000/acs">
<Issuer
xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://my-instance-xtzfbc.zitadel.cloud/saml/v2/metadata
</Issuer>
<Status>
<StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"></StatusCode>
</Status>
<Assertion
xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Version="2.0" ID="_6fbdb616-b77f-46af-9554-989c8b89eeda" IssueInstant="2024-06-11T04:17:41Z">
<Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://my-instance-xtzfbc.zitadel.cloud/saml/v2/metadata</Issuer>
<Signature
xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></CanonicalizationMethod>
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"></SignatureMethod>
<Reference URI="#_6fbdb616-b77f-46af-9554-989c8b89eeda">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></Transform>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
<DigestValue>/b1R9LJJSeNX...</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>n5GbV4xhkXV...</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MIIFITCCAwmgAwIBAgIBUTANBgkqh...</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
<Subject>
<NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">[email protected]</NameID>
<SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<SubjectConfirmationData NotOnOrAfter="2024-06-11T04:22:41Z" Recipient="http://127.0.0.1:5000/acs" InResponseTo="id-8LjuzBEUQFYFWjL55"></SubjectConfirmationData>
</SubjectConfirmation>
</Subject>
<Conditions NotBefore="2024-06-11T04:17:41Z" NotOnOrAfter="2024-06-11T04:22:41Z">
<AudienceRestriction>
<Audience>https://zitadel-test.sp/metadata</Audience>
</AudienceRestriction>
</Conditions>
<AuthnStatement AuthnInstant="2024-06-11T04:17:41Z" SessionIndex="_6fbdb616-b77f-46af-9554-989c8b89eeda">
<AuthnContext>
<AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</AuthnContextClassRef>
</AuthnContext>
</AuthnStatement>
<AttributeStatement>
<Attribute Name="Email" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<AttributeValue>[email protected]</AttributeValue>
</Attribute>
<Attribute Name="SurName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<AttributeValue>Stark</AttributeValue>
</Attribute>
<Attribute Name="FirstName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<AttributeValue>Tony</AttributeValue>
</Attribute>
<Attribute Name="FullName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<AttributeValue>Tony Stark</AttributeValue>
</Attribute>
<Attribute Name="UserName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<AttributeValue>[email protected]</AttributeValue>
</Attribute>
<Attribute Name="UserID" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<AttributeValue>260242264868201995</AttributeValue>
</Attribute>
</AttributeStatement>
</Assertion>
</Response>
```
In this SAML response:
- The **ID** (_164ba12b-6711-40e0-8ddb-55aa810f1c92) matches the ID from the SAML request (id-8LjuzBEUQFYFWjL55), allowing the service provider to verify it.
- **InResponseTo** references the ID of the SAML request.
- **IssueInstant** (2024-06-11T04:17:41Z) is the timestamp of the response.
- **Destination** (http://127.0.0.1:5000/acs) confirms that the response is intended for the service provider's Assertion Consumer Service URL.
- **Issuer** (https://my-instance-xtzfbc.zitadel.cloud/saml/v2/metadata) is the identity provider's unique string, used to verify the response's origin.
- **StatusCode** indicates the status of the authentication process, with a value of "Success." If the status is not "Success," it means there was a problem with the login, such as incorrect credentials or other authentication issues.
- **Signature** ensures the integrity and authenticity of the SAML response. It includes:
- **SignedInfo** contains details about the canonicalization and signature methods.
- **Reference** points to the signed data and including transforms and a digest method.
- **SignatureValue** provides the actual digital signature.
- **KeyInfo** contains the X.509 certificate, which holds the public key used to verify the signature.
- **Assertion** includes user information, such as email, surname, first name, full name, username, user ID etc.
- **Subject** contains the user's unique identifier.
- **Conditions** specify the response's validity window.
- **AuthnStatement** includes authentication details.
- **AttributeStatement** contains additional user attributes.
## SAML identity brokering
### How SAML identity brokering works
- **Initial Authentication Request**: A user attempts to access a service (SP1) that is protected by an IdP (IdP1).
- **Redirection to IdP1**: The user is redirected to IdP1 for authentication. If IdP1 trusts another IdP (IdP2) for authentication, it will redirect the user to IdP2.
- **Authentication at IdP2**: The user authenticates with IdP2, which generates a SAML assertion containing the user's identity and attributes.
- **Assertion Processing**: ZITADEL (IdP1) processes the SAML assertion from IdP2. ZITADEL then creates an independent SAML assertion based on the information received and its own policies.
- **Response to SP1**: ZITADEL (IdP1) sends this newly created SAML assertion to SP1, completing the authentication process.
- **Access Granted to SP1**:IdP1 then sends a final SAML assertion to SP1, which grants the user access to the requested service.
See [Let Users Login with Preferred Identity Provider](https://zitadel.com/docs/guides/integrate/identity-providers/introduction) for more information.
## Best practices for SAML implementation
Implementing SAML securely involves several best practices:
- Limit XML Parser Features: Disable unnecessary features to prevent XML external entity (XXE) attacks.
- Use Canonicalized XML: Normalize XML to prevent manipulation.
- Validate XML Schema: Ensure only expected XML formats are accepted.
- Validate Signatures: Check all signatures in the SAML response.
- Use SSL Encryption: Protect against interception.
- Validate Parties: Ensure the destination, audience, recipient, and issuer information is correct.
- Enforce Validation Window: Accept responses only within a valid time frame.
- Use Historical Cache: Track and reject duplicate IDs to prevent replay attacks.
- Minimize Buffer Size: Protect against DDoS attacks.
## Alternatives to SAML
SAML has its flaws; it can be complex and cumbersome to implement. The primary alternatives to consider is OpenID Connect (OIDC). Some illustrate this with the following analogy: “SAML is to OpenID Connect as SOAP is to REST." Just as REST was created to address some inherent flaws in SOAP, OpenID Connect was created to address some of the limitations in specifications like SAML. OpenID Connect is flexible, easy to use, widely adopted, and reliably secure.
For example, SAML is not well-suited for desktop and mobile applications due to its reliance on HTTP redirects, cookie-based session management, and complex certificate handling. OIDC, on the other hand, offers a modern, flexible, and simpler solution for authentication and authorization needs across desktop and mobile applications. It uses authorization codes and tokens that are easier to manage and supports custom URL schemes and deep linking, which simplifies the handling of redirects and improves the overall user experience​. Furthermore, OIDC offers more flexibility in managing sessions without relying solely on cookies.
If a project requires SAML due to specific requirements or existing infrastructure, it should be used. However, for new projects, it is advisable to consider OpenID Connect because it is a more modern standard and is the more popular choice in the industry.
## Testing SAML scenarios using ZITADEL
To test SAML scenarios with ZITADEL, follow these steps:
1. Integrate a SAML SP with ZITADEL as the IdP:
- Sign up for a ZITADEL account if you don't already have one. If you are self-hosting ZITADEL, you can skip this step.
- Create an Organization and a Project in ZITADEL.
- Within your project, create a SAML application.
- Follow this example on how to create a SAML SP and integrate ZITADEL as the SAML IdP: [ZITADEL Python SAML SP Integration](https://github.com/zitadel/python-saml-sp).
2. Integrate ZITADEL with another SAML IdP for identity brokering:
- Configure an identity provider that supports SAML.
- Set up the necessary metadata and endpoints. Here are some guides to help with this setup:
- [Configure Entra ID as a SAML IdP](https://zitadel.com/docs/guides/integrate/identity-providers/azure-ad-saml)
- [Configure Okta as a SAML IdP](https://zitadel.com/docs/guides/integrate/identity-providers/okta-saml)
- [Configure MockSAML as a SAML IdP](https://zitadel.com/docs/guides/integrate/identity-providers/mocksaml)
3. Create test users and simulate authentication requests:
- Create test users in ZITADEL.
- Simulate authentication requests to verify that the SAML assertions are correctly generated and transmitted.
- Ensure that the SAML assertions contain the expected attributes and that these attributes are correctly processed by the service provider.
4. Simulate various scenarios:
- Successful Login: Verify that a valid user can successfully authenticate and access the service.
- Failed Login: Test scenarios where authentication fails, such as incorrect credentials or disabled accounts.
- Attribute Mapping: Check that user attributes (e.g., roles, permissions) are correctly mapped and utilized by the service provider.
- Logout Requests: Test single logout (SLO) to ensure that logging out from one service logs the user out of all connected services.
For more information, refer to [SAML Endpoints in ZITADEL](https://zitadel.com/docs/apis/saml/endpoints).
@@ -0,0 +1,17 @@
Per default the login ui of the default organization is shown.
To allow users of a specific organization to register with their IDP (e.g Entra ID) you have two options.
1. Identify organization with organization scope
By sending the organization scope in the authorization request to ZITADEL the specified organization will directly be triggered.
The configured settings of that organization such as IDPs will directly be shown.
If only one IDP is configured and local accounts are disabled the user will directly be redirected to the external provider.
```
urn:zitadel:iam:org:id:{id}
```
2. Domain Discovery
When domain discovery is enabled, ZITADEL is able to identify the entered domain of a user and match it to a specific organization.
If you want to know more about domain discovery and how to set it up please read the following guide:
[Domain Discovery in ZITADEL](/guides/solution-scenarios/domain-discovery)
@@ -0,0 +1,218 @@
---
title: Onboard B2B customers using organizations
sidebar_label: Onboard B2B customer
---
In this guide we will explain how you can create and set up new organizations in ZITADEL to help you with your onboarding flows.
Creating a new organization is the best choice for multi-tenancy use cases that require separation of customers, teams, or groups of users.
We will also explain how to leverage [Managers](/guides/manage/console/managers) to delegate self-service team management and configuration of policies to users of each organization.
When you want to build an onboarding process for your business customers you have to go through the following steps:
1. Create an organization: The organization represents the customer or a team
2. Create the first administrator user: This user is the account for your customer, which should be able to configure settings such as SSO, MFA, etc.
3. Give the user permission to configure settings, create users and assign roles to users in ZITADEL
As soon as you have successfully created the organization and added an administrator, then your customer is able to start managing the organization and users themselves.
The first actions they typically want to take are the following:
1. Invite Team member
2. Configure SSO
3. Configure Security Settings
4. Configure Branding
ZITADEL does have multiple possibilities to achieve that process.
## Onboard customers through the ZITADEL Management Console
Each ZITADEL instance does automatically bring a management console with it. The [console](/guides/manage/console/console-overview) can be used to manage all your resources through a UI.
You can access it by calling the following URL: `${CUSTOM-DOMAIN}/ui/`
Make sure that your admins have a [Administrator role](/guides/manage/console/managers) with permissions on an instance level such as "INSTANCE_OWNER"
### Create a customer
You do have two different options to create a new Organization.
1. If you are on a specific organization, click the dropdown in the header and press click "+ New Organization"
<img src="/docs/img/guides/solution-scenarios/create-org-dropdown.png" alt="Create Organization Dropdown"/>
2. If you are on the instance level, go to the Organization tab and click the "+ New" button above the Organization list.
<img src="/docs/img/guides/solution-scenarios/org-list-add-new.png" alt="Organization List - Add new Organization"/>
No matter which option you choose you will be redirected to the create organization screen.
If the setting "Use your personal account as organization owner" is enabled, your user will automatically get the role "ORG_OWNER" in the organization.
Give the organization a name and create it.
<img src="/docs/img/guides/solution-scenarios/create-organization.png" alt="Create Organization"/>
Click on the newly created organization in the list and you will switch your context to that organization.
### Add First Administrator
Create the first user for your customer and ensure the user has enough permissions to self-manage the needed configuration.
#### Create User
import CreateUser from "../../../guides/manage/console/_create-user.mdx";
<CreateUser components={props.components} />
#### Add Administrator Role to User
Now you need to assign the right administrator role to your user. In this case we want to give "ORG_OWNER".
If you do want to know more about the roles check out the [ZITADEL Managers Guide with the Role List](/guides/manage/console/managers)
Go to the detail page of the organization and select the "+" button in the top right corner, where you already see a list of existing managers.
<img alt="Managers" src="/docs/img/guides/console/managers.png" width="200px" />
import AddManager from "../../../guides/manage/console/_add_manager.mdx";
<AddManager components={props.components} name="AddManager" />
### Invite Team Members
The first user of your customer is now ready to authenticate and manage resources in ZITADEL.
First task will be inviting a team member.
We are now switching to the view of your customer's administrator user.
<Callout>
The following actions can also be configured by an Instance Administrator for their customer.
To show how you can use the self-management possibilities it is shown from the customer's administrator.
</Callout>
The Administrator user received an initialization e-mail after the user was created.
With the link in the email the admin will be able to setup a password and optionally some multi-factors.
As the administrator only has permission for its own organization the ZITADEL Management Console UI does look slightly different.
To invite a team member the admin has to repeat the steps you did before.
1. [Create User](#create-user)
2. [Add role "ORG_OWNER" to the user](#add-administrator-role-to-user)
### Setup Single-Sign-On (SSO)
Your next step is to configure SSO so your users can authenticate with an existing user into ZITADEL.
First go to the Login Behavior and Security Settings.
Make sure that the "External IDP allowed" is enabled.
This setting does generally allow users to authenticate with an external provider.
1. Go to the Settings Page
2. Navigate to Identity Providers
You might see already a list of activated providers here. If so, this is because some default providers are configured on the ZITADEL instances.
3. Configure the identity provider you need.
Follow our detailed configuration description of the different providers we have: [Configure the needed identity provider](/guides/integrate/identity-providers/introduction)
#### Auto-register users with SSO
Let's assume you have configured Entra ID as an identity provider, and you want to allow all your employees to login with the corresponding user without having to register.
This does need some specific configuration on your provider.
1. Go to the detail page of your configured identity provider (In this example Entra ID)
2. Enable "Automatic creation" in the optional settings. Optionally if you want to update the user information in ZITADEL, when they have changed in the Entra ID additionally enable "Automatic update"
3. Enable the "Account creation allowed" if it is not already
4. If you also want to allow users to link to an existing account, if they already have an account in ZITADEL, enable "Account linking allowed"
<Callout>
It is only possible to automatically create accounts, that send all the required information to register a new user.
If your provider does not send all the required fields that ZITADEL needs to create a user, make sure to fill them in the background with an Action.
[Example for prefilling user data automatically](/guides/integrate/identity-providers/additional-information#automatically-pre-fill-user-data)
</Callout>
#### Login with your user
Last step is that your user can login to ZITADEL and use the SSO account.
import OrgLoginDescription from "./_org_login_description.mdx";
<OrgLoginDescription components={props.components} name="OrgLodinDescription" />
Your user is now ready to login with SSO.
<img src="/docs/img/guides/solution-scenarios/user-login-with-external-idp.png" alt="User Login with SSO"/>
## Automated onboarding for your customers
If you want to start automating the process of onboarding your customers the following sections give you some guidance.
### Built-in register organization form
A basic form that allows a customer to enter an organization name and a user account is hosted on the following URL:
`{custom-domain}/ui/login/register/org`
When a user registers through this form, an organization and a user are created.
The user will automatically get the role "ORG_OWNER" withing ZITADEL and is able to manage the whole organization.
You can read more about the managers, roles and their meanings [here](/guides/manage/console/managers)
<img src="/docs/img/guides/solution-scenarios/register-organization.png" alt="Register Organization Form"/>
#### Disable built-in register organization form
If you do not want to allow users to register organizations with this form, you can disable it with the following request27: [Restrict the instance features](/reference/api/admin/zitadel.admin.v1.AdminService.SetRestrictions)
Disabling the form makes sense, if your administrators manage new customers themselves or if you build your own form.
### Build your own form with setup organization request
If the built-in register form doesn't fulfill your needs we recommend building your own form.
The administration API of ZITADEL, allows you to setup a new organization with a first administrator user.
The setup organization requests, has the possibility to specify an organization with its name and a domain.
You can directly send a human user with all the needed information like the profile, email, password. etc.
This request allows you only to setup a user with password authentication at the moment.
By specifying the roles you can define, which permission the user should have within ZITADEL.
Per default the user will automatically get "ORG_OWNER".
<img src="/docs/img/guides/solution-scenarios/register-team-graphic.png" alt="Setup Organization with Admin User Graphic"/>
#### Example Request
```bash
curl -L -X POST 'https://${CUSTOM_DOMAIN}/admin/v1/orgs/_setup' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"org": {
"name": "Organisation C",
"domain": "org-c.com"
},
"human": {
"userName": "gigi-giraffe",
"profile": {
"firstName": "Gigi",
"lastName": "Giraffe",
"nickName": "gigi-giraffe",
"displayName": "Gigi Giraffe",
"preferredLanguage": "en",
"gender": "GENDER_UNSPECIFIED"
},
"email": {
"email": "[email protected]",
"isEmailVerified": true
},
"phone": {
"phone": "+41 71 000 00 00",
"isPhoneVerified": true
},
"password": "my_53cr3t-P4$$w0rd"
},
"roles": [
"string"
]
}'
```
Detailed description of [Setup Organization](/reference/api/admin/zitadel.admin.v1.AdminService.SetUpOrg)
If you need to add custom data to either the organization or the user you can use the metadata.
Metadata is a key value construct that allows you to store any additional information to the resources.
The set organization metadata request allows you to add one key value pair to an organization:
[Set Organization Metadata](/reference/api/management/zitadel.management.v1.ManagementService.SetOrgMetadata)
If you have more than one field, you can use the bulk add request:
[Bulk Set Organization Metadata](/reference/api/management/zitadel.management.v1.ManagementService.BulkSetOrgMetadata)
The same requests also exist on the user resource:
[Set User Metadata](/reference/api/management/zitadel.management.v1.ManagementService.SetUserMetadata)
[Bulk Set User Metadata](/reference/api/management/zitadel.management.v1.ManagementService.BulkSetUserMetadata)
@@ -0,0 +1,148 @@
---
title: Onboard Users
sidebar_label: Onboard Users
---
import CreateUser from "../../../guides/manage/console/_create-user.mdx";
import OrgLoginDescription from "./_org_login_description.mdx";
End Users have three different possibilities on how to login with ZITADEL.
1. Local Account with Username, Password, MFA, Passkey, etc
2. Social Login like Google, Apple, Github, etc
3. External Identity Provider hosted/managed by Organization like Entra ID, LDAP, Okta etc
You can either use the hosted login of ZITADEL to let users register themselves, or you can build your own UI and use the existing APIs.
## Manually add/invite users
<CreateUser components={props.components} />
## Automated / Self-registration possibilities
If you want to start automating the process of onboarding your users and let them do self-registration the following sections give you some guidance.
### Built-in register form
#### Prompt registration form
When users first access your application, they'll typically need to either log in or register.
By default, redirecting from your application displays the login page.
OpenID Connect (OIDC) allows you to control the initial screen by sending a [prompt](/apis/openidoauth/endpoints#additional-parameters) parameter in the authorization request.
With the `prompt=create`, the registration form/options will directly be shown to the user.
You can test the impact of the different prompts on your login UI in our [OIDC Playground](https://zitadel.com/playgrounds/oidc).
Per default a user will be registered to the default organization.
By sending the scope below in your authorization request you can choose the organization to which the user will be added.
```
urn:zitadel:iam:org:id:{id}
```
Unfortunately, SAML doesn't offer the same level of control over the initial screen.
You won't be able to directly influence which page (login or registration) is shown through the SAML flow.
#### Choose Registration Option
If an organization allows local user registration as well as registration with a social or enterprise login, the user can choose an option to register.
As soon as users click the "register" button, they will be presented with a screen showing the different registration options.
<img
src="/docs/img/guides/solution-scenarios/register-options.png"
alt="Register Options"
/>
After that, the user can select either local user registration or an external provider.
By pressing the button of an external provider, the user will directly be redirected to the provider for consent.
<Callout>
The options are dependent on the settings of the organization.
If only one option is possible, the option will directly be selected and shown.
</Callout>
#### Local User Registration
To allow users to register themselves, you have to enable the "register allowed" in the login behavior settings.
You will now see the register button on the login screen.
<img
src="/docs/img/guides/solution-scenarios/register-end-user.png"
alt="Register End User"
/>
If nothing else is specified, a user will be registered to the default organization.
<img
src="/docs/img/guides/solution-scenarios/default-organization.png"
alt="Default Organization"
/>
You can specify another organization, by sending the organization scope in the authorization requests.
By sending the scope below the settings of the specified organization will be triggered and only users of the said organization will be able to authenticate.
The users will be registered to the given organization.
```
urn:zitadel:iam:org:id:{id}
```
If the user chooses to register a local account, the register form will be shown.
All the mandatory fields like first name, last name, e-mail and password have to be filled.
You can only setup authentication with the built-in form.
<img
src="/docs/img/guides/solution-scenarios/register-local-user.png"
alt="Register local user"
/>
#### Registration with Social Login
To allow your users to register with social logins you have to configure the external identity providers.
If you only need the social logins for your end users and you want to have them all in the organization, we recommend using the default organization for those users.
In that case you can configure the identity providers on the default organization.
If you want to have the social logins on different organizations you can configure the default on the instance, and enable it on the needed organizations.
Please follow the configuration guides for the needed providers: [Let Users Login with Preferred Identity Provider in ZITADEL](/guides/integrate/identity-providers/introduction)
The configured providers will be shown on the first login screen or when the users click on the registration button, they will be able to choose between local account or the social login.
<img
src="/docs/img/guides/solution-scenarios/register-end-user.png"
alt="Register End User"
/>
#### Registration with Organization External Identity Provider
If your business customer already have an identity provider, and you want to allow SSO for them, you can configure their providers directly for their organization.
Configure the needed provider such as Entra ID or OKTA.
Please follow the configuration guides for the needed providers: [Let Users Login with Preferred Identity Provider in ZITADEL](/guides/integrate/identity-providers/introduction)
<OrgLoginDescription components={props.components} name="OrgLoginDescription" />
### Build your own registration form
ZITADEL allows you to build your own registration form and login UI.
The registration process highly depends on your needs.
We do have a guide series on how to build your own login ui, which also includes the registration of different authentication methods, such as:
- Password authentication
- Multi-Factor
- Passkeys
- External Login Providers
You can find all the guides here: [Build your own login UI](/guides/integrate/login-ui)
#### Custom fields
The [create user request](/reference/api/user/zitadel.user.v2.UserService.AddHumanUser) also allows you to add [metadata](/guides/manage/customize/user-metadata) (key, value) to the user.
This gives you the possibility to collect additional data from your users during the registration process and store it directly to the user in ZITADEL.
Those metadata can also directly be included in the [token](/guides/manage/customize/user-metadata#use-tokens-to-get-user-metadata) of the user.
We recommend storing business relevant data in the database of your application, and only authentication and authorization relevant data in ZITADEL to follow the separation of concern pattern.
#### Registration with Organization External Identity Provider
If you want to know more about the multi-tenancy possibilities of ZITADEL, read the following blog post:
[Multi-Tenancy and Delegated Access Management with Organizations](https://zitadel.com/blog/multi-tenancy-with-organizations)
@@ -0,0 +1,17 @@
---
title: "Onboard Customers and Users"
description: "When building your own application, one of the first questions you have to face, is 'How do my customers onboard to my application?'
These guides will explain the built-in solution for onboarding new tenants, customers, and users and how you can handle more advanced onboarding use cases. "
---
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
import { Card, Cards } from 'fumadocs-ui/components/card';
import { FileText, Folder, Link as LinkIcon } from 'lucide-react';
<Cards>
<Card title="B2b" href="/guides/integrate/onboarding/b2b" icon={<FileText />} />
<Card title="End Users" href="/guides/integrate/onboarding/end-users" icon={<FileText />} />
</Cards>
@@ -0,0 +1,490 @@
---
title: Retrieve User Roles in ZITADEL
sidebar_label: Retrieve User Roles
---
This guide explains all the possible ways of retrieving user roles across different organizations and projects using ZITADEL's APIs.
## What are roles/authorizations/grants in ZITADEL?
User roles, user grants, or authorizations refer to the roles that are assigned to a user. These terms are used interchangeably to mean the roles assigned to the user, e.g., the ZITADEL Management Console refers to the pairing of roles and users as authorizations, whereas the APIs refer to them as grants. This guide will use the term roles for application-specific roles (e.g., `admin`, `accountant`, `employee`, `hr`, etc.) and ZITADEL-specific administrator roles (e.g., `IAM_OWNER`, `ORG_OWNER`, `PROJECT_OWNER`, etc.).
Roles are critical to managing permissions in a single-tenant or multi-tenant application. It can, however, be tricky to retrieve them, especially when spanning multiple organizations and projects.
## Assign roles and memberships
Human or service users can be assigned roles. You can do this via the ZITADEL Management Console or the ZITADEL APIs. As mentioned earlier, there are two types of roles in ZITADEL. You can have your own application-specific roles, and alternatively, ZITADEL also has administrator roles, such as `ORG_OWNER` and `IAM_OWNER`.
Follow the links below to assign roles to your users.
- [Add application roles via the ZITADEL Management Console](/guides/manage/console/roles)
- [Add administrator roles via the ZITADEL Management Console](/guides/manage/console/managers)
- [Add application roles via the ZITADEL Management API](/reference/api/project)
- [Add administrator roles to users via the ZITADEL Management API](/reference/api/management)
## Retrieve roles
Roles can be requested via our auth and management APIs, from userinfo endpoint or ID token. Currently, administrator roles cannot be directly included in the token. You will need to use the ZITADEL APIs to retrieve them.
### Generate a token
You must first of all generate a token for the user. For human users, the typical approach involves using a front-end application and logging in through the browser or device. An access token will be returned after they log in successfully. A machine user will use a script or other program to generate a token using the JWT profile or client credentials grant types.
How to generate a token:
- [Generate tokens for human users](/guides/integrate/login/oidc/login-users)
- [Generate tokens for service users](/guides/integrate/service-users/authenticate-service-users)
In order to access role information via the token you must include the right audience and the necessary role claims in the scope and/or select the required role settings in the ZITADEL management console before requesting the token.
### Determine the audience
An important concept in OpenID Connect (OIDC) is the 'audience' (`aud`) claim, which is part of the token payload. The `aud` claim identifies who or what this token is intended for. If the recipient (e.g., a resource server) does not identify itself with a value in the `aud` claim when this claim is present, then the token must be rejected.
The audience is essential in multi-tier systems, where you may authenticate with one client (in one project) but access resources from another client (in a different project) or when you are accessing ZITADEL’s management APIs. Without the correct audience in your token, you will run into errors, such as the ‘Invalid audience’ error in ZITADEL.
You can determine the audience in two ways:
**1. Use the explicit scope for ZITADEL to access only ZITADEL APIs:**
If your application needs to access ZITADEL's APIs (for example, to pull a list of all users), follow this steps:
- Add the scope `urn:zitadel:iam:org:project:id:zitadel:aud` to the authentication request when authenticating the user. Now, the application can make calls to ZITADEL's API without running into an ‘Invalid audience’ error.
**2. Include the project id of the ZITADEL project in the scope for accessing the ZITADEL APIs or anything else:**
Let's assume you have a frontend application and a backend application under different projects. Here's how to add the correct audience:
- Authenticate the end-users to an application in your front-end project.
- In the authentication request sent from the front-end application, add the scope `urn:zitadel:iam:org:project:id:{projectId}:aud`, replacing `{projectId}` with the project ID of your backend application.
- Now, the front end can send requests to the backend. The backend will validate the token with ZITADEL's introspection endpoint and will not return an ‘Invalid audience’ error.
And you can also use the same to access the ZITADEL APIs.
### Role settings in the ZITADEL Management Console
If you need user roles returned from the userinfo endpoint, you must select the **’Assert Roles on Authentication’** checkbox in your project under general settings.
<img
src="/docs/img/guides/integrate/retrieve-user-roles-1.png"
width="75%"
alt="Assert Roles on Authentication"
/>
If you need them included in your ID Token, select **’User Roles Inside ID Token’** in application settings. This has to be set in your applications as this is dependent on your application type. Navigate to your application and select this setting.
<img
src="/docs/img/guides/integrate/retrieve-user-roles-2.png"
width="75%"
alt="Assert Roles on Authentication"
/>
Alternatively, you can include the claims `urn:iam:org:project:roles` or/and `urn:zitadel:iam:org:projects:roles` in your scope to achieve the same as above.
### Determine the roles assigned to a user
To determine the roles assigned to a user, you have two options:
- **Token claims**: Request that the relevant roles claim (such as `urn:zitadel:iam:org:project:{projectId}:roles`) is included in the token when you authenticate. The roles assigned to the user will then be part of the token’s payload.
- **Token introspection**: After a token is issued, you can introspect it using the [ZITADEL introspection endpoint](/guides/integrate/token-introspection) to view the roles claim. This will return the roles assigned to the user according to the token.
### Retrieve roles from the userinfo endpoint
The user info endpoint is **`${CUSTOM_DOMAIN}`/oidc/v1/userinfo**.
This endpoint will return information about the authenticated user.
Send the access token of the user as `Bearer Token` in the `Authorization` header:
**cURL Request:**
```bash
curl --request GET \
--url ${CUSTOM_DOMAIN}/oidc/v1/userinfo
--header 'Authorization: Bearer <TOKEN>'
```
If the access token is valid, the information about the user (depending on the granted scopes) is returned. Check the [Claims page](/apis/openidoauth/claims) for more details.
**Sample responses:**
**1. Scope used:** `openid email profile urn:zitadel:iam:org:project:id:zitadel:aud`
**Sample response**:
```bash
{
"email": "[email protected]",
"email_verified": true,
"family_name": "Wallace",
"gender": "male",
"given_name": "David",
"locale": "en",
"name": "David Wallace",
"nickname": "David",
"preferred_username": "david.wallace",
"sub": "223427827918176513",
"updated_at": 1689669364,
"urn:zitadel:iam:org:project:223281986649719041:roles": {
"cfo": {
"223281939119866113": "corporate.user-authorizations-io8epz.zitadel.cloud"
},
"corporate member": {
"223279178798072065": "org-a.user-authorizations-io8epz.zitadel.cloud",
"223279223391912193": "org-b.user-authorizations-io8epz.zitadel.cloud"
}
},
"urn:zitadel:iam:org:project:roles": {
"cfo": {
"223281939119866113": "corporate.user-authorizations-io8epz.zitadel.cloud"
},
"corporate member": {
"223279178798072065": "org-a.user-authorizations-io8epz.zitadel.cloud",
"223279223391912193": "org-b.user-authorizations-io8epz.zitadel.cloud"
}
}
}
```
This request can be tested out in the following way:
1. Select the **‘Assert Roles on Authentication’** checkbox.
2. Do not include the roles claims in the scope.
3. When you run the command, you will see that the roles were returned.
4. If you unselect the **‘Assert Roles on Authentication’** checkbox, you will not see the roles.
**2. Scope used:** `openid email profile urn:zitadel:iam:org:project:id:{projectId}:aud urn:iam:org:project:roles urn:zitadel:iam:org:projects:roles`
<Callout>
In order to stay up-to-date with the latest ZITADEL standards, we recommend that you use the roles from the identifier `urn:zitadel:iam:org:project:{projectId}:roles` rather than `urn:zitadel:iam:org:project:roles`. While both identifiers are maintained for backwards compatibility, the format which includes the specific ID represents our more recent model.
</Callout>
**Sample response:**
```bash
{
"email": "[email protected]",
"email_verified": true,
"family_name": "Wallace",
"gender": "male",
"given_name": "David",
"locale": "en",
"name": "David Wallace",
"nickname": "David",
"preferred_username": "david.wallace",
"sub": "223427827918176513",
"updated_at": 1689669364,
"urn:zitadel:iam:org:project:223281986649719041:roles": {
"cfo": {
"223281939119866113": "corporate.user-authorizations-io8epz.zitadel.cloud"
},
"corporate member": {
"223279178798072065": "org-a.user-authorizations-io8epz.zitadel.cloud",
"223279223391912193": "org-b.user-authorizations-io8epz.zitadel.cloud"
}
},
"urn:zitadel:iam:org:project:roles": {
"cfo": {
"223281939119866113": "corporate.user-authorizations-io8epz.zitadel.cloud"
},
"corporate member": {
"223279178798072065": "org-a.user-authorizations-io8epz.zitadel.cloud",
"223279223391912193": "org-b.user-authorizations-io8epz.zitadel.cloud"
}
}
}
```
This request can be tested out in the following way:
1. Do not select the **‘Assert Roles on Authentication’** checkbox
2. Include the role claims in the scope as given.
3. When you run the command, you will see the roles in the response.
4. If you remove the role claims in the scope and run the command, you will not receive the roles.
#### Customize roles using actions
If your application requires a custom role structure, [ZITADEL actions](/apis/actions/complement-token#pre-userinfo-creation-id-token-userinfo-introspection-endpoint) allow you to customize your claims.
<details open="open">
<summary>Example on github</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/custom_roles.js
```
</details>
### Retrieve roles using the auth API
Now we will use the auth API to retrieve roles from a logged in user using the user’s token
The base URL is: **https://`${CUSTOM_DOMAIN}`/auth/v1**
Let’s start with a user who has multiple roles in different organizations in a multi-tenanted set up. You can use the logged in user’s token or the machine user’s token to retrieve the authorizations using the [APIs listed under user authorizations/grants in the auth API](/reference/api/authorization).
**Scope used:** `openid urn:zitadel:iam:org:project:id:zitadel:aud`
#### **1. [List my project roles](/reference/api/auth/zitadel.auth.v1.AuthService.ListMyProjectPermissions)**
Returns a list of roles for the authenticated user and for the requesting project (based on the token).
**URL: https://`${CUSTOM_DOMAIN}`/auth/v1/permissions/me/_search**
**cURL request:**
```bash
curl -L -X POST 'https://${CUSTOM_DOMAIN}/auth/v1/permissions/me/_search' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>'
```
**Sample response:**
```bash
{
"result": [
"cfo"
]
}
```
#### **2.[List my ZITADEL permissions](/reference/api/auth/zitadel.auth.v1.AuthService.ListMyZitadelPermissions)​**
Returns a list of permissions the authenticated user has in ZITADEL based on the administrator roles the user has. (e.g: `ORG_OWNER` = `org.read`, `org.write`, ...).
This request can be used if you are building a management UI. For instance, if the UI is managing users, you can show the management functionality based on the permissions the user has. Here’s an example: if the user has `user.read` and `user.write` permission you can show the edit buttons, if the user only has `user.read` permission, you can hide the edit buttons.
**URL: https://`${CUSTOM_DOMAIN}`/auth/v1/permissions/zitadel/me/_search**
**cURL Request:**
```bash
curl -L -X POST 'https://${CUSTOM_DOMAIN}/auth/v1/permissions/zitadel/me/_search' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>'
```
**Sample result:**
```bash
{
"result": [
"org.read",
"user.read",
"user.global.read",
"user.write",
"user.delete",
"user.grant.read",
"user.grant.write",
"user.grant.delete",
"user.membership.read",
"policy.read",
"project.read",
"project.role.read",
"org.member.read",
"org.idp.read",
"org.action.read",
"org.flow.read",
"project.member.read",
"project.app.read",
"project.grant.read",
"project.grant.member.read",
"project.grant.user.grant.read",
"project.read:self",
"project.create"
]
}
```
#### **[3. List my authorizations/grants​](/reference/api/auth/zitadel.auth.v1.AuthService.ListMyUserGrants)**
Returns a list of user grants the authenticated user has. User grants consist of an organization, a project and roles.
**URL: https://`${CUSTOM-DOMAIN}`/auth/v1/usergrants/me/_search**
**cURL request:**
```bash
curl -L -X POST 'https://${CUSTOM_DOMAIN}/auth/v1/usergrants/me/_search' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"query": {
"offset": "0",
"limit": 100,
"asc": true
}
}'
```
**Sample result:**
```bash
{
"details": {
"totalResult": "3",
"processedSequence": "339",
"viewTimestamp": "2023-07-19T09:20:34.371331Z"
},
"result": [
{
"orgId": "223279178798072065",
"projectId": "223281986649719041",
"userId": "223427827918176513",
"roles": [
"corporate member"
],
"orgName": "Org A",
"grantId": "223428842084106497",
"details": {
"sequence": "296",
"creationDate": "2023-07-18T08:46:07.692435Z",
"changeDate": "2023-07-18T08:46:07.692435Z",
"resourceOwner": "223279178798072065"
},
"orgDomain": "org-a.user-authorizations-io8epz.zitadel.cloud",
"projectName": "HR",
"projectGrantId": "223282340514758913",
"roleKeys": [
"corporate member"
],
"userType": "TYPE_HUMAN"
},
{
"orgId": "223279223391912193",
"projectId": "223281986649719041",
"userId": "223427827918176513",
"roles": [
"corporate member"
],
"orgName": "Org B",
"grantId": "223428980244480257",
"details": {
"sequence": "298",
"creationDate": "2023-07-18T08:47:30.015324Z",
"changeDate": "2023-07-18T08:47:30.015324Z",
"resourceOwner": "223279223391912193"
},
"orgDomain": "org-b.user-authorizations-io8epz.zitadel.cloud",
"projectName": "HR",
"projectGrantId": "223282930787549441",
"roleKeys": [
"corporate member"
],
"userType": "TYPE_HUMAN"
},
{
"orgId": "223281939119866113",
"projectId": "223281986649719041",
"userId": "223427827918176513",
"roles": [
"cfo"
],
"orgName": "Corporate",
"grantId": "223428420858544385",
"details": {
"sequence": "293",
"creationDate": "2023-07-18T08:41:56.649257Z",
"changeDate": "2023-07-18T08:44:33.094117Z",
"resourceOwner": "223281939119866113"
},
"orgDomain": "corporate.user-authorizations-io8epz.zitadel.cloud",
"projectName": "HR",
"roleKeys": [
"cfo"
],
"userType": "TYPE_HUMAN"
}
]
}
```
### Retrieve roles using the management API
Now we will use the management API to retrieve user roles under an admin user.
The base URL is: **https://`${CUSTOM_DOMAIN}`/management/v1**
In [APIs listed under user grants in the management API](/reference/api/management), you will see that you can use the management API to retrieve and modify user grants. The two API paths that we are interested in to fetch user roles are given below.
**Scope used:** `openid urn:zitadel:iam:org:project:id:zitadel:aud`
#### **1. [Search user grants](/reference/api/management/zitadel.management.v1.ManagementService.ListUserGrants)​**
Returns a list of user roles that match the search queries. A user with manager permissions will call this API and will also have to reside in the same organization as the user.
**URL: https://`${CUSTOM_DOMAIN}`/management/v1/users/grants/_search**
**cURL request:**
```bash
curl -L -X POST 'https://${CUSTOM_DOMAIN}/management/v1/users/grants/_search' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data '{
"query": {
"offset": "0",
"limit": 100,
"asc": true
},
"queries": [
{
"user_id_query": {
"user_id": "223427827918176513"
}
}
]
}
```
**Sample result:**
```bash
{
"details": {
"totalResult": "1",
"processedSequence": "342",
"viewTimestamp": "2023-07-19T11:24:58.769023Z"
},
"result": [
{
"id": "223428420858544385",
"details": {
"sequence": "293",
"creationDate": "2023-07-18T08:41:56.649257Z",
"changeDate": "2023-07-18T08:44:33.094117Z",
"resourceOwner": "223281939119866113"
},
"roleKeys": [
"cfo"
],
"state": "USER_GRANT_STATE_ACTIVE",
"userId": "223427827918176513",
"userName": "david.wallace",
"firstName": "David",
"lastName": "Wallace",
"email": "[email protected]",
"displayName": "David Wallace",
"orgId": "223281939119866113",
"orgName": "Corporate",
"orgDomain": "corporate.user-authorizations-io8epz.zitadel.cloud",
"projectId": "223281986649719041",
"projectName": "HR",
"preferredLoginName": "david.wallace",
"userType": "TYPE_HUMAN"
}
]
}
```
#### **2. [User grant by ID](/reference/api/management/zitadel.management.v1.ManagementService.GetUserGrantByID)​**
Returns a user grant per ID. A user grant is a role a user has for a specific project and organization.
**URL: https://`${CUSTOM_DOMAIN}`/management/v1/users/:userId/grants/:grantId**
**cURL request:**
```bash
curl -L -X GET 'https://${CUSTOM_DOMAIN}/management/v1/users/:userId/grants/:grantId' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>'
```
## Summary
The process of retrieving a user's roles involves understanding the audience scope, getting a token, and accessing the correct API endpoints based on your requirement. Following these steps will help efficiently manage roles in single and multi-tenant applications.
@@ -0,0 +1,119 @@
---
title: SCIM Provisioning from Okta
---
This guide provides step-by-step instructions to configure SCIM provisioning from Okta into ZITADEL.
## Pre-requisites:
* Access to your ZITADEL Organization with an **Org Owner** role.
* Access to your Okta Admin dashboard.
* An existing **SAML app integration** between Okta (Identity Provider) and ZITADEL (Service Provider).
## Step 1: Set Up SCIM Provisioning in ZITADEL
SCIM provisioning in ZITADEL is accomplished by authenticating a Service User with appropriate permissions.
1. **Create a Service User**:
* Follow [this guide](https://zitadel.com/docs/guides/manage/console/users-overview) to create a Service User within your ZITADEL Organization.
2. **Assign the Role**:
* Grant the Service User the **Org User Manager** role. No higher managerial role is required.
3. **Choose an Authentication Method**:
* Select one of these two supported authentication methods:
* Personal Access Token \- PAT
* Client Credentials Grant
4. Detailed instructions to authenticate the Service User can be found [here](https://zitadel.com/docs/guides/integrate/service-users/authenticate-service-users).
## Step 2: Set Up SCIM in Okta
Follow these precise steps to configure SCIM provisioning in Okta:
1. Log in to your Okta Admin Console.
2. Navigate to **Applications** → **Application** and the existing **SAML app** linked to ZITADEL.
3. Select the **General** tab, then choose **Edit** for **App Settings**.
4. In the **Provisioning** section, select **SCIM** and then **Save**.
<img src="/docs/img/manage/users/enable-scim-provisioning.png" alt="Enable SCIM provisioning in Okta"/>
5. Under the **General** tab, also confirm that [Federation Broker Mode](https://help.okta.com/en-us/content/topics/apps/apps-fbm-main.htm) is disabled.
6. Click on the **Provisioning** tab, then go to the **Integration** tab and select **Edit**.
<img src="/docs/img/manage/users/select-provisioning-actions.png" alt="Select provisioning actions in Okta"/>
7. Enter the **SCIM connector base URL** using this format:
```https://${ZITADEL_DOMAIN}/scim/v2/{orgId}```
Like the example in the above image:
```https://test-domain-bkeog4.us1.zitadel.cloud/scim/v2/322355063156684166```
*(Find more details about endpoints [here](https://zitadel.com/docs/apis/scim2#supported-endpoints)).*
8. For **Unique identifier field for users**, enter **userName**.
9. Under **Supported provisioning actions**, select ***Push New Users*** and ***Push Profile Updates***.
10. Choose your authentication method under **Authentication Mode**:
* **HTTP Header** if using a Personal Access Token (PAT).
* **OAuth 2** if using Client Credentials Grant.
11. Provide the authentication details according to your chosen method:
* For **HTTP Header (PAT)**, enter the PAT token generated from ZITADEL.
* For **OAuth 2**, provide the client credentials (Client ID, Client Secret, token URL, authorization URL).
12. Click **Test Connection Configuration** to verify the integration (optional but recommended), then click **Save**.
13. Under the **Provisioning to App** settings, enable:
* **Create Users**
* **Update User Attributes**
* **Deactivate Users**
14. Click **Save** to apply these settings.
<img src="/docs/img/manage/users/provisioning-to-app.png" alt="Enable provisioning to App in Okta"/>
## Step 3: Attribute Mapping (Recommended)
Review and adjust attribute mappings in Okta as needed:
* Ensure standard attributes such as `userName`, `email`, `name.givenName`, and `name.familyName` are correctly mapped.
## Step 4: Verify SCIM Provisioning
* Assign the configured application to test users/groups in Okta.
* Verify that users are automatically provisioned into ZITADEL by checking under **Users** in your ZITADEL console.
* Validate attribute synchronization and lifecycle management (activation, updates, deactivation).
## Helpful Reference Links
* [Authenticate users with SAML](https://zitadel.com/docs/guides/integrate/login/saml)
* [ZITADEL: Creating Service Users](https://zitadel.com/docs/guides/manage/console/users-overview)
* [ZITADEL: Service User Authentication](https://zitadel.com/docs/guides/integrate/service-users/authenticate-service-users)
* [ZITADEL SCIM 2.0 API Endpoints](https://zitadel.com/docs/apis/scim2)
* [SCIM v2.0 (Preview) docs](https://zitadel.com/docs/guides/manage/user/scim2)
@@ -0,0 +1,136 @@
---
title: Authenticate service users and client applications
sidebar_label: Authenticate service users
---
import DocCardList from '@/components/docusaurus/doc-card-list';
This guide explains ZITADEL service users and their role in facilitating secure machine-to-machine communication within your applications.
## What are Service Users?
Service users in ZITADEL represent **non-human entities** within your system.
They are ideal for scenarios involving secure communication between applications, particularly when interacting with backend services or APIs.
Service users in combination with [Manager](/concepts/structure/managers) permissions are used to access ZITADEL's APIs, for example, to manage user resources.
Unlike regular human users, service users don't rely on traditional login methods (e.g., username/password) and require alternative authentication mechanisms.
## Benefits of using Service Users
### Enhanced security
* **Principle of Least Privilege:** Grant service users only the minimum permissions they need, minimizing potential damage in case of compromise.
* **Distinct Credentials:** Avoid embedding sensitive credentials like API keys directly in code. Service user credentials can be rotated independently.
### Segregated authorization
Manage authorization for service users separately from human users, providing an extra layer of control.
### API and backend access
Service users offer a secure way to authenticate and access various API endpoints and protected backend services.
You can [use service users to access ZITADEL APIs](../zitadel-apis/access-zitadel-apis), follow the guides to learn how to access the different ZITADEL APIs.
While you can define the scopes and required information in your requests for your applications API endpoints, when using the ZITADEL APIs, you must include the scope `urn:zitadel:iam:org:project:id:zitadel:aud` to gain access.
### Improved auditability
Actions performed by service users are clearly identifiable in logs, facilitating easier auditing and tracing.
Using the [Event API](../zitadel-apis/event-api) you can use these logs for further analysis or to integrate the logs with [external SOC / SIEM](../external-audit-log) systems.
## Authentication methods
ZITADEL supports two primary authentication methods for service users:
### Private key JWT authentication
#### How private key JWT authentication works
* Generate a private/public key pair associated with the service user.
* Sign JWTs with the private key.
* ZITADEL validates the signature using the service user's public key.
* JWTs can include expiration dates and scopes to control access.
Follow our guide on using [private key JWT client authentication](./private-key-jwt) to get started authenticating service users and clients.
#### Benefits of private key JWT authentication
* **Decentralized Verification:** No need for constant server calls, improving performance and scalability.
* **Flexibility and Control:** Define scopes and expiration within the JWT itself for granular access control.
* **Stateless:** The server doesn't need to maintain a session state, simplifying server implementation.
#### Drawbacks of private key JWT authentication
* **Complexity:** Slightly more complex to implement compared to other methods, requiring knowledge of JWT and digital signing.
* **Revocation:** Invalidating a JWT before its expiry can be challenging; blacklisting mechanisms might be required.
#### Security considerations when using private key JWT authentication
* **Secure Key Storage:** The private key used for signing must be stored with the highest level of security. Compromise could allow attackers to forge tokens.
* **Short Expirations:** Implementing short expiration durations for JWTs helps limit the impact of stolen tokens.
### Client credentials grant
* Presents a client ID and client secret associated with the service user.
* Simpler than the JWT profile in specific scenarios.
Follow our guide on using [client credentials grant](./client-credentials) to get started authenticating service users and clients.
This method is still available in ZITADEL but is generally considered less secure than JWT due to:
* **Centralized Validation:** Relies on the server to verify credentials for every request, potentially impacting performance and requiring more server resources.
* **Credentials Exposure:** Leaked client ID and secret could be used by attackers to impersonate the service user until rotation occurs.
### Personal Access Tokens (PATs)
* **Ready-to-use tokens:** Generated for specific service users and can be directly included in the authorization header of API requests.
* **Currently available only for machine users** (service users) and not regular human users.
Follow our guide on using [personal access tokens](./personal-access-token) to get started authenticating service users and clients.
PAT offer some benefits, such as:
* **Ease of Use:** Ready-to-use tokens, eliminating the need for complex signing logic.
However, PATs also come with limitations:
* **Centralized Validation:** Similar to Client Credentials, relying on the server for verification could impact performance under high load.
* **Revocation:** Requires deleting the PAT directly, potentially causing downtime if not managed carefully.
* **Leakage:** PATs are long-lived tokens that can be readily used in API calls, if leaked the attacker can access all resources until the PAT is expired or deleted. Private key JWT and client credentials create a short-lived access token instead.
## Using Service Users
1. **Creation:** Access the ZITADEL management console and create a new service user. Assign a descriptive name that reflects its purpose. Follow our detailed guide on [how to create service users](../../manage/console/users).
2. **Credentials:** Choose your preferred authentication method (JWT or Client Credentials) and securely store the generated credentials (private key, client secret).
3. **Making API Calls:** When your service needs to make an API call:
* **For JWT:** Generate and sign a JWT. Include it in the "Authorization" header of your API request.
* **For Client Credentials:** Include the client ID and client secret in your API request.
* **For PATs:** Include the PAT directly in the "Authorization" header of your API request.
4. ZITADEL Verifies the credentials and authorizes the service user to perform the requested action based on its granted permissions.
We have guides for the different authentication methods:
- [Private key JWT authentication](./private-key-jwt)
- [Client credential authentication](./client-credentials)
- [Personal access token authentication](./personal-access-token)
## Important considerations
* **Secure Credentials:** Treat service user credentials (private keys, client secrets) with utmost care. Store them securely, similar to any other sensitive information like API keys or passwords.
* **Expiry Management:** Set appropriate expiration dates for JWTs and regularly rotate all credentials to maintain strong security practices.
* **Permission Granting:** Adhere to the principle of least privilege by granting only the specific permissions required for a service user's function.
## Choosing the right authentication method
For most service user scenarios in ZITADEL, [private key JWT authentication](./private-key-jwt) is the recommended choice due to its benefits in security, performance, and control.
However, [client credentials authentication](./client-credentials) might be considered in specific situations where simplicity and trust between servers are priorities.
## Further resources
* Read about the [different methods to authenticate service users](./authenticate-service-users)
* [Service User API reference](/reference/api/user)
* [OIDC JWT with private key](/apis/openidoauth/authn-methods#jwt-with-private-key) authentication method reference
* [Access ZITADEL APIs](../zitadel-apis/access-zitadel-apis)
* Validate access tokens with [token introspection with private key jwt](../token-introspection/private-key-jwt.mdx)
<DocCardList />
@@ -0,0 +1,108 @@
---
title: Configure client credential authentication for service users
sidebar_label: Client credential authentication
---
This guide demonstrates how developers can leverage Client Credential authentication to secure communication between [service users](/guides/manage/console/users-overview#service-users-machine-accounts) and client applications within ZITADEL.
In ZITADEL, the Client Credentials Flow can be used for this [non-interactive authentication](./authenticate-service-users) as alternative to the [JWT profile authentication](./authenticate-service-users).
## Steps to authenticate a Service User with client credentials
### 1. Create a Service User with a client secret
1. Navigate to Service Users
2. Click on **New**
3. Enter a username and a display name
4. Click on **Create**
5. Open **Actions** in the top right corner and click on **Generate Client Secret**
6. Copy the **ClientID** and **ClientSecret** from the dialog
<Callout>
Make sure to copy in particular the ClientSecret. You won't be able to retrieve it again.
If you lose it, you will have to generate a new one.
</Callout>
![](../../../../public/img/console_serviceusers_secret.gif)
### 2. Authenticating a service user and request a token
In this step, we will authenticate a service user and receive an access_token to use against the ZITADEL API.
You will need to craft a POST request to ZITADEL's token endpoint:
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/oauth/v2/token \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=client_credentials \
--data scope='openid profile' \
--user "$CLIENT_ID:$CLIENT_SECRET"
```
* `CUSTOM_DOMAIN` should be set to your [Custom Domain](/concepts/features/custom-domain)
* `grant_type` should be set to `client_credentials`
* `scope` should contain any [Scopes](/apis/openidoauth/scopes) you want to include, but must include `openid`. For this example, please include `profile`
* `CLIENT_ID` and `CLIENT_SECRET` should be set with the values shown in Management Console when generating a new secret to enable [basic authentication](/apis/openidoauth/authn-methods)
If you want to access ZITADEL APIs, make sure to include the required scopes `urn:zitadel:iam:org:project:id:zitadel:aud`.
Read our guide [how to access ZITADEL APIs](../zitadel-apis/access-zitadel-apis) to learn more.
**Important Note:** If the service user token needs to be validated using token introspection, ensure you include the `urn:zitadel:iam:org:project:id:{projectid}:aud` scope in your token request.
Without this, token introspection will fail.
You should receive a successful response with `access_token`, `token_type` and time to expiry in seconds as `expires_in`.
```bash
HTTP/1.1 200 OK
Content-Type: application/json
{
"access_token": "MtjHodGy4zxKylDOhg6kW90WeEQs2q...",
"token_type": "Bearer",
"expires_in": 43199
}
```
Per default a service user will get an opaque access token.
If you want to get a Jason Web Token (JWT) as an access token for your user, you can change the token type in the general settings of your service account.
To learn more about opaque and JWT tokens read our [Opaque Tokens in ZITADEL: Enhancing Application Security](/concepts/knowledge/opaque-tokens) Guide
![](../../../../public/img/console_service_user_tokentype.png)
### 3. Include the access token in the authorization header
When making API requests on behalf of the service user, include the generated token in the "Authorization" header with the "Bearer" prefix.
```bash
curl --request POST \
--url $YOUR_API_ENDOINT \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Bearer MtjHodGy4zxKylDOhg6kW90WeEQs2q...'
```
## Accessing ZITADEL APIs
You might want to access ZITADEL APIs to manage resources, such as users, or to validate tokens sent to your backend service.
Follow our guides on [how to access ZITADEL API](../zitadel-apis/access-zitadel-apis) to use the ZITADEL APIs with your service user using client credentials.
### Token introspection
Your API endpoint might receive tokens from users and need to validate the token with ZITADEL.
In this case, your API needs to authenticate with ZITADEL and then do a token introspection.
Follow our [guide on token introspection with client credentials](../token-introspection/basic-auth) to learn more.
## Security considerations
* **Store private keys securely:** **Never share or embed the private key in your code or application.** Consider using secure key management solutions.
* **Set appropriate expiration times:** Limit the validity period of tokens to minimize the impact of potential compromise.
By following these steps and adhering to security best practices, you can effectively secure service user and client application communication within ZITADEL using client credential authentication.
## Notes
* Read about the [different methods to authenticate service users](./authenticate-service-users)
* [Service User API reference](/reference/api/user)
* [OIDC client secret basic](/apis/openidoauth/authn-methods#client-secret-basic) authentication method reference
* [Access ZITADEL APIs](../zitadel-apis/access-zitadel-apis)
* Validate access tokens with [token introspection with basic auth](../token-introspection/basic-auth)
@@ -0,0 +1,68 @@
---
title: Configure personal access token authentication for service users
sidebar_label: Personal access token authentication
---
A Personal Access Token (PAT) is a ready to use token which can be used as _Authorization_ header.
At the moment ZITADEL only allows PATs for machine accounts (service users).
It is an alternative to the [private key JWT](./private-key-jwt) and [client credentials](./client-credentials). Read more about the different [authentication methods for service users](./authenticate-service-users).
## Create a Service User with a PAT
1. Navigate to Service Users
2. Click on **New**
3. Enter a user name and a display name
4. Click on the Personal Access Token menu point in the detail of your user
5. Click on **New**
6. You can either set an expiration date or leave it empty if you don't want it to expire
7. Copy the token from the dialog (You will not see this again)
![](../../../../public/img/guides/console-service-user-pat.gif)
## Grant role for ZITADEL
To be able to access the ZITADEL APIs your service user needs permissions to ZITADEL.
1. Go to the detail page of your organization
2. Click in the top right corner the "+" button
3. Search for your service user
4. Give the user the role you need, for the example we choose Org Owner (More about [ZITADEL Permissions](/guides/manage/console/managers))
![](../../../../public/img/guides/console-service-user-org-owner.gif)
## Accessing ZITADEL APIs
You might want to access ZITADEL APIs to manage resources, such as users, or to validate tokens sent to your backend service.
Follow our guides on [how to access ZITADEL API](../zitadel-apis/access-zitadel-apis) to use the ZITADEL APIs with your service user.
### Token introspection
Your API endpoint might receive tokens from users and need to validate the token with ZITADEL.
In this case your API needs to authenticate with ZITADEL and then do a token introspection.
Follow our [guide on token introspection with private key JWT](../token-introspection/private-key-jwt) to learn more.
## Call ZITADEL API with PAT
Because the PAT is a ready to use token, you can add it as Authorization Header and send it in your requests to the ZITADEL API.
In this example we read the organization of the service user.
```bash
curl --request GET \
--url ${CUSTOM_DOMAIN}/management/v1/orgs/me \
--header 'Authorization: Bearer {PAT}'
```
## Client application authentication
The above steps demonstrate service user authentication.
If your application also needs to authenticate itself, you can utilize [Client Credentials Grant](./client-credentials).
Refer to ZITADEL documentation for details on this alternative method.
## Security considerations
* **Store private keys securely:** **Never share or embed the private key in your code or application.** Consider using secure key management solutions.
* **Set appropriate JWT expiration times:** Limit the validity period of tokens to minimize the impact of potential compromise.
* **Implement proper error handling:** Handle situations where JWT verification fails or tokens are expired.
By following these steps and adhering to security best practices, you can effectively secure service user and client application communication within ZITADEL using private key JWT authentication.
@@ -0,0 +1,237 @@
---
title: Configure private key JWT authentication for service users
sidebar_label: Private key JWT authentication
---
This guide explains how developers can use private key JWT authentication to secure communication between service users and client applications in ZITADEL.
In ZITADEL, the `urn:ietf:params:oauth:grant-type:jwt-bearer` (**"JWT bearer token with private key"**, [RFC7523](https://tools.ietf.org/html/rfc7523)) authorization grant type is used for non-interactive authentication.
Read more about the [different authentication methods for service users](./authenticate-service-users), including their benefits, drawbacks, and security considerations.
#### How private key JWT authentication works
1. Generate a private/public key pair associated with the service user.
2. The authorization server stores the public key.
3. The authorization server returns the private key as a JSON file.
4. The developer configures the client application to use the private key.
5. The client creates a JWT assertion with the subject set to the service user and signs the JWT with the private key.
6. The client application requests a token from ZITADEL by sending the `client_assertion`.
7. The authorization server validates the signature using the service user's public key.
8. The authorization server returns an OAuth `access_token`.
9. The client sends an API request, including the `access_token` in the Authorization header.
10. The resource server validates the JWT with [token introspection](../token-introspection/).
![](../../../../public/img/guides/integrate/service-users/sequence-private-key-jwt.svg)
## Prerequisites
A code library or framework that supports JWT generation and verification (such as `pyjwt` for Python or `jsonwebtoken` for Node.js).
## Steps to authenticate a Service User with private JWT
Follow these steps to authenticate a service user and obtain an access token for subsequent requests.
### 1. Create a Service User
1. Navigate to **Service Users**.
2. Click on **New**.
3. Enter a username and a display name.
4. Click on **Create**.
### 2. Register a private key for your service user
You can let ZITADEL generate a private key for you, **or** upload your own (externally generated) public key. Choose one of the following:
#### Option A: Generate and download a private key from ZITADEL
1. Open the ZITADEL web console and go to your service user.
2. Click on the **Keys** tab.
3. Click **New**.
4. Optionally set an expiration date for the key, or leave empty for no expiration.
5. Click **Download** and save the JSON key file securely.
#### Option B: Register an existing public key for your service user
If you already have a key pair, you can upload the public key for your service user. For example, to generate a key pair using OpenSSL:
```bash
# generate key pair
openssl genrsa -out privatekey.pem 2048
# extract public key
openssl rsa -in privatekey.pem -pubout -out publickey.pem
```
To upload your public key, use the [User Service Add Key API](/reference/api/user/zitadel.user.v2.UserService.AddKey)
<Callout>
If you generate a key in ZITADEL, make sure to download and securely store the key file right away. For security reasons, ZITADEL cannot show you the private key again after creation. If the key file is lost, you must generate and register a new one.
</Callout>
<Callout title="Expiration">
If you set an expiration date when generating a key in ZITADEL, that key will expire at midnight on the specified day.
</Callout>
![](../../../../public/img/console_serviceusers_new_key.gif)
If you let ZITADEL generate a key for you and download the JSON file, it will look similar to the example below. In this ZITADEL-generated key file, the `key` property contains the _private_ key for your service account. Store this JSON securely and never share your private key. ZITADEL automatically stores the public key component, so you do not need to upload it.
```json
{
"type": "serviceaccount",
"keyId": "100509901696068329",
"key": "-----BEGIN RSA PRIVATE KEY----- [...] -----END RSA PRIVATE KEY-----\n",
"userId": "100507859606888466"
}
```
### 3. Create a JWT and sign it with your private key
You need to create a JWT with the following header and payload and sign it using the RS256 algorithm.
Header:
```json
{
"alg": "RS256",
"kid": "100509901696068329"
}
```
Make sure to include the `kid` header with the value of `keyId` from the downloaded JSON.
Payload:
```json
{
"iss": "100507859606888466",
"sub": "100507859606888466",
"aud": "https://$CUSTOM-DOMAIN",
"iat": [Current UTC timestamp, e.g. 1605179982, must be no older than 1 hour],
"exp": [Expiration UTC timestamp, e.g. 1605183582]
}
```
* `iss`: The entity making the request, i.e., the owner of the private key. Use the `userId` from your downloaded JSON.
* `sub`: The application. Set this to the same value as `userId`.
* `aud`: Your [Custom Domain](../../../concepts/features/custom-domain).
* `iat`: The Unix timestamp when the JWT is created/signed (must be no older than 1 hour).
* `exp`: The Unix timestamp when this assertion expires.
For further details, see the [JWT with private key](/apis/openidoauth/authn-methods#jwt-with-private-key) API reference.
If you use Go, you can use the [provided tool](https://github.com/zitadel/zitadel-tools) to generate a JWT from the downloaded JSON.
There are also [many libraries](https://jwt.io/#libraries-io) available for generating and signing JWTs.
**Code Example (Python using `pyjwt`):**
```python
import jwt
import datetime
# Replace with your service user ID and private key
service_user_id = "your_service_user_id"
private_key = "-----BEGIN PRIVATE KEY-----\nYOUR_PRIVATE_KEY\n-----END PRIVATE KEY-----"
key_id = "your_key_id"
# ZITADEL API URL (replace if needed)
api_url = "your_custom_domain"
# Generate JWT claims
payload = {
"iss": service_user_id,
"sub": service_user_id,
"aud": api_url,
"exp": datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=5),
"iat": datetime.datetime.now(datetime.timezone.utc)
}
header = {
"alg": "RS256",
"kid": key_id
}
# Sign the JWT using the RS256 algorithm
encoded_jwt = jwt.encode(payload, private_key, algorithm="RS256", headers=header)
print(f"Generated JWT: {encoded_jwt}")
```
### 4. Request an OAuth token with the generated JWT
With the encoded JWT from the previous step, craft a POST request to ZITADEL's token endpoint:
```bash
curl --request POST \
--url https:/$CUSTOM-DOMAIN/oauth/v2/token \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \
--data scope='openid' \
--data assertion=eyJ0eXAiOiJKV1QiL...
```
* `grant_type`: Must be set to `urn:ietf:params:oauth:grant-type:jwt-bearer`
* `scope`: Include any desired [Scopes](/apis/openidoauth/scopes), but must include `openid`.
* `assertion`: The encoded JWT you created and signed with your private key.
If you want to access ZITADEL APIs, make sure to include the required scope `urn:zitadel:iam:org:project:id:zitadel:aud`.
See our guide on [how to access ZITADEL APIs](../zitadel-apis/access-zitadel-apis) for more details.
**Important Note:** If the service user token must be validated using token introspection, you must include the `urn:zitadel:iam:org:project:id:{projectid}:aud` scope in your token request.
If you do not include this, token introspection will fail.
A successful response will include the `access_token`, `token_type`, and the time to expiry in seconds (`expires_in`):
```bash
HTTP/1.1 200 OK
Content-Type: application/json
{
"access_token": "MtjHodGy4zxKylDOhg6kW90WeEQs2q...",
"token_type": "Bearer",
"expires_in": 43199
}
```
### 5. Include the access token in the Authorization header
When making API requests as the service user, include the generated token in the "Authorization" header with the "Bearer" prefix.
```bash
curl --request POST \
--url $YOUR_API_ENDPOINT \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Bearer MtjHodGy4zxKylDOhg6kW90WeEQs2q...'
```
## Accessing ZITADEL APIs
You may use ZITADEL APIs to manage resources (e.g., users) or to validate tokens received by your backend service.
Follow our guide on [how to access ZITADEL API](../zitadel-apis/access-zitadel-apis) with your service user.
### Token introspection
If your API endpoint receives tokens from users and needs to validate them with ZITADEL,
your API must first authenticate with ZITADEL, then perform a token introspection.
See our [guide on token introspection with private key JWT](../token-introspection/private-key-jwt) for details.
## Client application authentication
The steps above describe service user authentication.
If your application itself also needs authentication, you can use the [Client Credentials Grant](./client-credentials).
See the ZITADEL documentation for additional details on this approach.
## Security considerations
* **Store private keys securely:** **Never share or embed the private key in your code or application.** Use secure key management solutions whenever possible.
* **Set appropriate JWT expiration times:** Limit token validity to reduce risk in the event of a compromise.
* **Implement proper error handling:** Handle cases where JWT verification fails or tokens are expired.
By following these steps and adhering to best practices, you can securely manage service user and client application authentication within ZITADEL using private key JWTs.
## Notes
* [JWT with private key](/apis/openidoauth/authn-methods#jwt-with-private-key) API reference
* [Accessing ZITADEL API](../zitadel-apis/access-zitadel-apis)
* [Token introspection with private key JWT](../token-introspection/private-key-jwt)

Some files were not shown because too many files have changed in this diff Show More