# ZITADEL Documentation
**ZITADEL is the Identity Infrastructure for Developers.**
We provide a hardened, extensible turnkey solution for all your authentication and authorization needs. Instead of building your own login system, use ZITADEL to accelerate your project with features that work out of the box:
* **Secure Login**: Multi-factor authentication (OTP, U2F, Passkeys) and single sign-on (OIDC, SAML, OAuth2).
* **B2B Ready**: First-class multi-tenancy with branding customization and self-service.
* **Auditable**: Exhaustive audit trail of all events.
* **Extensible**: Execute custom code on events (Actions) to fit your unique workflows.
Get Started [#get-started]
Explore our guides to get up and running with ZITADEL quickly.
Deployment Options [#deployment-options]
You can use ZITADEL in two ways:
* **ZITADEL Cloud**: This is our public cloud service. Use the free tier to start in minutes.
* **Self-hosted ZITADEL**: For full control, deploy ZITADEL wherever you prefer.
If you're unsure, consider the generous free tier of [ZITADEL Cloud](/guides/manage/cloud/start).
Choose if you want:
* A turnkey solution that's ready to go
* Global scalability without the hassle
* Data-residency compliance
Choose if you want:
* Total control over all components
* Air-gapped or regulated environments
* Flexibility when deploying updates
Explore Components [#explore-components]
Deep dive into the architecture and capabilities of ZITADEL.
Community & Support [#community-support]
Join the community and get help.
# ZITADEL API Reference Overview
ZITADEL exposes all features via different gRPC and REST APIs and provides SDKs for popular languages and frameworks.
The [OpenID Connect & OAuth endpoints](/apis/openidoauth/endpoints) and [SAML 2.0 endpoints](/apis/saml/endpoints) are implemented and exposed according to the specific standards. Managing resources such as users, organizations, instances, or settings must be done with the different [ZITADEL APIs](#zitadel-apis).
[Actions](/guides/manage/console/actions-overview) allow extending ZITADEL with custom code to change default behaviors or calling external systems.
Authentication & Authorization [#authentication-authorization]
Authentication for users (interactive) [#authentication-for-users-interactive]
ZITADEL implements industry standards such as OpenID Connect, OAuth 2.0, or SAML for authentication.
Please refer to our guides on how to [authenticate users](/guides/integrate/login/login-users) through an interactive authentication process.
For user authentication on devices with limited accessibility (eg, SmartTV, Smartwatch, etc.) use the [device authorization grant](/guides/integrate/login/oidc/device-authorization).
Additionally, you can use the [session API](/reference/api/session) to authenticate users, for example, by building a [custom login UI](/guides/integrate/login-ui).
Authenticate service accounts [#authenticate-service-accounts]
Service accounts allow for machine-to-machine (M2M) communication.
Follow the guides to learn how to [authenticate service accounts](/guides/integrate/service-accounts/authenticate-service-accounts).
Accessing the ZITADEL APIs through a service account might require additional steps, please follow the guide on how to [access ZITADEL APIs](/guides/integrate/zitadel-apis/access-zitadel-apis) to include the correct audience scope in your requests.
OpenID Connect & OAuth [#open-id-connect-o-auth]
* [OpenID Connect endpoints](/apis/openidoauth/endpoints) definition
* Standard and reserved [scopes reference](/apis/openidoauth/scopes)
* Standard, custom, and reserved [claims reference](/apis/openidoauth/claims)
The [OIDC Playground](https://zitadel.com/playgrounds/oidc) is for testing OpenID authentication requests and their parameters.
SAML 2.0 [#saml-2-0]
* [SAML 2.0 endpoints](/apis/saml/endpoints) definition
* [Custom attributes](https://github.com/zitadel/actions/blob/main/examples/set_custom_attribute.js) can be added with an action
Session-based and custom login [#session-based-and-custom-login]
For building a custom login flow, you have access to purpose-built v2 APIs:
* [Create or update a session](/reference/api/session/zitadel.session.v2.SessionService.CreateSession) to authenticate users step by step
* [Get OIDC auth request details](/reference/api/oidc/zitadel.oidc.v2.OIDCService.GetAuthRequest) when acting as an OIDC provider
* [Get SAML request details](/reference/api/saml/zitadel.saml.v2.SAMLService.GetSAMLRequest) when acting as a SAML provider
User role assignments can be [retrieved as roles from our APIs](/guides/integrate/retrieve-user-roles).
Refer to our guide to learn how to [build your own login UI](/guides/integrate/login-ui)
ZITADEL APIs [#zitadel-ap-is]
**Use the [v2 APIs](/apis/v2) for all new integrations.** They are the recommended, resource-based API surface for ZITADEL and where all new development is happening.
The v1 APIs are legacy: fully supported and still required in some places, but not being extended.
If the resource you need is not yet available in v2, use the corresponding legacy v1 API for that operation and check the [migration guide](/apis/migration_v1_to_v2) to plan your transition.
APIs v2 (resource-based) [#ap-is-v-2-resource-based]
ZITADEL provides v2 APIs for the following resources:
* [User](/reference/api/user) — manage users and their authentication methods
* [Session](/reference/api/session) — create and manage user sessions
* [Organization](/reference/api/org) — manage organizations
* [Instance](/reference/api/instance) — manage your ZITADEL instance
* [Project](/reference/api/project) — manage projects and granted projects
* [Application](/reference/api/application) — manage OIDC, SAML, and API applications
* [IDP](/reference/api/idp) — manage identity providers
* [Group](/reference/api/group) — manage user groups
* [Settings](/reference/api/settings) — read login and instance settings
* [Feature](/reference/api/feature) — manage feature flags
* [Authorization](/reference/api/authorization) — check and manage user permissions
* [Action](/reference/api/action) — manage execution targets and action flows
* [WebKey](/reference/api/webkey) — manage signing keys
* [OIDC](/reference/api/oidc) — handle OIDC authorization flows (used when building a custom login UI)
* [SAML](/reference/api/saml) — handle SAML authorization flows (used when building a custom login UI)
Not finding a resource here? It has not yet been migrated to v2 — use the legacy v1 APIs below for that operation.
Legacy v1 APIs [#legacy-v-1-ap-is]
The following APIs predate the v2 resource-based design. They are still fully supported and required for capabilities not yet covered by v2, but they are no longer being extended. If you are building something new, check the v2 section above first.
Looking to move an existing integration off v1? See the [v1 to v2 migration guide](/apis/migration_v1_to_v2).
Authentication [#authentication]
The Auth API covers operations on the currently authenticated user. The user is identified from the `sub` claim in the access token.
For new integrations, prefer the [User v2 API](/reference/api/user) and [Session v2 API](/reference/api/session) which provide the same capabilities in a resource-based design.
Management [#management]
The Management API lets systems read and mutate resources within an organization — users, projects, applications, roles, and more. The target organization is determined by the `x-zitadel-orgid` request header, or falls back to the organization of the authenticated user.
For new integrations, prefer the [User v2 API](/reference/api/user) for user management, which handles both instance-level and organization-level access automatically based on caller permissions.
System [#system]
This API is intended to manage the different ZITADEL instances within the system.
Checkout the guide how to [access the ZITADEL System API](/guides/integrate/zitadel-apis/access-zitadel-system-api).
System GRPC [#system-grpc]
Endpoint:
`${CUSTOM_DOMAIN}/zitadel.system.v1.SystemService/`
Definition:
[System Proto](https://github.com/zitadel/zitadel/blob/main/proto/zitadel/system.proto)
System REST [#system-rest]
Endpoint:
`${CUSTOM_DOMAIN}/system/v1/`
API Reference:
[OpenAPI Docs](/reference/api/system)
Assets [#assets]
The Assets API allows you to up- and download all kinds of assets. This can be files such as logos, fonts or user avatar.
Client libraries & schemas [#client-libraries-schemas]
ZITADEL publishes [official and community-supported SDKs](/sdk-examples/introduction) for multiple languages and frameworks.
All API services are defined as [proto definitions on GitHub](https://github.com/zitadel/zitadel/tree/main/proto/zitadel) — most languages let you generate a type-safe client directly from them.
Domains [#domains]
ZITADEL hosts everything under a single domain: `{instance}.zitadel.cloud` or your Custom Domain `${CUSTOM_DOMAIN}`
The domain is used as the OIDC issuer and as the base URL for the gRPC and REST APIs, the Management Console (`${CUSTOM_DOMAIN}/ui/console/`), and the hosted Login UI (`${CUSTOM_DOMAIN}/ui/v2/login`).
Are you self-hosting and having troubles with *Instance not found* errors? [Check out this page](/self-hosting/manage/custom-domain).
API path prefixes [#api-path-prefixes]
If you run ZITADEL on a Custom Domain, you may want to reuse that domain for other applications.
For easy copying to your reverse proxy configuration, here is the list of URL path prefixes, ZITADEL uses.
```
# v2 APIs (recommended for new integrations)
# REST (HTTP/JSON transcoding):
/v2/
# gRPC + Connect protocol (binary or JSON via connectRPC):
/zitadel.action.v2.ActionService/
/zitadel.application.v2.ApplicationService/
/zitadel.authorization.v2.AuthorizationService/
/zitadel.feature.v2.FeatureService/
/zitadel.group.v2.GroupService/
/zitadel.idp.v2.IdentityProviderService/
/zitadel.instance.v2.InstanceService/
/zitadel.oidc.v2.OIDCService/
/zitadel.org.v2.OrganizationService/
/zitadel.project.v2.ProjectService/
/zitadel.saml.v2.SAMLService/
/zitadel.session.v2.SessionService/
/zitadel.settings.v2.SettingsService/
/zitadel.user.v2.UserService/
/zitadel.webkey.v2.WebKeyService/
# v2beta — kept for backward compatibility, use /v2/ for new work
/v2beta/
# Legacy v1 APIs
/zitadel.admin.v1.AdminService/
/admin/v1/
/zitadel.auth.v1.AuthService/
/auth/v1/
/zitadel.management.v1.ManagementService/
/management/v1/
/zitadel.system.v1.SystemService/
/system/v1/
/assets/v1/
# OpenID Connect, OAuth 2.0, SAML 2.0 & standards
/oidc/v1/
/saml/v2/
/oauth/v2/
/device
/.well-known/openid-configuration
/openapi/
/idps/callback
# UIs
/ui/console/ # Management Console (Angular admin UI)
/ui/v2/login # Login UI v2 (Next.js; routes to a separate container in self-hosted deployments)
/ui/login/ # Login UI v1 (legacy, built into the ZITADEL binary)
```
Postman Collection (Beta) [#postman-collection-beta]
We published an official Postman collection to help you explore and test the ZITADEL APIs.
The collection is organized by services and includes a script to automatically authenticate requests. Before using it, make sure to configure the required environment variables in Postman:
| Variable Name | Description |
| ----------------------- | ------------------------------------------------------------------------------- |
| `protocol` | **http** for local testing, or **https** for cloud/self hosted instances |
| `custom_domain` | The domain of your Zitadel instance. |
| `service_client_id` | Client ID of service account with **IAM\_OWNER** role in your Zitadel instance. |
| `service_client_secret` | Client secret for the service account. |
| `api_client_id` | Client ID of an API application. |
| `api_client_secret` | Client secret for the API application. |
| `project_id` | A project ID within your Zitadel instance. |
[Fork the Postman Collection](https://app.getpostman.com/run-collection/44392838-6417c238-b9b9-4334-bef0-233ae38cbc51?action=collection%2Ffork\&source=rip_markdown\&collection-url=entityId%3D44392838-6417c238-b9b9-4334-bef0-233ae38cbc51%26entityType%3Dcollection%26workspaceId%3D54ef8cdd-5ea3-4f31-89f6-20720b45528c)
This release is currently in **beta**, and we welcome your feedback to improve it.
# Migrate from v1 APIs to v2 APIs
This guide helps you migrate from our v1 APIs to v2 APIs.
Use v2 where the required resource is available, and keep using v1 for capabilities that are not yet migrated.
The sections below explain the architectural changes and the concrete migration considerations.
The v1 Approach: Use-Case Based APIs [#the-v-1-approach-use-case-based-ap-is]
Our v1 API was structured around use cases, meaning we provided distinct APIs tailored to different user roles:
* Auth API: For authenticated users.
* Management API: For administrators of an organization.
* Admin API: For administrators of an instance.
* System API: For managing multiple instances.
While this approach served its initial purpose, it presented a few challenges.
Developers often found it difficult to determine which specific API endpoint to use for their needs.
Additionally, this model sometimes led to redundant implementations of similar functionalities across different APIs – for example, listing users might have existed in slightly different forms for an instance context versus an organization context.
This often required more extensive reading of documentation and further explanation to ensure correct usage.
The v2 Approach: A Resource-Based API [#the-v-2-approach-a-resource-based-api]
With our v2 API, we introduce a resource-based architecture.
This means instead of organizing by user type, we now structure our API around logical resources, such as:
* Users API
* Instance API
* Organization API
* And more...
A key improvement in v2 is how context and permissions are handled.
The data you receive from an endpoint will now automatically be scoped based on the role and permissions of the authenticated user.
For example:
* An instance administrator calling a GET /users endpoint will receive a list of all users within that instance.
* An organization administrator calling the exact same GET /users endpoint will receive a list of users belonging only to their specific organization.
Why the Change [#why-the-change]
The primary goals behind this architectural shift are to make our API:
* More Intuitive: Finding the right endpoint should feel natural. If you want to interact with users, you look at the Users API.
* Self-Explanatory: The structure itself guides you, reducing the need to sift through extensive documentation to understand which API "hat" you need to wear.
* Developer-Friendly: A cleaner, more consistent API surface means faster integration and less room for confusion.
We're confident that these changes will significantly enhance your experience working with our platform.
The following sections will detail the specific resources that have been migrated and outline any changes you'll need to be aware of.
Resource Migration [#resource-migration]
This section details the migrated resources, including any breaking changes and other important considerations for your transition from v1 to v2.
General Changes [#general-changes]
Sunsetting OpenAPI/REST Support in Favor of Connect RPC [#sunsetting-open-api-rest-support-in-favor-of-connect-rpc]
While our v1 API already offered gRPC, it also provided a parallel REST/OpenAPI interface for applications who preferred making traditional HTTP calls.
In our v2 API, we are consolidating our efforts to provide a more streamlined and efficient development experience.
The primary change is the removal of the OpenAPI/REST interface.
We will now exclusively support interaction with our gRPC services directly or through [Connect RPC](https://connectrpc.com/).
Connect RPC is being introduced as the new, official way to interact with our gRPC services using familiar, plain HTTP/1.1.
It effectively replaces the previous REST gateway.
For teams already using gRPC, your transition will be minimal.
For teams who were using the v1 REST API, migrating to v2 will involve adopting one of the following methods:
* Native gRPC: For the highest performance and to leverage features like bidirectional streaming.
* Connect RPC: For making CRUD-like (Create, Read, Update, Delete) calls over HTTP. This is the recommended path for most applications migrating from our v1 REST API.
A significant advantage of this new architecture remains the automatic generation of client libraries.
Based on our .proto service definitions, you can generate type-safe applications for your specific programming language, whether you use native gRPC or Connect RPC.
This eliminates the need to write boilerplate code for handling HTTP requests and parsing responses, leading to a more streamlined and less error-prone development process.
Contextual Information in the Request Body [#contextual-information-in-the-request-body]
A key change in v2 is that contextual data, like organization\_id, must now be sent in the request body.
Previously, this was sent in the request headers.
**v1 (Header)**
```
x-zitadel-orgid: 1234567890
```
**v2 (Request Body)**
```
{
"organization_id": "1234567890"
}
```
Instances [#instances]
No major changes have been made to the organization requests.
Organizations [#organizations]
No major changes have been made to the organization requests.
Users [#users]
When migrating your user management from v1 to v2, the most significant updates involve user states and the initial onboarding process:
* **Unified User Creation Endpoint**:
* A significant simplification in v2 is the consolidation of user creation. There is now one primary endpoint for creating users, regardless of whether they are User (Human) or service accounts.
* You can use this single endpoint to provision both users (individuals interacting with your application) and service accounts (i.e. API clients), typically by specifying the user type in the request payload.
* **No More "Initial" State**:
* In v1, new users without a password or verified email were automatically assigned an initial state. This default assumption wasn't always ideal.
* In v2, this initial state has been removed. All newly created users are now active by default, regardless of their initial attributes.
* **New Onboarding Process**:
* To enable users to set up their accounts, you can now send them an invitation code. This allows them to securely add their authentication methods.
* **Flexible Email Verification**:
* v2 provides more control over email verification:
* You can choose at user creation whether an email verification code should be sent automatically.
* Alternatively, the API can return the verification code directly to you, empowering you to send a customized verification email.
[Users API v2 Documentation](/reference/api/user)
Projects [#projects]
We've simplified how you interact with projects by unifying projects and granted\_projects into a single resource.
From a consumer's perspective, it no longer matters if you own a project or if it was granted to you by another organization; it's all just a project.
The main difference now is your level of permission.
Your permissions determine whether you have administrative rights (like updating the project's details) or if you can only view the project and manage authorizations for your users.
This change significantly streamlines API calls.
For example, where you previously had to make two separate requests to see all projects, you now make one.
**v1 (Separate Requests):**
```
- ListProjects
- ListGrantedProjects
```
**v2 (Single Request with Filter):**
```
- ListProjects (returns all projects you have access to)
```
You can now use filters within the single ListProjects request if you need to differentiate between project types, such as filtering by projects you own versus those that have been granted to you.
Update your code to use this new unified ListProjects endpoint.
Applications [#applications]
We have streamlined the creation and management of applications.
In v1, each application type had its own unique endpoints.
In v2, we have unified these into a single set of endpoints for all application types.
The biggest change is in how you create/update applications.
Instead of calling a specific endpoint for each type (e.g., CreateOidcApp, CreateSamlApp), you will now use a single CreateApp endpoint.
To specify the type of application, you will include its specific settings object within the request body.
For example, to create a OIDC app, you will provide an oidc object in the request.
All properties that are common to every application, such as name, are now top-level fields in the request body, consistent across all types.
This approach simplifies client-side logic, as you no longer need to route requests to different endpoints.
**v1 (Multiple, Type-Specific Endpoints):**
```
- AddOIDCApp
- AddSAMLApp
- AddAPIApp
```
**v2 (Single Endpoint with Type-Specific Body):**
```
- CreateApplication
- ProjectID
- Name
- Type
- OIDC
- SAML
- API
```
# SCIM v2.0 (Preview)
The Zitadel [SCIM v2](https://scim.cloud/) service provider interface enables integration of identity and
access management (instance) systems with Zitadel,
following the System for Cross-domain Identity Management (SCIM) v2.0 specification.
This interface allows standardized management of instance resources, making it easier to automate user provisioning and
deprovisioning.
Supported endpoints [#supported-endpoints]
The Zitadel SCIM v2.0 service provider implementation supports the following endpoints.
The base URL for the SCIM endpoint in Zitadel is: `https://${CUSTOM_DOMAIN}/scim/v2/{orgId}`.
| Endpoint | Remarks |
| ----------------------------------------------------------------------- | ----------------------------------------------------- |
| `GET /scim/v2/{orgId}/ServiceProviderConfig` | Retrieve the settings of the Zitadel service provider |
| `GET /scim/v2/{orgId}/Schemas` | Retrieve all supported schemas |
| `GET /scim/v2/{orgId}/Schemas/{id}` | Retrieve a known supported schema |
| `GET /scim/v2/{orgId}/ResourceTypes` | Retrieve all supported resource types |
| `GET /scim/v2/{orgId}/ResourceTypes/{name}` | Retrieve a known supported resource type |
| `GET /scim/v2/{orgId}/Users/{id}` | Retrieve a known user |
| `GET /scim/v2/{orgId}/Users` `POST /scim/v2/{orgId}/Users/.search` | Query users (including filtering, sorting, paging) |
| `POST /scim/v2/{orgId}/Users` | Create a user |
| `PUT /scim/v2/{orgId}/Users/{id}` | Replace a user |
| `PATCH /scim/v2/{orgId}/Users/{id}` | Modify a user |
| `DELETE /scim/v2/{orgId}/Users/{id}` | Delete a user |
| `POST /scim/v2/{orgId}/Bulk` | Apply multiple operations in a single request |
Authentication [#authentication]
The SCIM interface adheres to Zitadel's standard API authentication methods.
For detailed instructions on authenticating with the SCIM interface, refer to the [Authenticate Service Accounts Guide](/guides/integrate/service-accounts/authenticate-service-accounts).
Query [#query]
The list users endpoint supports sorting and filtering for both `GET /scim/v2/{orgId}/Users` and `POST /scim/v2/{orgId}/Users/.search` requests.
By default, the response includes up to 100 users, with a maximum allowable value for `count` set to 100.
Sort [#sort]
The following attributes are supported in the `SortBy` attribute.
* `meta.created`
* `meta.lastModified`
* `id`
* `username`
* `name.familyName`
* `name.givenName`
* `emails` and `emails.value`
Filter [#filter]
The following filter attributes and operators are supported:
| Attribute | Supported operators |
| ---------------------------- | ---------------------------- |
| `meta.created` | `EQ`, `GT`, `GE`, `LT`, `LE` |
| `meta.lastModified` | `EQ`, `GT`, `GE`, `LT`, `LE` |
| `id` | `EQ`, `NE`, `CO`, `SW`, `EW` |
| `externalId` | `EQ`, `NE` |
| `username` | `EQ`, `NE`, `CO`, `SW`, `EW` |
| `name.familyName` | `EQ`, `NE`, `CO`, `SW`, `EW` |
| `name.givenName` | `EQ`, `NE`, `CO`, `SW`, `EW` |
| `emails` `emails.value` | `EQ`, `NE`, `CO`, `SW`, `EW` |
| `active` | `EQ`, `NE` |
Filters can have a maximum length of 1000 characters.
Examples [#examples]
Here are practical examples demonstrating how to interact with the SCIM API,
providing clear guidance on common use cases such as creating a user.
Make sure to replace any placeholder values (`${}`) with the actual values from your environment.
`POST /Users`
: Create a minimal user
```bash
curl -X POST "https://${DOMAIN}/scim/v2/${ORG_ID}/Users" \
-H 'Content-Type: application/scim+json' \
-H 'Accept: application/scim+json' \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
--data-raw '
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "john.doe",
"name": {
"familyName": "Doe",
"givenName": "John"
},
"password": "Password1!",
"emails": [
{
"value": "john.doe@example.com",
"primary": true
}
]
}
'
```
**Response (`201 Created`)**
```json
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"meta": {
"resourceType": "User",
"created": "2025-01-27T15:30:27.651321Z",
"lastModified": "2025-01-27T15:30:27.651321Z",
"version": "2",
"location": "https://${DOMAIN}/scim/v2/${ORG_ID}/Users/304499468865155777"
},
"id": "304499468865155777",
"userName": "john.doe",
"name": {
"familyName": "Doe",
"givenName": "John"
},
"preferredLanguage": "en",
"emails": [
{
"value": "john.doe@example.com",
"primary": true
}
]
}
```
`POST /Users`
: Create a full user
```bash
curl -X POST "https://${DOMAIN}/scim/v2/${ORG_ID}/Users" \
-H 'Content-Type: application/scim+json' \
-H 'Accept: application/scim+json' \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
--data-raw '
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"externalId": "8d4b51c0-51bd-4386-ae17-79ce5fd36517",
"userName": "john.doe@example.com",
"name": {
"formatted": "Mr. John J Doe, III",
"familyName": "Doe",
"givenName": "John",
"middleName": "Jim",
"honorificPrefix": "Mr.",
"honorificSuffix": "III"
},
"displayName": "John Doe",
"nickName": "Johnny",
"profileUrl": "https://login.example.com/john.doe",
"emails": [
{
"value": "john.doe@example.com",
"type": "work",
"primary": true
}
],
"addresses": [
{
"type": "work",
"streetAddress": "100 Universal City Plaza",
"locality": "Hollywood",
"region": "CA",
"postalCode": "91608",
"country": "USA",
"formatted": "100 Universal City Plaza\nHollywood, CA 91608 USA",
"primary": true
}
],
"phoneNumbers": [
{
"value": "+1 555-555-5555",
"type": "work",
"primary": true
}
],
"ims": [
{
"value": "@j.doe",
"type": "X"
}
],
"photos": [
{
"value": "https://photos.example.com/profilephoto/john.doe/F",
"type": "photo"
}
],
"roles": [
{
"value": "user-admin",
"display": "User administrator"
}
],
"entitlements": [
{
"value": "read-passports",
"display": "Read Passports"
}
],
"userType": "Employee",
"title": "Tour Guide",
"preferredLanguage": "en-US",
"locale": "en-US",
"timezone": "America/Los_Angeles",
"active": true,
"password": "Password1!"
}'
```
**Response (`201 Created`)**
```json
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"meta": {
"resourceType": "User",
"created": "2025-01-27T15:31:47.84572Z",
"lastModified": "2025-01-27T15:31:47.84572Z",
"version": "16",
"location": "https://localhost:8080/scim/v2/303879575732073153/Users/304499603368096449"
},
"id": "304499603368096449",
"externalId": "8d4b51c0-51bd-4386-ae17-79ce5fd36517",
"userName": "john.doe@example.com",
"name": {
"formatted": "John Doe",
"familyName": "Doe",
"givenName": "John",
"middleName": "Jim",
"honorificPrefix": "Mr.",
"honorificSuffix": "III"
},
"displayName": "John Doe",
"nickName": "Johnny",
"profileUrl": "https://login.example.com/john.doe",
"title": "Tour Guide",
"preferredLanguage": "en-US",
"locale": "en-US",
"timezone": "America/Los_Angeles",
"active": true,
"emails": [
{
"value": "john.doe@example.com",
"primary": true
}
],
"phoneNumbers": [
{
"value": "+15555555555",
"primary": true
}
],
"ims": [
{
"value": "@j.doe",
"type": "X"
}
],
"addresses": [
{
"type": "work",
"streetAddress": "100 Universal City Plaza",
"locality": "Hollywood",
"region": "CA",
"postalCode": "91608",
"country": "USA",
"formatted": "100 Universal City Plaza\nHollywood, CA 91608 USA",
"primary": true
}
],
"photos": [
{
"value": "https://photos.example.com/profilephoto/john.doe/F",
"type": "photo"
}
],
"entitlements": [
{
"value": "read-passports",
"display": "Read Passports"
}
],
"roles": [
{
"value": "user-admin",
"display": "User administrator"
}
]
}
```
`GET /Users/{id}`
: Retrieve a known user
```bash
curl -G "https://${DOMAIN}/scim/v2/${ORG_ID}/Users/${USER_ID}" \
-H 'Accept: application/scim+json' \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
```
**Response (`200 OK`)**
```json
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"meta": {
"resourceType": "User",
"created": "2025-01-27T15:31:47.84572Z",
"lastModified": "2025-01-27T15:31:47.84572Z",
"version": "16",
"location": "https://localhost:8080/scim/v2/303879575732073153/Users/304499603368096449"
},
"id": "304499603368096449",
"externalId": "8d4b51c0-51bd-4386-ae17-79ce5fd36517",
"userName": "john.doe@example.com",
"name": {
"formatted": "John Doe",
"familyName": "Doe",
"givenName": "John",
"middleName": "Jim",
"honorificPrefix": "Mr.",
"honorificSuffix": "III"
},
"displayName": "John Doe",
"nickName": "Johnny",
"profileUrl": "https://login.example.com/john.doe",
"title": "Tour Guide",
"preferredLanguage": "en-US",
"locale": "en-US",
"timezone": "America/Los_Angeles",
"active": true,
"emails": [
{
"value": "john.doe@example.com",
"primary": true
}
],
"phoneNumbers": [
{
"value": "+15555555555",
"primary": true
}
],
"ims": [
{
"value": "@j.doe",
"type": "X"
}
],
"addresses": [
{
"type": "work",
"streetAddress": "100 Universal City Plaza",
"locality": "Hollywood",
"region": "CA",
"postalCode": "91608",
"country": "USA",
"formatted": "100 Universal City Plaza\nHollywood, CA 91608 USA",
"primary": true
}
],
"photos": [
{
"value": "https://photos.example.com/profilephoto/john.doe/F",
"type": "photo"
}
],
"entitlements": [
{
"value": "read-passports",
"display": "Read Passports"
}
],
"roles": [
{
"value": "user-admin",
"display": "User administrator"
}
]
}
```
`GET /Users`
: List users created after a given date sorted by the creation date
```bash
curl -G "https://${DOMAIN}/scim/v2/${ORG_ID}/Users" \
-H 'Accept: application/scim+json' \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
--data-urlencode "sortBy=meta.created" \
--data-urlencode "sortOrder=descending" \
--data-urlencode "filter=meta.created gt \"2025-01-24T09:22:35.695245Z\""
```
**Response (`200 OK`)**
```json
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
"itemsPerPage": 100,
"totalResults": 1,
"startIndex": 1,
"Resources": [
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"meta": {
"resourceType": "User",
"created": "2025-01-27T15:31:47.84572Z",
"lastModified": "2025-01-27T15:31:47.84572Z",
"version": "3",
"location": "https://localhost:8080/scim/v2/303879575732073153/Users/304499603368096449"
},
"id": "304499603368096449",
"externalId": "8d4b51c0-51bd-4386-ae17-79ce5fd36517",
"userName": "john.doe@example.com",
"name": {
"formatted": "John Doe",
"familyName": "Doe",
"givenName": "John",
"middleName": "Jim",
"honorificPrefix": "Mr.",
"honorificSuffix": "III"
},
"displayName": "John Doe",
"nickName": "Johnny",
"profileUrl": "https://login.example.com/john.doe",
"title": "Tour Guide",
"preferredLanguage": "und",
"locale": "en-US",
"timezone": "America/Los_Angeles",
"active": true,
"emails": [
{
"value": "john.doe@example.com",
"primary": true
}
],
"phoneNumbers": [
{
"value": "+15555555555",
"primary": true
}
],
"ims": [
{
"value": "@j.doe",
"type": "X"
}
],
"addresses": [
{
"type": "work",
"streetAddress": "100 Universal City Plaza",
"locality": "Hollywood",
"region": "CA",
"postalCode": "91608",
"country": "USA",
"formatted": "100 Universal City Plaza\nHollywood, CA 91608 USA",
"primary": true
}
],
"photos": [
{
"value": "https://photos.example.com/profilephoto/john.doe/F",
"type": "photo"
}
],
"entitlements": [
{
"value": "read-passports",
"display": "Read Passports"
}
],
"roles": [
{
"value": "user-admin",
"display": "User administrator"
}
]
}
]
}
```
`PATCH /Users/{id}`
: Set a user inactive
```bash
curl -X PATCH "https://${DOMAIN}/scim/v2/${ORG_ID}/Users/${USER_ID}" \
-H 'Content-Type: application/scim+json' \
-H 'Accept: application/scim+json' \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
--data-raw '
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "replace",
"path": "active",
"value": false
}
]
}
'
```
**Response**: `204 No Content`
`PATCH /Users/{id}`
: Set the password of a user
```bash
curl -X PATCH "https://${DOMAIN}/scim/v2/${ORG_ID}/Users/${USER_ID}" \
-H 'Content-Type: application/scim+json' \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
--data-raw '
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "replace",
"path": "password",
"value": "Password2!"
}
]
}
'
```
**Response**: `204 No Content`
`PUT /Users/{id}`
: Replace a full user
```bash
curl -X PUT "https://${DOMAIN}/scim/v2/${ORG_ID}/Users/${USER_ID}" \
-H 'Content-Type: application/scim+json' \
-H 'Accept: application/scim+json' \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
--data-raw '
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"externalId": "8d4b51c0-51bd-4386-ae17-79ce5fd36517",
"userName": "john.doe@example.com",
"name": {
"formatted": "Mr. John J Doe, III",
"familyName": "Doe",
"givenName": "John",
"middleName": "Jim",
"honorificPrefix": "Mr.",
"honorificSuffix": "III"
},
"displayName": "John Doe",
"nickName": "Johnny",
"profileUrl": "https://login.example.com/john.doe",
"emails": [
{
"value": "john.doe@example.com",
"type": "work",
"primary": true
}
],
"addresses": [
{
"type": "work",
"streetAddress": "100 Universal City Plaza",
"locality": "Hollywood",
"region": "CA",
"postalCode": "91608",
"country": "USA",
"formatted": "100 Universal City Plaza\nHollywood, CA 91608 USA",
"primary": true
}
],
"phoneNumbers": [
{
"value": "+1 555-555-5555",
"type": "work",
"primary": true
}
],
"ims": [
{
"value": "@j.doe",
"type": "X"
}
],
"photos": [
{
"value": "https://photos.example.com/profilephoto/john.doe/F",
"type": "photo"
}
],
"roles": [
{
"value": "user-admin",
"display": "User administrator"
}
],
"entitlements": [
{
"value": "read-passports",
"display": "Read Passports"
}
],
"userType": "Employee",
"title": "Tour Guide",
"preferredLanguage": "en-US",
"locale": "en-US",
"timezone": "America/Los_Angeles",
"active": true,
"password": "Password1!"
}'
```
**Response (`200 OK`)**
```json
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"meta": {
"resourceType": "User",
"created": "2025-01-27T15:31:47.84572Z",
"lastModified": "2025-01-27T15:31:47.84572Z",
"version": "16",
"location": "https://localhost:8080/scim/v2/303879575732073153/Users/304499603368096449"
},
"id": "304499603368096449",
"externalId": "8d4b51c0-51bd-4386-ae17-79ce5fd36517",
"userName": "john.doe@example.com",
"name": {
"formatted": "John Doe",
"familyName": "Doe",
"givenName": "John",
"middleName": "Jim",
"honorificPrefix": "Mr.",
"honorificSuffix": "III"
},
"displayName": "John Doe",
"nickName": "Johnny",
"profileUrl": "https://login.example.com/john.doe",
"title": "Tour Guide",
"preferredLanguage": "en-US",
"locale": "en-US",
"timezone": "America/Los_Angeles",
"active": true,
"emails": [
{
"value": "john.doe@example.com",
"primary": true
}
],
"phoneNumbers": [
{
"value": "+15555555555",
"primary": true
}
],
"ims": [
{
"value": "@j.doe",
"type": "X"
}
],
"addresses": [
{
"type": "work",
"streetAddress": "100 Universal City Plaza",
"locality": "Hollywood",
"region": "CA",
"postalCode": "91608",
"country": "USA",
"formatted": "100 Universal City Plaza\nHollywood, CA 91608 USA",
"primary": true
}
],
"photos": [
{
"value": "https://photos.example.com/profilephoto/john.doe/F",
"type": "photo"
}
],
"entitlements": [
{
"value": "read-passports",
"display": "Read Passports"
}
],
"roles": [
{
"value": "user-admin",
"display": "User administrator"
}
]
}
```
`DELETE /Users/{id}`
: Delete a user
```bash
curl -X DELETE "https://${DOMAIN}/scim/v2/${ORG_ID}/Users/${USER_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
```
**Response**: `204 No Content`
`POST /Bulk`
: Update the password of one user and delete another one
```bash
curl -X POST "https://${DOMAIN}/scim/v2/${ORG_ID}/Bulk" \
-H 'Content-Type: application/scim+json' \
-H 'Accept: application/scim+json' \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
--data-raw '
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:BulkRequest"],
"Operations": [
{
"method": "PATCH",
"path": "/Users/${USER_ID}",
"data": {
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "replace",
"path": "password",
"value": "Password2!"
}
]
}
},
{
"method": "DELETE",
"path": "/Users/${USER_ID2}"
}
]
}'
```
**Response**: `200 OK`
```json
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:BulkResponse"],
"Operations": [
{
"method": "PATCH",
"location": "https://${DOMAIN}/scim/v2/${ORG_ID}/Users/${USER_ID}",
"status": "204"
},
{
"method": "DELETE",
"location": "https://${DOMAIN}/scim/v2/${ORG_ID}/Users/${USER_ID2}",
"status": "204"
}
]
}
```
`GET /ServiceProviderConfig`
: Get service provider settings
```bash
curl -G "https://${DOMAIN}/scim/v2/${ORG_ID}/ServiceProviderConfig" \
-H 'Accept: application/scim+json'
```
**Response**: `200 OK`
```json
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"
],
"meta": {
"resourceType": "ServiceProviderConfig",
"location": "https://${DOMAIN}/scim/v2/${ORG_ID}/ServiceProviderConfig"
},
"documentationUri": "https://zitadel.com/docs/guides/manage/user/scim2",
"patch": {
"supported": true
},
"bulk": {
"supported": true,
"maxOperations": 100,
"maxPayloadSize": 1000000
},
"filter": {
"supported": true,
"maxResults": 100
},
"changePassword": {
"supported": true
},
"sort": {
"supported": true
},
"etag": {
"supported": false
},
"authenticationSchemes": [
{
"name": "Zitadel authentication token",
"description": "Authentication scheme using the OAuth Bearer Token Standard",
"specUri": "https://www.rfc-editor.org/info/rfc6750",
"documentationUri": "https://zitadel.com/docs/guides/integrate/service-accounts/authenticate-service-accounts",
"type": "oauthbearertoken",
"primary": false
}
]
}
```
Error handling [#error-handling]
The SCIM interface uses standard HTTP status codes and error messages to indicate the success or failure of API requests
following the error handling guidelines of [RFC7644](https://datatracker.ietf.org/doc/html/rfc7644#section-3.12).
In addition to the default SCIM error schema (`urn:ietf:params:scim:api:messages:2.0:Error`),
Zitadel extends the error response with a custom schema, `urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail`.
This schema includes additional attributes, such as the untranslated error message and an error id,
which aids pinpointing the source of the error in the system.
Resources [#resources]
* **[Zitadel SCIM Documentation](/guides/manage/user/scim2)**: Documentation of Zitadel's SCIM implementation, including settings and known limitations.
* **[SCIM](https://scim.cloud/)**: The Webpage of SCIM.
* **[RFC7643](https://tools.ietf.org/html/rfc7643) Core Schema**:
The Core Schema provides a platform-neutral schema and extension model for representing users and groups.
* **[RFC7644](https://tools.ietf.org/html/rfc7644) Protocol**:
The SCIM Protocol is an application-level, REST protocol for provisioning and managing identity data on the web.
* **[RFC7642](https://tools.ietf.org/html/rfc7642) Definitions, Overview, Concepts, and Requirements**:
This document lists the user scenarios and use cases of System for Cross-domain Identity Management (SCIM).
# GRPC Status Codes in ZITADEL
| GRPC Number | GRPC Code | HTTP Status Code | HTTP Status Text | Description |
| :---------- | :------------------- | ---------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0 | OK | 200 | OK | Not an error; returned on success. |
| 2 | UNKNOWN | 500 | Internal | Unknown error, this is sent if the error could not be identified as one of the errors described below |
| 3 | INVALID\_ARGUMENT | 400 | Bad Request | The client specified an invalid argument. Note that this differs from FAILED\_PRECONDITION. INVALID\_ARGUMENT indicates arguments that are problematic regardless of the state of the system (e.g., a malformed file name). |
| 4 | DEADLINE\_EXCEEDED | 504 | Gateway Timeout | The deadline expired before the operation could complete. |
| 5 | NOT\_FOUND | 404 | Not found | Some requested entity (e.g. user or project) was not found. |
| 6 | ALREADY\_EXISTS | 409 | Conflict | The entity that a client attempted to create (e.g. user or project) already exists. |
| 7 | PERMISSION\_DENIED | 403 | Forbidden | The caller does not have permission to execute the specified operation. |
| 9 | FAILED\_PRECONDITION | 400 | Bad Request | The operation was rejected because the system is not in a state required for the operation's execution. e.g a project that is already deactivated, should be deactivated |
| 12 | UNIMPLEMENTED | 501 | Not Implemented | The operation is not implemented or is not supported/enabled in this service. |
| 13 | INTERNAL | 500 | Internal | Internal errors. This means that some invariants expected by the underlying system have been broken. This error code is reserved for serious errors. |
| 14 | UNAVAILABLE | 503 | Service Unavailable | The service is currently unavailable. |
| 16 | UNAUTHENTICATED | 401 | Unauthorized | The request does not have valid authentication credentials for the operation. |
# APIs V2 (Generally Available)
The v2 APIs are the recommended way to interact with ZITADEL resources.
They are resource-based, automatically scope results to the caller's permissions, and replace the context-specific v1 APIs over time.
If a resource you need is not yet available here, use the corresponding [legacy v1 API](/apis/introduction#legacy-v1-apis) for that operation and check the [migration guide](/apis/migration_v1_to_v2) to plan your transition.
Users created with the V2 API have no initial state anymore, so new users are immediately active.
Requesting ZITADEL to send a verification email on user creation is still possible.
# Key Concepts
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
# Principles
ZITADEL engineering and design principles [#zitadel-engineering-and-design-principles]
* Be transparent about your decisions
* Embrace stateless application design
* System of records is the event store
* Everything else needs to be able to be regenerated
* Try not to solve complex problems outside the IAM Domain
* Use a scalable storage for the event store and read models
* Try to be idempotent whenever possible
* Reduce necessity of external systems or dependencies as much as possible
* Embrace automation
* Design API first
* Optimize all components for day-two operations
* Use only open source projects with permissive licenses
* Don't roll your own crypto algorithm
* Embrace (industry) standard as much as possible
* Make use of platform features
* Be able to run with a CDN and WAF
* Releases utilized semantic versioning and release whenever feasible
# Annex for ZITADEL Enterprise and Support Services
Last updated on November 15, 2023
This annex of the [Framework Agreement](./terms-of-service) describes the commercial support services (**Support Services**, **Enterprise License**, or **Enterprise Agreement**) for units of ZITADEL software products (**Unit**), if not otherwise defined a Unit refers to a is a single, dedicated setup of an application or service covered under an Enterprise agreement.
The customer relationship (**Framework Agreement** or **The Agreement**) is created by the **Customer** (**"you"**) by accepting a **Purchase Order** (**"PO"**) for the specified Support Services (**Booking**). Jointly you and ZITADEL will be referred to as **the Parties**. The terms of service (**"TOS"**) outlined in this document establish the most important points of this Framework Agreement – independently of the use of any other services.
Term [#term]
Coverage under this Agreement will start with Booking of Support Services, for a minimum period of 12 months.
Support Services agreements will automatically renew for additional one-year term upon submission of a purchase order for renewal, unless either you or ZITADEL provides written notice (E-Mail sufficient) of termination of any such term.
Each renewal will be at ZITADEL's then-current rate.
In the event that you accesses ZITADEL Support services in any way after the Agreement has expired or been terminated, you will continue to be bound by this Agreement, which will continue to apply to the services after such expiration or termination.
Service review [#service-review]
If not otherwise agreed, ZITADEL offers a yearly review meeting with you to discuss the service quality and any feedback you might have. We are not required to participate in the meeting after the term has expired.
Your obligations [#your-obligations]
Maintenance of units [#maintenance-of-units]
You will ensure that units eligible for Support Service are maintained and upgraded frequently.
If you operate units with a release date older than 180 days since our latest stable release, the term is continued but ZITADEL is not required to handle any support request for that unit until the units are upgraded and re-certified.
Support Process [#support-process]
You will ensure to follow the support process, especially provide all required initial information to the issue, as outlined in the [Annex](./service-description/support-services) to this document.
Training of support staff [#training-of-support-staff]
You will ensure regular training of your support staff. Your support staff must be able to provide the required information for support issues to us, and thus requires access and up-to-date knowledge of the services.
Initial know-how transfer for the services will be organized in training sessions conducted by us. We can provide knowledge sessions throughout the term to train newly onboarded staff, update your support staff about important updates, or refresh knowledge in specified areas. In case we notice insufficient quality of support requests from Customers, we will propose appropriate training sessions.
Financial [#financial]
Lapsed Service Fee [#lapsed-service-fee]
In case the term of the Support Service contract has expired within 1 to 180 days, you will be required to pay a Lapsed Service fee in addition to purchasing and activating a one-year renewal contract term at the then-current fee and conditions. The renewal term's start date will also be backdated to begin coverage from the service's original expiration date.
Please contact us for current fees.
Recertification Fee [#recertification-fee]
Recertification of a unit, to be covered under Support Services, is required for:
* units for which Support Services have been expired for more than 180 days
* units that run a release that is older than 180 days from the products most recent stable release
* requests for support on products and services purchased or supported from non-authorized resellers
Recertification of a unit requires payment of a Recertification Fee which results in a checkup of the unit by ZITADEL. The unit will be inspected to assess its condition and eligibility for service coverage.
Please contact us for current fees.
Disaster recovery [#disaster-recovery]
You are solely responsible to ensure appropriate backup and disaster recovery of Units managed by you.
Any liability for damages, indirect or direct, in case of data loss is explicitly rejected.
Amendments [#amendments]
We are entitled to unilaterally amend these TOS at any time. The current version is accessible via our website. We will inform you of any amendments via email. These amendments shall be considered as accepted upon booking additional services or at the latest after 30 days. In the case of a rejection on your part we reserve the right to terminate the Framework Agreement.
# Data Processing Agreement
Last updated on May 8, 2025
This Data Protection Agreement and its annexes (“**DPA**”) are part of the [Framework Agreement](./terms-of-service) between Zitadel, Inc. and it's affiliates ("**Zitadel**") and the Customer in respect of the provision of certain services, including any applicable statement of work, booking, purchase order (PO) or any agreed upon instructions (the "**Agreement**") and applies where, and to the extent that, Zitadel processes Personal Data as a Processor on behalf of the Customer under the Framework Agreement (each a “**Party**” and together the “**Parties**”).
All capitalized terms not defined in this DPA will have the meanings set forth in the Agreement.
Any privacy or data protection related clauses or agreement previously entered into by Zitadel and the Customer, with regards to the subject matter of this DPA, will be superseded and replaced by this DPA.
No one other than a Party to this DPA, their successors and permitted assignees will have any right to enforce any of its terms.
This DPA shall become legally binding upon Customer entering into the Agreement.
Definitions [#definitions]
"**Applicable Data Protection Law**" means all worldwide data protection and privacy laws and regulations applicable to the Personal Data, including, where applicable, EU/UK Data Protection Law and US Data Protection Laws (in each case, as amended, adopted, or superseded from time to time).
“**Controller**,” “**collecting**,” “**processor**,” and “**processing**,” shall have the meanings given to them under Applicable Data Protection Law.
“**Business**,” “**service provider**,” “**contractor**,” “**selling**,” “**sharing**” and “**third party**” shall have the meanings given to them under applicable US Data Protection Laws.
"**Customer Data**" means information, data and other content, in any form or medium, that is submitted, posted or otherwise transmitted by or on behalf of the Customer through the Zitadel Cloud or Services. For the avoidance of doubt, Customer Data includes Customer Personal Data.
“**Customer Personal Data**” means, in any form or medium, all Personal Data that is processed by Zitadel or its sub-processors on behalf of Customer in connection with the Agreement.
“**EU/UK Data Protection Law**” means: (i) Regulation 2016/679 of the European Parliament and of the Council on the protection of natural persons with regard to the processing of Personal Data and on the free movement of such data, also known as the General Data Protection Regulation (“**GDPR**”); (ii) the GDPR as saved into United Kingdom law by virtue of section 3 of the United Kingdom’s European Union (Withdrawal) Act 2018 (“**UK GDPR**”); (iii) the EU e-Privacy Directive (Directive 2002/58/EC); (iv) the Swiss Federal Act on Data Protection of 2020 and its Ordinance (“**Swiss FADP**”) and (v) any and all applicable national data protection laws and regulatory requirements made under, pursuant to or that apply in conjunction with any of (i), (ii) or (iii); in each case as may be amended or superseded from time to time.
“**Personal Data**” shall have the meaning given to it, or to the terms “personally identifiable information” and “personal information” under applicable Data Protection Law, but shall include, at a minimum, any information related to an identified or identifiable natural person.
“**Restricted Transfer**” means: (i) where the GDPR applies, a transfer of Personal Data from the EEA to a country outside of the EEA which is not subject to an adequacy determination by the European Commission; and (ii) where UK-GDPR applies, a transfer of Personal Data from the United Kingdom to any other country which is not subject to adequacy regulations pursuant to Section 17A of the United Kingdom Data Protection Act 2018, in each case whether such transfer is direct or via onward transfer.
“**Security Incident**” means any unauthorized or unlawful breach of security leading to, or reasonably believed to have led to, the accidental or unlawful destruction, loss, or alteration of, or unauthorized disclosure or access to, Personal Data transmitted, stored or otherwise processed by Zitadel under or in connection with the Agreement.
“**Standard Contractual Clauses**” or “**SCCs**” means the contractual clauses annexed to the European Commission’s Implementing Decision 2021/914 of 4 June 2021 on standard contractual clauses for the transfer of personal data to third countries pursuant to Regulation (EU) 2016/679 of the European Parliament and of the Council.
“**sub-processor**” means any third-party processor engaged by Zitadel to process Customer Data (but shall not include Zitadel employees, contractors or consultants).
“**UK Addendum**” means the International Data Transfer Addendum (version B1.0) issued by the Information Commissioner’s Office under S119(A) of the UK Data Protection Act 2018, as updated or amended from time to time.
“**US Data Protection Laws**” means any relevant U.S. federal and state privacy laws (and any implementing regulations and amendment thereto) effective as of the date of this DPA and that applies to the processing of Customer Personal Data under the Agreement, which may include, depending on the circumstances and without limitation, (i) the California Consumer Privacy Act (Cal. Civ. Code §§ 1798.100 et seq.), as amended by the California Privacy Rights Act of 2020 along with its implementing regulations (“**CCPA**”), (ii) the Colorado Privacy Act (Colo. Rev. Stat. §§ 6-1-1301 et seq.) (CPA), (iii) Connecticut’s Data Privacy Act (CTDPA), (iv) the Utah Consumer Privacy Act (Utah Code Ann. §§ 13-61-101 et seq.) (UCPA) and (v) the Virginia Consumer Data Protection Act VA Code Ann. §§ 59.1-575 et seq. (VCDPA).
Processing of Personal Data [#processing-of-personal-data]
This DPA applies where and only to the extent that Zitadel processes Customer Personal Data in connection with the provision of the Services under the Agreement involving the processing of Personal Data protected by Applicable Data Protection Law.
This DPA reflects the commitment of both Parties to abide by Applicable Data Protection Law for the processing of Personal Data by Zitadel as a processor for the purpose of the Zitadel's provision of the Services and its execution of the Agreement.
This DPA will become effective on the date the Agreement enters into effect and will remain in force for the term of the Agreement, unless otherwise provided for in this DPA or unless individual provisions obviously result in obligations going beyond this.
For the avoidance of doubt, the terms of the Framework Agreement will continue in full force and effect; however, to the extent any term in any Agreement regarding either Party’s obligations with respect to Customer Data is less restrictive than or is inconsistent with this DPA, the terms of this DPA shall supersede and control.
The Parties acknowledge that the following Customer Data will be processed as part of the Services:
Scope [#scope]
Under this Agreement, Zitadel shall process Customer Personal Data to perform its obligations under the Agreement and and strictly in accordance with the documented instructions of Customer (the “**Permitted Purpose**”), except where otherwise required by law(s) that are not incompatible with Applicable Data Protection Law.
The Parties acknowledge and agree that for the purposes of this DPA, the Customer is the controller and appoints Zitadel as a processor to process the Customer Personal Data.
To the extent that the Parties are subject to the California Consumer Privacy Act (CCPA), the Customer is the business whereas Zitadel is a service provider to the Customer.
Each Party shall comply with the obligations that apply to it under Applicable Data Protection Law.
Each Party shall comply with its own obligations under Applicable Data Protection Law in respect of any Customer Personal Data processed under the Agreement.
Customer's Responsibilities [#customers-responsibilities]
The Customer’s instructions to Zitadel shall comply with Applicable Data Protection Law.
The Customer will have sole responsibility for the accuracy, quality and legality of the Customer Data, the means by which the Customer acquired the Customer Data, and the Customer's permissions to process the Customer Data pursuant to this DPA.
As required under Applicable Data Protection Law, the Customer will provide all necessary notices to data subjects and secure the applicable lawful grounds for processing Data under the DPA, including where applicable, all necessary permissions and consents from them. To the extent required under Applicable Data Protection Law, the Customer will receive and document the appropriate consent from the data subject(s).
The Customer represents and warrants that (i) it complies with Applicable Data Protection Law as relevant to the lawful processing by Zitadel of Customer Personal Data for the purposes contemplated by this DPA and the Agreement; and (ii) to the knowledge of the Customer, the processing of Customer Personal Data by Zitadel in accordance with the Customer’s instructions will not cause Zitadel to be in breach of any Applicable Data Protection Law.
The Customer shall not disclose any special categories of Personal Data or sensitive personal information (as these terms are defined under Applicable Data Protection Law) to Zitadel for processing.
Obligations of the processor [#obligations-of-the-processor]
Bound by the Customer's directions and instructions [#bound-by-the-customers-directions-and-instructions]
Customer hereby instructs Zitadel to process Customer Data for the Permitted Purpose.
Zitadel processes Personal Data in accordance with its privacy policy (cf. [Privacy Policy](./policies/privacy-policy)) and upon the documented directions of the Customer (which includes the Agreement).
Subsequent instructions shall be given either in writing, whereby e-mail shall suffice, or orally with immediate written confirmation.
Zitadel shall promptly inform Customer if it becomes aware that such processing instructions infringe Applicable Data Protection Law (but without obligation to actively monitor compliance with Applicable Data Protection Law).
In such case, Zitadel shall be entitled to suspend the processing until the infringing instruction is withdrawn or confirmed.
Confidentiality obligations [#confidentiality-obligations]
Zitadel shall ensure that any person that it authorizes to process Customer Data (including Zitadel’s staff, agents and sub-processors) (an “Authorized Person”) shall be subject to a strict duty of confidentiality (whether a contractual duty or a statutory duty) and shall not permit any person to process the Customer Data that is not under such a duty of confidentiality.
Zitadel shall ensure that all Authorized Persons process the Customer Data only as necessary for the Permitted Purpose.
Technical and organizational measures [#technical-and-organizational-measures]
Zitadel shall implement appropriate technical and organizational measures to protect the Customer Data from a Security Incident, as described in Annex II to this DPA.
Such measures shall comply with all Applicable Data Protection Law and shall further have regard to the state of the art, the costs of implementation and the nature, scope, context and purposes of processing as well as the risk of varying likelihood and severity for the rights and freedoms of natural persons.
The Customer acknowledges that such measures are subject to technical progress and development and that Zitadel may update or modify such measures from time to time, provided that such updates and modifications do not degrade or diminish overall security of the Customer Data, or of the Services under the Agreement.
Involvement of subcontracted processors [#involvement-of-subcontracted-processors]
Customer agrees that Zitadel may engage sub-processors to process Customer Data on Customer’s behalf. A current and complete [list of involved and approved sub-processors](https://zitadel.com/trust) can be found on our [Trust Center](https://zitadel.com/trust) (as may be updated from time to time in accordance with this DPA).
Zitadel will notify Customer by updating the list of sub-processors and, if Customer has subscribed to notices, via email.
If, within five (5) calendar days after such notice, Customer notifies Zitadel in writing that Customer objects to Zitadel's appointment of a new sub-processor based on reasonable data protection concerns, the parties will discuss such concerns in good faith with a view to achieving a commercially reasonable resolution.
If the parties are not able to mutually agree to a resolution of such concerns, Customer, as its sole and exclusive remedy, may terminate the Agreement for convenience with no refunds and Customer will remain liable to pay any committed fees in an order form, order, statement of work or other similar ordering document.
Zitadel shall inform the Customer if it adds or replaces any sub-processor at least fifteen (15) days prior to any such change (including details of the processing it performs or will perform).
The Customer may object in writing to Zitadel’s engagement of a new sub-processor on reasonable grounds relating to the protection of Customer Personal Data by notifying Zitadel promptly in writing within fifteen (15) calendar days of receipt of Zitadel’s notice.
In such case, the parties shall discuss Customer’s concerns in good faith with a view to achieving a commercially reasonable resolution.
If such objection right is not exercised by Customer, silence shall be deemed to constitute an approval of the relevant sub-processor engagement.
Where Zitadel appoints a sub-processor, Zitadel shall: (i) enter into an agreement with each sub-processor containing data protection terms that provide at least the same level of protection for Customer Data as those contained in this DPA, to the extent applicable to the nature of the services provided by such sub-processor; and (ii) remain responsible to the Customer for Zitadel’s sub-processors’ failure to perform their obligations with respect to the processing of Customer Data.
Taking into account the safeguards set forth in this DPA, Customer Data may be processed outside of Switzerland or the EU/EAA, such as in the United States or any country in which Zitadel or is sub-processors operate. Our [list of involved and approved sub-processors](https://zitadel.com/trust) provides additional details.
Assistance in responding to requests [#assistance-in-responding-to-requests]
Zitadel shall provide all reasonable and timely assistance (which may include by appropriate technical and organizational measures) to the Customer to enable the Customer to respond to: (i) any request from a data subject to exercise any of their rights under Applicable Data Protection Law ("**Data Subject Request**"); and (ii) any other correspondence, enquiry or complaint received from a data subject, regulator or other third party in connection with the processing of Customer Personal Data.
In the event that any such request, correspondence, enquiry or complaint is made directly to Zitadel, Zitadel shall promptly inform the Customer providing full details of the same.
Zitadel will not respond to a Data Subject Request, however the Customer acknowledges and agrees that Zitadel may at its discretion respond to confirm that such request relates to the Customer.
The Customer hereby acknowledges and agrees that the Services include features which will allow the Customer to manage Data Subject Requests directly through the Services without additional assistance from the Processor.
If the Customer does not have the ability to address a Data Subject Request, Zitadel will, upon the Customer’s written request, provide reasonable assistance to facilitate the Customer’s response to such Data Subject Request to the extent such assistance is consistent with Applicable Data Protection Law; provided that the Customer will be responsible for paying for any reasonable costs incurred or fees charged by Zitadel for providing such assistance.
Zitadel, unless prohibited from doing so by applicable law, will promptly notify the Customer of any requests from a regulator, law enforcement authority or any other relevant and competent authority in relation to the Customer Personal Data that is being processed on behalf of the Customer, to the extent that the request may result in the disclosure of Customer Personal Data to such regulator, law enforcement authority or any other relevant and competent authority.
Cooperation and support for the Customer [#cooperation-and-support-for-the-customer]
Zitadel shall provide the Customer with all such reasonable and timely assistance as Customer may require in order to enable it to conduct a data protection impact assessment (or equivalent document) where required by Applicable Data Protection Law, including, if necessary, to assist Customer to consult with its relevant data protection or other regulatory authority.
Security incidents [#security-incidents]
Upon becoming aware of a Security Incident, Zitadel shall inform Customer without undue delay and provide all such timely information and cooperation as Customer may require for the Customer to fulfil its data breach or cybersecurity incident reporting obligations under (and in accordance with the timescales required by) Applicable Data Protection Law.
Customer shall further take all such measures and actions as are reasonable and necessary to investigate, contain, and remediate or mitigate the effects of the Security Incident, to the extent that the remediation is within Zitadel's control, and shall keep Customer informed of all material developments in connection with the Security Incident.
Notwithstanding anything to the contrary, Zitadel's notification of or response to a Security Incident under this section will not be construed as an acknowledgment by Zitadel of any fault or liability with respect to such Security Incident.
Deletion or destruction after termination [#deletion-or-destruction-after-termination]
Upon termination or expiry of the Agreement, Zitadel shall (at the Customer’s election) destroy or return to the Customer all Customer Data (including all copies of the Customer Data) in its possession or control (including any Customer Data subcontracted to a third party for processing).
This requirement shall not apply to the extent that Zitadel is required by any applicable law to retain some or all Customer Data, in which case Zitadel shall isolate and protect the Customer Data from any further processing except to the extent required by such law until deletion is possible.
Customer's information and audit rights [#customers-information-and-audit-rights]
To the extent required under Applicable Data Protection Law and on written request from the Customer, Zitadel shall provide written responses (which may include audit report summaries/extracts) to all reasonable requests for information made by the Customer related to its processing of Customer Personal Data as necessary to confirm Zitadel's compliance with this DPA.
The Customer shall not exercise this right more than once in any twelve (12)-month rolling period, except (i) if and when required by instruction of a competent data protection or other regulatory authority; or (ii) if Zitadel has experienced a Security Incident where Customer was directly impacted.
Nothing in this section shall be construed to require Zitadel to document or provide: (i) trade secrets or any proprietary information; (ii) any information that would violate Zitadel’s confidentiality obligations, contractual obligations, or applicable law; or (iii) any information, the disclosure of which could threaten, compromise, or otherwise put at risk the security, confidentiality, or integrity of Zitadel’s infrastructure, networks, systems, algorithms or data.
Service Optimization [#service-optimization]
Where permitted by Applicable Data Protection Law, Zitadel may process Customer Data: (i) for its internal uses to build or improve the quality of its services; (ii) to detect Security Incidents; and (iii) to protect against fraudulent or illegal activity.
Zitadel may: (i) compile aggregated and/or de-identified information in connection with the provision of the Services, provided that such information cannot reasonably be used to identify Customer or any data subject to whom Customer Personal Data relates (“Aggregated and/or De-Identified Data”); and (ii) use such Aggregated and/or De-Identified Data for its lawful business purposes in accordance with Applicable Data Protection Law.
Data Transfers [#data-transfers]
Where either Party intends to transfer Personal Data cross-border and Applicable Data Protection Law requires certain measures to be implemented prior to such transfer, each Party agrees to implement such measures to ensure compliance with Applicable Data Protection Law.
To the extent that the transfer of Personal Data from Customer to Zitadel involves a transfer of Personal Data outside the European Economic Area (EEA), Switzerland, or the United Kingdom to a jurisdiction which is not subject to an adequacy determination by the European Commission, United Kingdom or Swiss authorities (as applicable) that covers such transfer, then the SCCs are hereby incorporated by reference and form an integral part of the DPA.
EEA Transfers [#eea-transfers]
To the extent that Customer Personal Data is subject to the GDPR, and the transfer would be a Restricted Transfer, the SCCs apply as follows:
1. the Customer is the ‘data exporter’ and Zitadel is the ‘data importer’;
2. the Module Two terms (Transfer controller to processor) apply;
3. in Clause 7, the optional docking clause does not apply;
4. in Clause 9, Option 2 (General Authorization) applies and the time period for prior notice of sub-processor changes is set out in this DPA;
5. in Clause 11, the optional language does not apply;
6. in Clause 17, Option 1 applies, and the SCCs are governed by German law;
7. in Clause 18(b), disputes will be resolved before the courts of Hamburg in Germany;
8. in Annex I, the details of the parties and the transfer are set out in the Agreement;
9. in Clause 13(a) and Annex I, the Hamburg data protection authority will act as competent supervisory authority;
10. in Annex II, the description of the technical and organizational security measures is set out in Annex 2 of this DPA or, if not set out therein, the applicable statement of work; and
11. in Annex III, the list of sub-processors is set out at the address [https://zitadel.com/trust](https://zitadel.com/trust) or, if not set out therein, applicable statement of work.
Swiss Transfers [#swiss-transfers]
To the extent that Customer Personal Data is subject to Swiss law, and the transfer would be a Restricted Transfer, the SCCs apply as set out above with the following modifications:
1. references to ‘Regulation (EU) 2016/679’ are interpreted as references to the Swiss FADP or any successor thereof;
2. references to specific articles of ‘Regulation (EU) 2016/679’ are replaced with the equivalent article or section of the Swiss FADP,
3. references to ‘EU’, ‘Union’ and ‘Member State’ are replaced with ‘Switzerland’,
4. Clause 13(a) and Part C of Annex 2 is not used and the ‘competent supervisory authority’ is the Swiss Federal Data Protection Information Commissioner (“**FDPIC**”) or, if the transfer is subject to both the Swiss FADP and the GDPR, the FDPIC (insofar as the transfer is governed by the Swiss FADP) or the DPC (insofar as the transfer is governed by the GDPR),
5. references to the ‘competent supervisory authority’ and ‘competent courts’ are replaced with the FDPIC and ‘competent Swiss courts’,
6. in Clause 17, the SCCs are governed by the laws of Switzerland,
7. in Clause 18(b), disputes will be resolved before the competent Swiss courts, and
8. the SCCs also protect the data of legal entities until entry into force of the revised Swiss FADP.
UK Transfers [#uk-transfers]
To the extent that Customer Personal Data is subject to Applicable Data Protection Law of the United Kingdom, and the transfer would be a Restricted Transfer, the SCCs as set out above shall apply as amended by Part 2 of the UK Addendum, and Part 1 of the UK Addendum is deemed completed as follows:
1. in Table 1, the details of the parties are set out in the Agreement or, if not set out therein, the applicable statement of work;
2. in Table 2, the selected modules and clauses are set out in Section 6.3 of this DPA;
3. in Table 3, the appendix information is set out in the annexes to this DPA or, if not set out therein, the applicable statement of work; and
4. in Table 4, the ‘Exporter’ is selected.
Alternative Transfer Mechanism [#alternative-transfer-mechanism]
In the event that a court of competent jurisdiction or supervisory authority orders (for whatever reason) that the measures described in this DPA cannot be relied on to lawfully transfer Customer Personal Data, or Zitadel adopts an alternative data transfer mechanism to the mechanisms described in this DPA, including any new version of or successor to the standard contractual clauses (“Alternative Transfer Mechanism”), the Customer agrees to fully co-operate with Zitadel to agree an amendment to this DPA and/or execute such other documents and take such other actions as may be necessary to remedy such non-compliance or give legal effect to such Alternative Transfer Mechanism.
Additional Provisions under US Data Protection Laws [#additional-provisions-under-us-data-protection-laws]
The Parties agree that all Customer Personal Data that is subject to US Data Protection Laws (including the CCPA) is disclosed to Zitadel by the Customer for the Permitted Purpose and its use or sharing by the Customer with Zitadel is necessary to perform such Permitted Purpose.
Zitadel agrees that it will not:
1. sell or share any Customer Personal Data to a third party for any purpose other than than for the Permitted Purpose;
2. retain, use, or disclose any Customer Personal Data (i) for any purpose other than for the Permitted Purpose, including for any commercial purpose, or (ii) outside of the direct business relationship between the Parties, except as necessary to perform the Permitted Purpose or as otherwise permitted by US Data Protection Laws; or
3. combine Customer Personal Data received from or on behalf of Customer with Personal Data received from or on behalf of any third party or collected from Zitadel’s own interaction with individuals or data subjects, except to perform a Permitted Purpose in accordance with the CCPA, the Agreement and this DPA.
The Parties acknowledge that the Customer Personal Data that Customer discloses to Zitadel is provided only for the limited and specified purposes set forth as the Permitted Purpose in the Agreement and this DPA.
Zitadel shall provide the same level of protection to Customer Personal Data as required by the CCPA and will: (i) assist the Customer in responding to any request from a data subject to exercise rights under US Data Protection Laws; and (ii) immediately notify the Customer if it is not able to meet the requirements under the CCPA.
The Customer may take such reasonable and appropriate steps as may be necessary (a) to ensure that the Customer Personal Data collected is used in a manner consistent with the business’s obligations under the CCPA; and (b) to stop and remediate any unauthorized use of Customer Personal Data, and (b) to ensure that Customer Personal Data is used in a manner consistent with the CCPA.
Miscellaneous [#miscellaneous]
This DPA shall be governed by and construed in accordance with the governing law and jurisdiction provisions set out in the Agreement, unless required otherwise by Applicable Data Protection Law.
Any liability owed by one party to the other under this DPA shall be subject to the limitations of liability set forth in the Agreement.
This DPA shall terminate upon the earlier of (i) the termination or expiry of all Agreement under which Customer Data may be processed, or (ii) the written agreement of the Parties.
Any notices shall be delivered to a Party in accordance with the notice provisions of the Agreement, unless otherwise specified hereunder.
Annex 1: Description of Processing Activities / Transfer [#annex-1-description-of-processing-activities-transfer]
List of Parties [#list-of-parties]
| Data Exporter | Data Importer |
| :----------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------- |
| Name: The Party identified as the Customer in the Agreement. | Name: The Party identified as Zitadel in the Agreement. |
| Address: As identified in the Agreement. | Address: As identified in the Agreement. |
| Contact Person's Name, position and contact details: As identified in the Agreement. | Contact Person's Name, position and contact details: As identified in the Agreement. |
| Activities relevant to the transfer: See below | Activities relevant to the transfer: See below |
| Role: Controller | Role: Processor |
Description of processing / transfer [#description-of-processing-transfer]
| | Description |
| :--------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------ |
| **Categories of data subjects:** | As described in the section "Processing of Personal Data" of the DPA |
| **Categories of personal data:** | As described in the section "Processing of Personal Data" of the DPA |
| **Sensitive data:** | None. |
| **If sensitive data, the applied restrictions or safeguards** | N/A |
| **Frequency of the transfer:** | Continuous |
| **Nature and subject matter of processing:** | The Services described in the Agreement. |
| **Purpose(s) of the data transfer and further processing:** | As set forth in the Agreement. |
| **Retention period (or, if not possible to determine, the criteria used to determine that period):** | The personal data may be retained until termination or expiry of the DPA. |
Competent supervisory authority [#competent-supervisory-authority]
The competent supervisory authority in connection with Customer Personal Data protected by the GDPR, is the Hamburg data protection authority.
If this is not possible, then as otherwise agreed by the parties consistent with the conditions set forth in Clause 13.
In connection with Customer Personal Data that is protected by UK-GDPR, the competent supervisory authority is the Information Commissioners Office (the "ICO").
Annex 2: Technical and organizational measures [#annex-2-technical-and-organizational-measures]
Zitadel has implemented an information security program, that is designed to protect the confidentiality, integrity and availability of Customer Data. Zitadel's information security program includes the following organizational and technical security measures to ensure a level of protection of the Personal Data processed that is appropriate to the risk:
Pseudonymization / Encryption [#pseudonymization-encryption]
The following measures for pseudonymization and encryption exist:
1. All communication is encrypted with TLS >1.2 with PFS
2. Critical data is exclusively stored in encrypted form
3. Storage media that store customer data are always encrypted
4. Passwords are irreversibly stored with a hash function
5. Data for web analytics are pseudonymized and do not contain any personal data
Ensuring certain properties of the systems and services [#ensuring-certain-properties-of-the-systems-and-services]
Confidentiality [#confidentiality]
The following confidentiality measures exist:
1. Information security policies
2. Authentication policies
3. Vendor management policies
4. Technical measures in this annex
Integrity [#integrity]
The following integrity measures exist:
1. Code and container images are automatically checked for vulnerabilities
2. An automated system is used to keep dependencies up to date
3. Secrets are automatically rotated whenever possible and are short-lived (for example, signing keys)
4. Changes to code or infrastructure require mandatory review by at least one other employee
Availability [#availability]
The following measures of availability exist:
1. Operation of the systems in combination with a CDN/DDoS mitigation service
2. High availability operation
3. Geo-redundant operation over at least two data centers
Load capacity [#load-capacity]
The following measures of availability exist:
1. Automatic scaling of resources
2. Monitoring, logging, tracing and alerting
Restoring availability and access [#restoring-availability-and-access]
The following measures exist to restore availability and access:
1. Implementation of a backup concept
2. Emergency plan
3. Testing of the emergency plan
Regular review, assessment and evaluation of effectiveness [#regular-review-assessment-and-evaluation-of-effectiveness]
The following measures exist for regular review, assessment and evaluation of effectiveness:
1. At least annual audit and evaluation of processes within the framework of an information security management system
2. Responsible Disclosure and Bug Bounty policies
3. External audit of system security ("penetration testing")
# Legal Agreements
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
# Third party sub-processors for ZITADEL
Last updated on December 5, 2025.
In order to achieve the best possible transparency we publish which sub-processors and services we use to provide ZITADEL and related services.
The table shows what activity each entity performs.
More information about each activity is provided directly below.
This explains the limited processing of customer data the entity is authorized to perform.
We regularly audit all data processing agreements that we have with our sub-processors to guarantee that they adhere to the same level of privacy as ours to protect your personal data.
You can find [the full list of sub-processors in our trust center](https://trust.zitadel.com/subprocessors). We try to minimize the number of sub-processors that handle end-user data on our behalf to reduce any vendor related risks.
Some providers are used by default, but you can opt out of the default provider and replace the sub-processor by a provider of your choice.
# Terms of Service Agreement
Last updated on November 15, 2023
General [#general]
Introduction [#introduction]
CAOS Ltd. (**"We"**, **ZITADEL**, **CAOS AG**, or simply **CAOS**), with head office in Lerchenfeldstrasse 3, 9014 St. Gallen, Switzerland, offers "Identity and Access Management as service" with the brand name "ZITADEL Cloud Services" and all of our Websites (**Services** or **ZITADEL Cloud**).
The customer relationship (**Framework Agreement** or **The Agreement**) is created by the **Customer** (**"you"**) by creating a user or organization within the ZITADEL Cloud Service or with signature of a purchase order between you and ZITADEL (jointly referred to as **Parties**).
On the basis of this Framework Agreement you may then choose to make use of payable services (**Subscription**) as you wish, i.e. you may book services, options and packages yourself at any time (**Booking**, **Purchase Order**, **PO**) and subsequently terminate them.
The terms of service (**"TOS"**) outlined in this document establish the most important points of this Framework Agreement – independently of the use of any services.
This Agreement has the following appendices. When you enter the Agreement with us, you accept these agreements.
* [**Data Processing Agreement**](./data-processing-agreement) - How we process personal data on behalf of you
* [**Service Descriptions**](./service-description) - How we provide services to you
* [**Policies**](./policies) - Policies that apply for use of our services
* [**Enterprise Agreement**](./annex-support-services) - Annex for Enterprise Agreement and Support Services
The outlined policies complement these terms of service.
When accepting the TOS, you accept these policies.
Alterations [#alterations]
Any provisions which deviate from these TOS must be agreed in writing between the Customer and us. Such agreements shall take precedence over the TOS outlined in this document.
Transfer [#transfer]
You may only transfer the Framework Agreement or Services used in the context of the Framework Agreement to third parties with our prior written consent.
Our Services [#our-services]
Type and scope of the services [#type-and-scope-of-the-services]
We provide the Services under the conditions stated on our websites, or the latest customer specific purchase order, at the time of booking.
Modifications of services offered [#modifications-of-services-offered]
We are entitled to offer new services, to withdraw existing services (**Termination**) or to modify the specifications and prices of existing services (**Modification**) at any time.
If the modification or termination affects a service that you are using at that time, we will inform you via email that said service will be automatically modified and/or is no longer available after a period of 30 days.
If such modification would have a disadvantageous impact on the Customer use of service, ZITADEL and Customer must discuss the change with the Customer first and, to the best of its ability, find a solution that is acceptable to both Parties.
If such a solution cannot be found, ZITADEL may implement the modification and Customer may submit notice of termination of the relevant Service (email is sufficient) before the modification becomes effective without being obliged to pay contractual penalties or termination fees.
ZITADEL may modify the prices for a service after the minimum term of the agreement.
Modification of services booked by you [#modification-of-services-booked-by-you]
You may change or terminate Services or Subscriptions booked by you at any time.
You may, where applicable, add more Services (e.g. add-ons) to your existing Services at any time.
Modifications will take effect in the next billing period, or as agreed otherwise between the Parties.
Changing services booked by you requires a new purchase order, stating the new conditions of the services after Modification, to be accepted by the Parties.
Due care [#due-care]
We take all appropriate physical and electronic precautions to ensure the security and availability of our infrastructure and the service offered thereupon, in particular to protect against unauthorized access to data, data loss, failures and misuse.
The [Annex of the data processing agreement](./data-processing-agreement#annex-2-technical-and-organizational-measures) outlines the measures we take in more detail.
Support [#support]
We offer Support Services directly related to the use of our Services.
The Description of Support Services is available as [Annex](./service-description/support-services) to this document.
Customers without a subscription can contact us via the official [communication channels](https://zitadel.com/contact).
The parties may enter a service level agreement, as specified in our [Support Service Description](./service-description/support-services), for booked Support Services.
Only named persons in the Purchase Order, or as agreed in writing (email is sufficient) may use the Support Services to interact with ZITADEL.
Service level [#service-level]
Customers with a Subscription may be eligible for a SLA as outlined in our [Service Level Description](./service-description/service-level-description).
Service credit [#service-credit]
Failure to provide the agreed service level objectives during the term of the Agreement results in compensation via service credits, as outlined in the [Annex](./service-description/support-services) per service level objective.
Customer must request service credit and must notify ZITADEL in writing (email sufficient) within 30 days of becoming eligible for service credit and must prove failure of ZITADEL to meet the stated objective.
ZITADEL will confirm or reject the claim with reasons for a refusal within 10 days.
Service credit will in no case be paid as a cash equivalent.
No further guarantees are provided.
Limited influence [#limited-influence]
Be advised that the scope of our influence is limited. For example, the actual accessibility of a service is also dependent on the connection to and between various Internet Service Providers ("ISPs").
Portions of our services, i.e. software components, may also be beyond our influence and be subject to their own contractual conditions. You accept that in such cases we reject any responsibility.
Inclusion of third parties [#inclusion-of-third-parties]
We may include third parties in the provision of our services. See our [Privacy Policy](./policies/privacy-policy), [Third Party Sub-Processor List](./subprocessors), and our [Data Processing Agreement](./data-processing-agreement) for more information.
Your obligations [#your-obligations]
Contact information [#contact-information]
At our request you will provide your truthful contact information and keep it updated at all times. You must also ensure that you actually receive messages, in particular emails, intended for you.
Use [#use]
You will ensure that the use of our Websites and Services by you or third parties complies with all applicable legislation, this Agreement, any Annexes and policies, specifically the [Acceptable Use Policy](./policies/acceptable-use-policy), at all times.
Security [#security]
You will take appropriate measures to prevent any misuse of the services you booked.
These include, for example, securing the software used and the prompt installation of security updates as well as using suitably secure passwords.
Disaster recovery [#disaster-recovery]
We take care of the necessary disaster recovery measures. The goal is to maintain a maximum 24h old restore point off all the vital data.
Any liability for damages, indirect or direct, in case of data loss is explicitly rejected.
Reporting obligations [#reporting-obligations]
You will immediately report any knowledge of a misuse of your booked services.
Cooperation [#cooperation]
If the maintenance of service quality requires your cooperation, for example to remedy errors in the services you use, you will provide said cooperation promptly and free of charge.
Third party obligations [#third-party-obligations]
You will ensure that your vicarious agents, customers and third parties fulfill these obligations as well.
Financial [#financial]
Credit and payment [#credit-and-payment]
Signup to our Services does not require you to open a payment account.
However, a payment account is required for the purchase of our Subscriptions.
The costs for the services you have purchased will be debited periodically from your payment account or must be paid according to the purchase order.
Payment procedure [#payment-procedure]
If payment upon invoice is agreed, the payment deadline shall be 30 days after receipt of the invoice, or as stated in the purchase order.
Offsetting [#offsetting]
Offsetting against a counterclaim is prohibited.
Collection [#collection]
In the event of default we reserve the right to transfer our claim to a collections agency. You will bear any resulting costs insofar as legally permissible.
Termination [#termination]
Termination by you [#termination-by-you]
You may terminate the Framework Agreement at any time by ceasing your use of the services and deleting your customer account on our website.
For purchase orders, the term must be terminated by providing written notice (email is sufficient) of termination at least 30 days prior to the end of the term.
Termination by us [#termination-by-us]
We may terminate the Framework Agreement at any time via email message with a notice period of 90 days. Any use of the services will cease at the end of this period and the Framework Agreement will be terminated.
Automatic termination [#automatic-termination]
If you have neither used services nor made payment for a period of 180 days, the Framework Agreement will be considered automatically terminated at the end of this period.
If you have a Subscription to any free plans, that don't require payment, we automatically the Framework Agreement will be considered automatically terminated after 30 days without any Daily Active User on the Unit.
No reimbursement [#no-reimbursement]
Any remaining credit shall automatically expire upon termination of the Framework Agreement.
Termination of services [#termination-of-services]
We are entitled to suspend and terminate services used by you if
* Your credit has been used up by services and/or any applicable credit limit has been reached;
* You are in default in the payment of open invoices and/or prompt payment seems unlikely (i.e. in the event of insolvency proceedings);
* Your services were used illegally or in breach of contract, or if there is reasonable suspicion of such use (i.e. in the event of complaints or abuse reports);
* Other customers' services are being negatively affected in breach of the fair use provision, including in the event of your services being subject to attacks by third parties (i.e. DoS/DDoS attacks);
* We consider the suspension or termination of the services to be necessary for the protection of ourselves, our infrastructure or other customers.
We reserve the right to immediately terminate the Framework Agreement in such cases.
Deletion of data [#deletion-of-data]
In the event of the termination of the contract, we reserve the right to irrevocably delete all of your data.
Data protection [#data-protection]
Please consult the annex to this Framework Agreement, specifically our [Privacy Policy](./policies/privacy-policy), [Data Processing Agreement](./data-processing-agreement), [Third Party Sub-Processors](./subprocessors), and or our [Trust Site](https://zitadel.com/trust/) for more information about how we process and protect your data.
Liability [#liability]
Our liability [#our-liability]
We and/or third parties which we involve are only liable for demonstrably willful or grossly negligent damages.
Our liability per damage event is limited to the value of the services used during the previous contractual year.
Any liability in other cases, for consequential damages or lost profits is hereby excluded.
Your liability [#your-liability]
You are liable for all damages and costs arising from the illegal or non-contractual use of the services which you have booked.
We in particular reserve the right to invoice you for any additional costs incurred by us in this context.
Force majeure [#force-majeure]
You acknowledge that we may be partially or entirely unable to provide our services during and/or as a result of events beyond our influence.
These include events such as natural disasters, war, terrorism, sabotage, attacks on our infrastructure (i.e. DoS/DDoS attacks), failure of electrical or data connections and unexpected official requirements.
We are not liable for any damages in such cases.
Final provision [#final-provision]
Applicable law [#applicable-law]
The Framework Agreement is subject to Swiss law.
Place of jurisdiction [#place-of-jurisdiction]
The exclusive place of jurisdiction is St. Gallen, Switzerland.
Severability clause [#severability-clause]
Should any provision of these TOS be or become invalid, this shall not affect the validity of the remaining TOS. The invalid provision will be replaced by a valid one which approximates the invalid one as much as possible.
Amendments [#amendments]
We are entitled to unilaterally amend this Agreement at any time.
The current version is accessible via our website.
We will inform you of any amendments via email.
These amendments shall be considered as accepted upon booking additional services or at the latest after 30 days.
In the case of a rejection on your part we reserve the right to terminate the Framework Agreement.
# Release Cycle
We release a new major version of our software every three months. This predictable schedule allows us to introduce significant features and enhancements in a structured way.
This cadence provides enough time for thorough development and rigorous testing. Before each stable release, we engage with our community and customers to test and stabilize the new version. This ensures high quality and reliability. For our customers, this approach creates a clear and manageable upgrade path.
While major changes are reserved for these three-month releases, we address urgent needs by backporting smaller updates, such as critical bug and security fixes, to earlier versions. This allows us to provide essential updates without altering the predictable rhythm of our major release cycle.
Preparation [#preparation]
The first quarter of our cycle is for Preparation and Planning, where we create the blueprint for the upcoming major release.
During this time, we define the core architecture, map out the implementation strategy, and finalize the design for the new features.
Implementation [#implementation]
The second month is the Implementation and Development Phase, where our engineers build the features defined in the planning stage.
During this period, we focus on writing the code for the new enhancements.
We also integrate accepted contributions from our community and create the necessary documentation alongside the development work.
This phase concludes when the new version is feature-complete and ready to enter the testing phase.
Release Candidate (RC) [#release-candidate-rc]
The first month of the third quarter is for the Release Candidate (RC) and Stabilization Phase.
At the beginning of this month, we publish a Release Candidate version.
This is a feature-complete version that we believe is ready for public release, made available to our customers and community for widespread testing.
This phase is critical for ensuring the quality of the final release. We have two main objectives:
* **Community Feedback and Bug Fixing**: This is when we rely on your feedback. By testing the RC in your own environments, you help us find and fix bugs and other issues we may have missed. Your active participation is crucial for stabilizing the new version.
* **Enhanced Internal Testing**: While the community provides feedback, our internal teams conduct enhanced quality assurance. This includes in-depth feature validation, rigorous testing of upgrade paths from previous versions, and comprehensive performance and benchmark testing.
The goal of this phase is to use both community feedback and internal testing to ensure the new release is robust, bug-free, and performs well, so our customers can upgrade with confidence.
General Availability (GA) / Stable [#general-availability-ga-stable]
Following the month-long Release Candidate and Stabilization phase, we publish the official General Availability (GA) / Stable Release.
This is the final, production-ready version of our software that has been thoroughly tested by both our internal teams and the community.
This release is available to everyone, and we recommend that customers begin reviewing the official upgrade path for their production environments.
The deployment of this new major version to our cloud services also happens at this time.
**Ongoing Maintenance: Minor and Patch Releases**
Once a major version becomes stable, we provide ongoing support through back-porting. This means we carefully select and apply critical updates from our main development track to the stable release, ensuring it remains secure and reliable. These updates are delivered in two ways:
* Minor Releases: These include simple features and enhancements from the next release cycle that are safe to add, requiring no major refactoring or large database migrations.
* Patch Releases: These are focused exclusively on high-priority bug and security fixes to address critical issues promptly.
This process ensures that you can benefit from the stability of a major release while still receiving important updates and fixes in a timely manner.
Deprecated [#deprecated]
Each major version is actively supported for a full release cycle after its launch. This means that approximately six months after its initial stable release, a version enters its deprecation period.
Once a version is deprecated, we strongly encourage all self-hosted customers to upgrade to a newer version as soon as possible to continue receiving the latest features, improvements, and bug fixes.
For our enterprise customers, we may offer extended support by providing critical security fixes for a deprecated version beyond the standard six-month lifecycle. This extended support is evaluated on a case-by-case basis to ensure a secure and manageable transition for large-scale deployments.
# Zitadel Release Versions and Roadmap
Timeline and Overview [#timeline-and-overview]
2025
2026
Q1
Q2
Q3
Q4
Q1
Q2
Q3
Q4
Jan
Feb
Mar
Apr
May
Jun
Jul
Aug
Sep
Oct
Nov
Dec
Jan
Feb
Mar
Apr
May
Jun
Jul
Aug
Sep
Oct
Nov
Dec
Zitadel Versions
[v2.x](/product/roadmap#v2-x)
GA / Stable
Deprecated
[v3.x](/product/roadmap#v3-x)
Implementation
RC
GA / Stable
Deprecated
[v4.x](/product/roadmap#v4-x)
Implementation
RC
GA / Stable
Deprecated
[v5.x](/product/roadmap#v5-x)
TBC
For more detailed description about the different stages and the release cycle check out the following Page: [Release Cycle](/product/release-cycle)
25-Q1
25-Q2
25-Q3
25-Q4
Zitadel Core
[v2.x](/product/roadmap#v2-x)
[v3.x](/product/roadmap#v3-x)
Actions V2
Removed CockroachDB Support
License Change
Login v2
Initial Release
All standard authentication methods
OIDC & SAML
[v4.x](/product/roadmap#v4-x)
Resource API
Login v2 as default
Device Authorization Flow
LDAP IDP
JWT IDP
Custom Login UI Texts
[v5.x](/product/roadmap#v5-x)
Release: TBC
Analytics
User Groups
User Uniqueness on Instance Level
Remove Required Fields from User
Zitadel SDKs
[Initial Version of PHP SDK](/product/roadmap#v-3-x-php)
[Initial Version of Java SDK](/product/roadmap#v-3-x-java)
[Initial Version of Ruby SDK](/product/roadmap#v-3-x-ruby)
[Initial Version of Python SDK](/product/roadmap#v-3-x-python)
Zitadel Core [#zitadel-core]
Check out all [Zitadel Release Versions](https://github.com/zitadel/zitadel/releases)
v2-x [#v-2-x]
**Current State**: General Availability / Stable
**Release**: [v2.x](https://github.com/zitadel/zitadel/releases?q=v2.\&expanded=true)
In Zitadel versions 2.x and earlier, new releases were deployed with a minimum frequency of every two weeks.
This practice resulted in a significant number of individual versions.
To review the features and bug fixes for these releases, please consult the linked release information provided above.
v3-x [#v-3-x]
ZITADEL v3 is here, bringing key changes designed to empower your identity management experience.
This release transitions our licensing to AGPLv3, reinforcing our commitment to open and collaborative development.
We've streamlined our database support by removing CockroachDB.
Excitingly, v3 introduces the foundational elements for Actions V2, opening up a world of possibilities for tailoring and extending ZITADEL to perfectly fit your unique use cases.
**Current State**: General Availability / Stable
**Release**: [v3.x](https://github.com/zitadel/zitadel/releases?q=v3.\&expanded=true)
**Blog**: [Zitadel v3: AGPL License, Streamlined Releases, and Platform Updates](https://zitadel.com/blog/zitadel-v3-announcement)
New Features
Actions V2
Zitadel Actions V2 empowers you to customize Zitadel's workflows by executing your own logic at specific points. You define external Endpoints containing your code and configure Targets and Executions within Zitadel to trigger them based on various conditions and events.
Why we built it: To provide greater flexibility and control, allowing you to implement custom business rules, automate tasks, enrich user data, control access, and integrate with other systems seamlessly. Actions V2 enables you to tailor Zitadel precisely to your unique needs.
Read more in our [documentation](https://zitadel.com/docs/concepts/features/actions_v2)
License Change Apache 2.0 to AGPL3
Zitadel is switching to the AGPL 3.0 license to ensure the project's sustainability and encourage community contributions from commercial users, while keeping the core free and open source.
Read more about our [decision](https://zitadel.com/blog/apache-to-agpl)
Breaking Changes
CockroachDB Support removed
After careful consideration, we have made the decision to discontinue support for CockroachDB in Zitadel v3 and beyond.
While CockroachDB is an excellent distributed SQL database, supporting multiple database backends has increased our maintenance burden and complicated our testing matrix.
Check out our [migration guide](https://zitadel.com/docs/self-hosting/manage/cli/mirror) to migrate from CockroachDB to PostgreSQL.
More details can be found [here](https://github.com/zitadel/zitadel/issues/9414)
Actions API v3 alpha removed
With the current release we have published the Actions V2 API as a beta version, and got rid of the previously published alpha API.
Check out the [new API](/reference/api/action)
v4-x [#v-4-x]
**Current State**: General Availability / Stable
New Features
Resource API (v2)
We are revamping our APIs to improve the developer experience.
Currently, our use-case-based APIs are complex and inconsistent, causing confusion and slowing down integration.
To fix this, we're shifting to a resource-based approach.
This means developers will use consistent endpoints (e.g., /users) to manage resources, regardless of their own role.
This change, along with standardized naming and improved documentation, will simplify integration, accelerate development, and create a more intuitive experience for our customers and community.
Resources integrated in this release:
* Applications (in beta)
* Authorizations (in beta)
* Instances (in beta)
* Organizations
* Permissions (in beta)
* Projects (in beta)
* Settings (beta) now includes 3 new endpoints: `ListOrganizationSettings()`, `SetOrganizationSettings()` and `DeleteOrganizationSettings()`
* Users
For more details read the [Github Issue](https://github.com/zitadel/zitadel/issues/6305)
Login V2
Our new login UI has been enhanced with additional features, bringing it to feature parity with Version 1.
Device Authorization Flow
The Device Authorization Grant is an OAuth 2.0 flow designed for devices that have limited input capabilities (like smart TVs, gaming consoles, or IoT devices) or lack a browser.
Read our docs about how to integrate your application using the [Device Authorization Flow](https://zitadel.com/docs/guides/integrate/login/oidc/device-authorization)
LDAP IDP
This feature enables users to log in using their existing LDAP (Lightweight Directory Access Protocol) credentials.
It integrates your system with an LDAP directory, allowing it to act as an Identity Provider (IdP) solely for authentication purposes.
This means users can securely access the service with their familiar LDAP username and password, streamlining the login process.
JWT IDP
This "JSON Web Token Identity Provider (JWT IdP)" feature allows you to use an existing JSON Web Token (JWT) from another system (like a Web Application Firewall managing a session) as a federated identity for authentication in new applications managed by ZITADEL.
Essentially, it enables session reuse by letting ZITADEL trust and validate a JWT issued by an external source. This allows users already authenticated in an existing system to seamlessly access new applications without re-logging in.
Read more in our docs about how to login users with [JWT IDP](https://zitadel.com/docs/guides/integrate/identity-providers/jwt_idp)
Custom Login UI Texts
This feature provides customers with the flexibility to personalize the user experience by customizing various text elements across different screens of the login UI. Administrators can modify default messages, labels, and instructions to align with their branding, provide specific guidance, or cater to unique regional or organizational needs, ensuring a more tailored and intuitive authentication process for their users.
General Availability
Hosted Login v2
We're officially moving our new Login UI v2 from beta to General Availability.
Starting now, it will be the default login experience for all new customers.
With this release, 8.0 we are also focused on implementing previously missing features, such as device authorization and LDAP IDP support, to make the new UI fully feature-complete.
* [Hosted Login V2](../guides/integrate/login/hosted-login#hosted-login-version-2)
Actions v2
This API enables you to manage custom executions and targets—formerly known as actions—across your entire ZITADEL instance.
With Actions V2, you gain significantly more flexibility to tailor ZITADEL’s behavior compared to previous versions.
Actions are now available instance-wide, eliminating the need to configure them for each organization individually.
ZITADEL no longer restricts the implementation language, tooling, or runtime for action executions.
Instead, you define external endpoints that are called by ZITADEL and maintained by you.
* [Actions V2](/reference/api/action)
Deprecated endpoints
Organization Objects V1 > Users V1
* `AddMachineKey()`
* `AddMachineUser()`
* `AddPersonalAccessToken()`
* `BulkRemoveUserMetadata()`
* `BulkSetUserMetadata()`
* `GenerateMachineSecret()`
* `GetMachineKeyByIDs()`
* `GetOrgByDomainGlobal()`
* `GetPersonalAccessTokenByIDs()`
* `GetUserMetadata()`
* `ListAppKeys()`
* `ListMachineKeys()`
* `ListPersonalAccessTokens()`
* `ListUserMetadata()`
* `RemoveMachineKey()`
* `RemoveMachineSecret()`
* `RemovePersonalAccessToken()`
* `RemoveUserMetadata()`
* `SetUserMetadata()`
* `UpdateHumanPhone()`
* `UpdateMachine()`
* `UpdateUserName()`
Projects V1
* `AddProject()`
* `AddProjectGrant()`
* `AddProjectRole()`
* `BulkAddProjectRoles()`
* `DeactivateProject()`
* `DeactivateProjectGrant()`
* `GetGrantedProjectByID()`
* `GetProjectByID()`
* `GetProjectGrantByID()`
* `ListAllProjectGrants()`
* `ListGrantedProjectRoles()`
* `ListGrantedProjects()`
* `ListProjectGrants()`
* `ListProjectRoles()`
* `ListProjects()`
* `ReactivateProject()`
* `ReactivateProjectGrant()`
* `RemoveProject()`
* `RemoveProjectGrant()`
* `RemoveProjectRole()`
* `UpdateProject()`
* `UpdateProjectGrant()`
* `UpdateProjectRole()`
Members V1
* `AddIAMMember()`
* `AddOrgMember()`
* `AddProjectGrantMember()`
* `AddProjectMember()`
* `ListIAMMembers()`
* `ListOrgMembers()`
* `ListProjectGrantMembers()`
* `ListProjectMembers()`
* `ListUserMemberships()`
* `RemoveIAMMember()`
* `RemoveOrgMember()`
* `RemoveProjectGrantMember()`
* `RemoveProjectMember()`
* `UpdateIAMMember()`
* `UpdateOrgMember()`
* `UpdateProjectGrantMember()`
* `UpdateProjectMember()`
Instance Lifecycle V1 > System Service V1
* `AddInstanceTrustedDomain()`
* `GetMyInstance()`
* `ListInstanceDomains()`
* `ListInstanceTrustedDomains()`
* `RemoveInstanceTrustedDomain()`
Instance Objects V1 > Organizations V1
* `GetDefaultOrg()`
* `GetOrgByID()`
* `IsOrgUnique()`
v5-x [#v-5-x]
**Current State**: Planning
**Release**: TBC
New Features
Analytics
We provide comprehensive and insightful analytics capabilities that empower you with the information needed to understand platform usage, monitor system health, and make data-driven decisions.
Daily Active Users (DAU) & Monthly Active Users (MAU)
Administrators need to track user activity to understand platform usage and identify trends.
This feature provides basic metrics for daily and monthly active users, allowing for filtering by date range and scope (instance-wide or within a specific organization).
The metrics should ensure that each user is counted only once per day or month, respectively, regardless of how many actions they performed.
This minimal feature serves as a foundation for future expansion into more detailed analytics.
For more details track our [github issue](https://github.com/zitadel/zitadel/issues/7506).
Resource Count Metrics
To effectively manage a Zitadel instance, administrators need to understand resource utilization.
This feature provides metrics for resource counts, including organizations, users (with filtering options), projects, applications, and authorizations.
For users, we will offer filters to retrieve the total count, counts per organization, and counts by user type (human or machine).
These metrics will provide administrators with valuable insights into the scale and complexity of their Zitadel instance.
For more details track our [github issue](https://github.com/zitadel/zitadel/issues/9709).
Operational Metrics
To empower customers to better manage and optimize their Zitadel instances, we will provide access to detailed operational metrics.
This data will help customers identify potential issues, optimize performance, and ensure the stability of their deployments.
The provided data will encompass basic system information, infrastructure details, settings, error reports, and the health status of various Zitadel components, accessible via a user interface or an API.
For more details track our [github issue](https://github.com/zitadel/zitadel/issues/9476).
User Groups
Administrators will be able to define groups within an organization and assign users to these groups.
More details about the feature can be found [here](https://github.com/zitadel/zitadel/issues/9702)
User Uniqueness on Organization Level
Administrators will be able to define weather users should be unique across the instance or within an organization.
This allows managing users independently and avoids conflicts due to shared user identifiers.
Example: The user with the username [user@gmail.com](mailto:user@gmail.com) can be created in the Organization "Customer A" and "Customer B" if uniqueness is defined on the organization level.
Stay updated on the progress and details on our [GitHub Issue](https://github.com/zitadel/zitadel/issues/9535)
Remove Required Fields
Currently, the user creation process requires several fields, such as email, first name, and last name, which can be restrictive in certain scenarios. This feature allows administrators to create users with only a username, making other fields optional.
This provides flexibility for systems that don't require complete user profiles upon initial creation for example simplified onboarding flows.
For more details check out our [GitHub Issue](https://github.com/zitadel/zitadel/issues/4386)
Feature Deprecation
Actions V1
Breaking Changes
Hosted Login v1 will be removed
Zitadel APIs v1 will be removed
v6-x [#v-6-x]
New Features
Basic Threat Detection Framework
This initial version of our Threat Detection Framework is designed to enhance the security of your account by identifying and challenging potentially anomalous user behavior.
When the system detects unusual activity, it will present a challenge, such as a reCAPTCHA, to verify that the user is legitimate and not a bot or malicious actor.
Security administrators will also have the ability to revoke user sessions based on the output of the threat detection model, providing a crucial tool to mitigate potential security risks in real-time.
We are beginning with a straightforward reCAPTCHA-style challenge to build and refine the core framework.
This foundational step will allow us to gather insights into how the system performs and how it can be improved.
Future iterations will build upon this groundwork to incorporate more sophisticated detection methods and a wider range of challenge and response mechanisms, ensuring an increasingly robust and intelligent security posture for all users.
More details can be found in the (GitHub Issue]\([https://github.com/zitadel/zitadel/issues/9707](https://github.com/zitadel/zitadel/issues/9707))
SCIM Outbound
Automate user provisioning to your external applications with our new SCIM Client.
This feature ensures users are automatically created in downstream systems before their first SSO login, preventing access issues and streamlining onboarding.
It also synchronizes user lifecycle events, so changes like deactivations or deletions are instantly reflected across all connected applications for consistent and secure access management.
The initial release will focus on provisioning the user resource.
More details can be found in the (GitHub Issue]\([https://github.com/zitadel/zitadel/issues/6601](https://github.com/zitadel/zitadel/issues/6601))
Analytics
We provide comprehensive and insightful analytics capabilities that empower you with the information needed to understand platform usage, monitor system health, and make data-driven decisions.
Login Insights: Successful and Failed Login Metrics
To enhance security monitoring and gain insights into user authentication patterns, administrators need access to login metrics.
This feature provides data on successful and failed login attempts, allowing for filtering by time range and level (overall instance, within a specific organization, or for a particular application).
This will enable administrators to detect suspicious login activity, analyze authentication trends, and proactively address potential security concerns.
For more details track our [GitHub issue](https://github.com/zitadel/zitadel/issues/9711).
Impersonation: External Token Exchange
This feature expands our existing impersonation capabilities to support seamless and secure integration with external, third-party applications.
Currently, our platform supports impersonation for internal use cases, allowing administrators or support staff to obtain a temporary token for an end-user to troubleshoot issues or provide assistance within applications that already use ZITADEL for authentication. (You can find more details in our [existing documentation](/guides/integrate/token-exchange)).
The next evolution of this feature will focus on external applications.
This enables scenarios where a user, already authenticated in a third-party system (like their primary e-banking portal), can seamlessly access a connected application that is secured by ZITADEL without needing to log in again.
For example, a user in their e-banking app could click to open an integrated "Budget Planning" tool that relies on ZITADEL for access.
Using a secure token exchange, the budget app will grant the user a valid session on their behalf, creating a smooth, uninterrupted user experience while maintaining a high level of security.
This enhancement bridges the authentication gap between external platforms and ZITADEL-powered applications.
Future Vision / Upcoming Features [#future-vision-upcoming-features]
Fine Grained Authorization [#fine-grained-authorization]
We're planning the future of Zitadel and fine-grained authorization is high on our list.
While Zitadel already offers strong role-based access (RBAC), we know many of you need more granular control.
**What is Fine-Grained Authorization?**
It's about moving beyond broad roles to define precise access based on:
* Attributes (ABAC): User details (department, location), resource characteristics (sensitivity), or context (time of day).
* Relationships (ReBAC): Connections between users and resources (e.g., "owner" of a document, "manager" of a team).
* Policies (PBAC): Explicit rules combining attributes and relationships.
**Why Explore This?**
Fine-grained authorization can offer:
* Tighter Security: Minimize access to only what's essential.
* Greater Flexibility: Adapt to complex and dynamic business rules.
* Easier Compliance: Meet strict regulatory demands.
* Scalable Permissions: Manage access effectively as you grow.
**We Need Your Input!** 🗣️
As we explore the best way to bring this to Zitadel, tell us:
* Your Use Cases: Where do you need more detailed access control than standard roles provide?
* Preferred Models: Are you thinking attribute-based, relationship-based, or something else?
* Integration Preferences:
* A fully integrated solution within Zitadel?
* Or integration with existing authorization vendors (e.g. openFGA, cerbos, etc.)?
Your feedback is crucial for shaping our roadmap.
🔗 Share your thoughts and needs in our [discussion forum](https://discord.com/channels/927474939156643850/1368861057669533736)
Threat Detection [#threat-detection]
We're taking the next step in securing your applications by exploring a new Threat Detection framework for Zitadel.
Our goal is to proactively identify and stop malicious activity in real-time.
**Our First Step: A Modern reCAPTCHA Alternative**
We will begin by building a system to detect and mitigate malicious bots, serving as a smart, privacy-focused alternative to CAPTCHA.
This initial use case will help us combat credential stuffing, spam registrations, and other automated attacks, forming the foundation of our larger framework.
**How We Envision It**
Our exploration is focused on creating an intelligent system that:
* **Analyzes Signals**: Gathers data points like IP reputation, device characteristics, and user behavior to spot suspicious activity.
* **Uses AI/**: Trains models to distinguish between legitimate users and bots, reducing friction for real users.
* **Mitigates Threats**: Enables flexible responses when a threat is detected, such as blocking the attempt, requiring MFA, or sending an alert.
**Help Us Shape the Future** 🤝
As we design this framework, we need to know:
* What are your biggest security threats today?
* What kind of automated responses (e.g., block, notification) would be most useful for you?
* What are your key privacy or compliance concerns regarding threat detection?
Your feedback will directly influence our development and ensure we build a solution that truly meets your needs.
🔗 Join the conversation and share your insights [here](https://discord.com/channels/927474939156643850/1375383775164235806)
The Role of AI in Zitadel [#the-role-of-ai-in-zitadel]
As we look to the future, we believe Artificial Intelligence will be a critical tool for enhancing both user experience and security within Zitadel.
Our vision for AI is focused on two key areas: providing intelligent, contextual assistance and building a collective defense against emerging threats.
1. **AI-Powered Support**
We want you to get fast, accurate answers to your questions without ever having to leave your workflow.
To achieve this, we are integrating an AI-powered support assistant trained on our knowledge base, including our documentation, tutorials, and community discussions.
Our rollout is planned in phases to ensure we deliver a helpful experience:
* **Phase 1 (Happening Now)**: We are currently testing a preliminary version of our AI bot within our [community channels](https://discord.com/channels/927474939156643850/1357076488825995477). This allows us to gather real-world questions and answers, refining the AI's accuracy and helpfulness based on direct feedback.
* **Phase 2 (Next Steps)**: Once we are confident in its capabilities, we will integrate this AI assistant directly into our documentation. You'll be able to ask complex questions and get immediate, well-sourced answers.
* **Phase 3 (The Ultimate Goal)**: The final step is to embed the assistant directly into the ZITADEL Management Console/Customer Portal. Imagine getting help based on the exact context of what you're doing—whether you're configuring an action, setting up a new organization, or integrating social login.
2. **Decentralized AI for Threat Detection**
Security threats are constantly evolving.
A threat vector that targets one customer today might target another tomorrow.
We believe in the power of collective intelligence to provide proactive security for everyone.
This leads to our second major AI initiative: **decentralized model training** for our Threat Detection framework.
Here’s how it would work:
* **Collective Data, Anonymously**: Customers across our cloud and self-hosted environments experience different user behaviors and threat vectors. We plan to offer an opt-in system where anonymized, non-sensitive data (like behavioral patterns and threat signals) can be collected from participating instances.
* **Centralized Training**: This collective, anonymized data will be used to train powerful, next-generation AI security models. With a much larger and more diverse dataset, these models can learn to identify subtle and emerging threats far more effectively than a model trained on a single instance's data.
* **Shared Protection**: These constantly improving models would then be distributed to all participating Zitadel instances.
The result is a powerful security network effect. You could receive protection from a threat vector you haven't even experienced yet, simply because the system learned from an attack on another member of the community.
Zitadel Ecosystem [#zitadel-ecosystem]
PHP SDK [#php-sdk]
GitHub Repository: [PHP SDK](https://github.com/zitadel/client-php)
v3.x (PHP) [#v-3-x-php]
Java SDK [#java-sdk]
GitHub Repository: [Java SDK](https://github.com/zitadel/client-java)
v3.x (Java) [#v-3-x-java]
Ruby SDK [#ruby-sdk]
GitHub Repository: [Ruby SDK](https://github.com/zitadel/client-ruby)
v3.x (Ruby) [#v-3-x-ruby]
Python SDK [#python-sdk]
GitHub Repository: [Python SDK](https://github.com/zitadel/client-python)
v3.x (Python) [#v-3-x-python]
# ZITADEL Technical Advisories
Technical advisories are notices that report major issues with ZITADEL Self-Hosted or the ZITADEL Cloud platform that could potentially impact security or stability in production environments.
These advisories may include details about the nature of the issue, its potential impact, and recommended mitigation actions.
Users are strongly encouraged to evaluate these advisories and consider the recommended mitigation actions independently of their version upgrade schedule.
We understand that these advisories may include breaking changes, and we aim to provide clear guidance on how to address these changes.
The default behavior for users logging in is to be directed to the Select
Account Page on the Login. With the upcoming changes, users will be
automatically authenticated when logging into a second application, as
long as they only have one active session. No action is required on your
part if this is the intended behavior.
When disabling the option, users are currently not able to register
locally and also not through an external IDP. With the upcoming change,
the setting will only prevent local registration. Restriction to Identity
Providers can be managed through the corresponding IDP Template. No action
is required on your side if this is the intended behavior or if you
already disabled registration on your IDP.
Since Angular Material v15 many of the UI components have been refactored
to be based on the official Material Design Components for Web (MDC).
These refactored components do not support dynamic styling, so in order to
keep the library up-to-date, the management console UI will loose its dynamic theming
capability. If you need users to have your branding settings (background-,
button-, link and text coloring) you should implement your own user
facing UI yourself and not use ZITADEL's management console UI.
ZITADEL hosted Login-UI is not affected by this change.
When users are redirected to the ZITADEL Login-UI without any organizational context,
they're currently presented a login screen, based on the default settings,
e.g. available IDPs and possible login mechanisms. If the user will then register themselves,
by the registration form or through an IDP, the user will always be created on the default organization.
With the introduced change, the settings will no longer be loaded from the instance, but rather the default organization directly.
Due to storage optimizations ZITADEL changes the behavior of sequences.
This change improves command (create, update, delete) performance of ZITADEL.
Sequences are no longer unique inside an instance.
From now on sequences are incrementing per aggregate id.
For example sequences of newly created users begin at 1.
Existing sequences remain untouched.
Migrating to versions >= 2.39 from \< 2.39 will cause down time during setup starts and the new version is started.
This is caused by storage optimizations which replace the `eventstore.events` database table with the new `eventstore.events2` table.
All existing events are migrated during the execution of the `zitadel setup` command.
New events will be inserted into the new `eventstore.events2` table. The old table `eventstore.events` is renamed to `eventstore.events_old` and will be dropped in a future release of ZITADEL.
Versions >= 2.39.0 require the cockroach database user of ZITADEL to be granted to the `VIEWACTIVITY` grant. This can either be reached by grant the role manually or execute the `zitadel init` command.
Upcoming Versions require the SYSTEM\_OWNER role to be available in the permission role mappings. Self-hosting ZITADEL users who define custom permission role mappings need to make sure their system users don't lose access to the system API.
Subscribe to our Mailing List [#subscribe-to-our-mailing-list]
If you want to stay up to date on our technical advisories, we recommend subscribing to our [Technical Advisory mailing list](https://share-eu1.hsforms.com/1NAOUyAMmRzSnB7uP665xHA2dljbt) or visiting our [website](https://zitadel.com/technical-advisory).
As ZITADEL Cloud customer, you can also log in to the ZITADEL Customer Portal and enable the Technical Advisory Notifications in your settings.
Categories [#categories]
Breaking Behavior Change [#breaking-behavior-change]
A breaking behavior change refers to a modification or update that changes the behavior of ZITADEL.
This change does not necessarily affect the APIs or any functions you are calling, so it may not require an update to your code.
However, if you rely on specific results or behaviors, they may no longer be guaranteed after the change is implemented.
Therefore, it is important to be aware of breaking behavior changes and their potential impact on your use of ZITADEL, and to take appropriate action if needed to ensure continued functionality.
Expected downtime during upgrade [#expected-downtime-during-upgrade]
Expected downtime during upgrade means that ZITADEL might become unavailable during an upgrade.
ZITADEL is built for [zero downtime upgrades](/concepts/architecture/solution#zero-downtime-updates) at upgrades can be executed without downtime by just updating to a more recent version.
When deploying certain changes a zero downtime upgrade might not be possible, for example to guarantee data integrity.
In such cases we will issue a technical advisory to make you aware of this unexpected behavior.
# Troubleshoot ZITADEL
You will find some possible error messages here, what the problem is and what some possible solutions can be.
Join or [Chat](https://zitadel.com/chat) or open a [Discussion](https://github.com/zitadel/zitadel/discussions).
User agent does not correspond [#user-agent-does-not-correspond]
This error appeared for some users as soon as they were redirected to the login page of ZITADEL.
ZITADEL uses some cookies to identify the browser/user agent of the user, so it is able to store the active user sessions. By blocking the cookies the functions of ZITADEL will be affected.
We only found this issue with iPhone users, and it was dependent on the settings of the device.
Go to the settings of the app Safari and check in the "Experimental WebKit Features" if SameSite strict enforcement (ITP) is disabled
Also check if "block all cookies" is active. If so please disable this setting.
To make sure, that your new settings will trigger, please restart your mobile phone and try it again.
**Settings > Safari > Advanced > Experimental Features > disable: "SameSite strict enforcement (ITP)"**
**Settings > Safari > disable: "Block All cookies"**
Do you still face this issue? Please contact us, and we will help you find out what the problem is.
Invalid audience [#invalid-audience]
`invalid audience (APP-Zxfako)`
This error message refers to the audience claim (`aud`) of your token.
This claim identifies the audience, i.e. the resource server, that this token is intended for.
If a resource server does not identify itself with a value in the "aud" claim when this claim is present, then the must be rejected (see [RFC7519](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.3) for more details).
You might encounter this error message from ZITADEL, typically when you authenticated with an application in one project and trying to access an application in another project.
You need add a specific [reserved scope](/apis/openidoauth/scopes#reserved-scopes) to add the projectID to the audience of the access token.
The two scenarios should help you troubleshoot this issue:
Frontend to Backend [#frontend-to-backend]
You have one project for your frontend application and one project for your backend application.
End-users authenticate to an application in your frontend project.
The frontend then sends requests to the backend, validates the token with ZITADEL's introspection endpoint, and returns a payload to the frontend.
The backend returns the error `invalid audience (APP-Zxfako)`.
You must add the scope `urn:zitadel:iam:org:project:id:{projectId}:aud` to the auth request that is sent from the front end.
Replace `projectId` with the projectId of your backend.
Accessing ZITADEL's APIs [#accessing-zitade-ls-ap-is]
You have a project for a frontend application.
The application should also access the API of your ZITADEL, for example to pull a list of all users and display them on a user page.
End-users authenticate to the application in the frontend project, but when calling the management API you get the error `invalid audience (APP-Zxfako)`.
You must add the scope `urn:zitadel:iam:org:project:id:zitadel:aud` to the auth request that is sent from the front end.
When accessing your ZITADEL instance's APIs they act as a resource server.
You can check the Management Console or via API and see that when you open your default organization there exists a project "ZITADEL" that contains different applications for each API and the Console.
Like in the scenario above the access token requires to have an `aud` claim that includes the "ZITADEL" project.
Instead of `urn:zitadel:iam:org:project:id:zitadel:aud` you could also use `urn:zitadel:iam:org:project:id:{projectId}:aud`, where `projectId` is the projectId of the Project "ZITADEL".
WebFinger requirement for Tailscale [#web-finger-requirement-for-tailscale]
The WebFinger requirement and setup is a step a user has to take outside their IdP set-up. WebFinger is a protocol which supports the ability for OIDC issuer discovery, and we use it to prove that the user has administrative control over the domain and to retrieve the issuer. This is a requirement we have in place for all users, regardless of their IdP, who use custom OIDC with Tailscale.
On their Custom Domain, e.g. example.com, users need to host a WebFinger endpoint at [https://example.com/.well-known/webfinger](https://example.com/.well-known/webfinger). When queried, this endpoint returns a JSON response detailing the issuer. Users would need to host the endpoint with the link to the ZITADEL issuer. Tailscale only looks up this endpoint once when a user signs up, and will only look up this endpoint again if the user needs to make a configuration change to their identity provider.
The requirements and a set-up guide is detailed in the [Tailscale documentation](https://tailscale.com/kb/1240/sso-custom-oidc/).
Login not possible. The organization of the user must be granted to the project [#login-not-possible-the-organization-of-the-user-must-be-granted-to-the-project]
ZITADEL is not only capable of handling authentication but also authorization.
This error message tells you, that a project grant is missing from the owner organization to the organization of the authenticating user.
You do have two organizations, an owner (Organization A) and a customer (Organization B).
The Organization A owns a Project, and has to grant it to Organization B, so users are allowed to authenticate.
The error message is shown to users of Organization B that the permission is required, but the project is not granted to Organization B.
You do have two possibilities.
1. Disable the permission check
2. Give the permission to the organization
Disable the permission check [#disable-the-permission-check]
1. Go to the organization, who owns the project, where the user tries to authenticate.
2. Navigate to the general settings of the needed project
3. Disable "Check for Project on Authentication"
Give the needed permission to the organization [#give-the-needed-permission-to-the-organization]
1. Go to the organization, who owns the project, where the user tries to authenticate.
2. Navigate to the Project Grants page of the needed project
3. Click on the "New" button
4. Search for the organization to which you want to grant the project (e.g. Organization B)
5. Select the roles you want to grant
6. Click save
# Angular
Overview [#overview]
[Angular](https://angular.dev) is a TypeScript-based framework for building web applications. This example demonstrates how to integrate **Zitadel** using the **[OAuth 2.0 PKCE flow](https://oauth.net/2/pkce/)** to authenticate users securely and maintain sessions across your Angular application.
Auth library [#auth-library]
This example uses **[@edgeflare/ngx-oidc](https://www.npmjs.com/package/@edgeflare/ngx-oidc)** (built on **[oidc-client-ts](https://www.npmjs.com/package/oidc-client-ts)**) to implement the [OpenID Connect (OIDC)](https://zitadel.com/docs/apis/openidoauth/endpoints) protocol, manage [PKCE](https://oauth.net/2/pkce/) code challenge generation, perform secure token exchange, and provide session management helpers.
***
What this example demonstrates [#what-this-example-demonstrates]
This Angular application showcases a complete authentication pattern using [Zitadel](https://zitadel.com/docs) with [PKCE](https://oauth.net/2/pkce/). Users begin on a public landing page where they can initiate sign-in with Zitadel's authorization server. After successful authentication, the app handles the OAuth callback and redirects users to a protected profile page displaying their [user information and claims](https://zitadel.com/docs/apis/openidoauth/claims).
The application integrates authentication logic into Angular routing: protected routes use router guards to automatically redirect unauthenticated users to the sign-in flow, and callback routes complete the PKCE exchange and restore session state. The example requests standard OIDC scopes (`openid`, `profile`, `email`), `offline_access` for refresh tokens, and Zitadel-specific scopes for metadata and role information.
The example includes secure sign-out functionality implementing **[federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)** through Zitadel's end-session endpoint, which terminates both the local application session and the Zitadel session, then redirects the user back to the application.
***
Getting started [#getting-started]
Prerequisites [#prerequisites]
Before running this example, you need to create and configure a PKCE application in the [Zitadel Console](https://zitadel.com/docs/guides/manage/console/console-overview). Follow the PKCE application setup guide to:
1. Create a new Web application in your [Zitadel project](https://zitadel.com/docs/guides/manage/console/projects-overview)
2. Configure it to use the PKCE authentication method
3. Set up your redirect URIs (e.g., `http://localhost:3000/auth/callback` for development)
4. Configure post-logout redirect URIs (e.g., `http://localhost:3000/auth/logout/callback`)
5. Copy your **Client ID** for use in the next steps
6. Optionally enable [refresh tokens](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) in Token Settings for long-lived sessions
> **Note:** Make sure to enable **Dev Mode** in the [Zitadel Console](https://zitadel.com/docs/guides/manage/console/console-overview) if you're using HTTP URLs during local development. For production deployments, always use HTTPS URLs and disable Dev Mode.
Run the example [#run-the-example]
Once you have your [Zitadel application configured](https://zitadel.com/docs/guides/integrate/login/oidc/login-users):
1. Clone the [repository](https://github.com/zitadel/example-auth-angular).
2. Create a `.env` file (copy from `.env.example`) and configure it with the values from your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview). Use these environment variable names exactly as shown:
```
NODE_ENV=development
PORT=3000
NG_APP_ZITADEL_DOMAIN=https://your-zitadel-domain
NG_APP_ZITADEL_CLIENT_ID=your-zitadel-application-client-id
NG_APP_ZITADEL_CLIENT_SECRET=
NG_APP_ZITADEL_CALLBACK_URL=http://localhost:3000/auth/callback
NG_APP_ZITADEL_POST_LOGIN_URL=/profile
NG_APP_ZITADEL_POST_LOGOUT_URL=http://localhost:3000/auth/logout/callback
```
Replace these values with:
* Your actual [Zitadel instance URL](https://zitadel.com/docs/guides/manage/console/default-settings) (the **NG\_APP\_ZITADEL\_DOMAIN**)
* The **Client ID** you copied when creating the application
* The **redirect URIs** you configured in the PKCE setup (must match exactly)
* The **post-logout redirect URIs** you configured (must match exactly)
3. Install dependencies using [npm](https://www.npmjs.com) and start the development server to verify the authentication flow end-to-end.
***
Learn more and resources [#learn-more-and-resources]
* [PKCE concept](https://oauth.net/2/pkce/)
* [Federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
* [OIDC integration guide](https://zitadel.com/docs/guides/integrate/login/oidc/login-users)
* [Angular documentation](https://angular.dev)
* [@edgeflare/ngx-oidc package](https://www.npmjs.com/package/@edgeflare/ngx-oidc)
* [oidc-client-ts package](https://www.npmjs.com/package/oidc-client-ts)
* [Example repository](https://github.com/zitadel/example-auth-angular)
# Astro
Overview [#overview]
[Astro](https://astro.build) is a modern web framework that delivers lightning-fast performance with server-first architecture and seamless client-side hydration. This example demonstrates how to integrate **[Zitadel](https://zitadel.com/docs)** authentication using the **[OAuth 2.0 PKCE flow](https://oauth.net/2/pkce/)** to secure your Astro application with professional identity management.
Auth library [#auth-library]
This example uses **[Auth.js](https://authjs.dev)** (specifically the **[@auth/core](https://www.npmjs.com/package/@auth/core)** package and **[auth-astro](https://www.npmjs.com/package/auth-astro)** adapter), a comprehensive authentication library that implements the [OpenID Connect](https://openid.net/connect/) protocol. Auth.js handles [PKCE](https://oauth.net/2/pkce/) code generation and verification, token exchange, session management, and automatic [token refresh](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) to maintain long-lived user sessions.
***
What this example demonstrates [#what-this-example-demonstrates]
This Astro application showcases a complete authentication workflow with [Zitadel](https://zitadel.com/docs) using [Auth.js](https://zitadel.com/docs) using [Auth.js](https://authjs.dev). Users start on a public landing page and click a login button to authenticate through Zitadel's [authorization endpoint](https://zitadel.com/docs) using [Auth.js](https://authjs.dev). Users start on a public landing page and click a login button to authenticate through Zitadel's [authorization endpoint](https://zitadel.com/docs/apis/openidoauth/endpoints) using the secure [PKCE flow](https://zitadel.com/docs). After successful authentication, users are redirected to a protected profile page displaying their [user information and claims](https://zitadel.com/docs) using [Auth.js](https://authjs.dev). Users start on a public landing page and click a login button to authenticate through Zitadel's [authorization endpoint](https://zitadel.com/docs/apis/openidoauth/endpoints) using the secure [PKCE flow](https://oauth.net/2/pkce/). After successful authentication, users are redirected to a protected profile page displaying their [user information and claims](https://zitadel.com/docs/apis/openidoauth/claims) retrieved from the [ID token](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint).
The example implements server-side session management with encrypted [JWT tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint), ensuring secure authentication state across Astro's [server-rendered pages](https://zitadel.com/docs). Protected routes automatically redirect unauthenticated users to the login flow using [Astro's middleware](https://zitadel.com/docs), preventing unauthorized access to sensitive areas.
The application includes complete [logout functionality](https://zitadel.com/docs/guides/integrate/login/oidc/logout) that performs [federated single sign-out](https://zitadel.com/docs/guides/integrate/login/oidc/logout), terminating both the local session and the remote Zitadel session through the [end-session endpoint](https://zitadel.com/docs) using [Auth.js](https://authjs.dev). Users start on a public landing page and click a login button to authenticate through Zitadel's [authorization endpoint](https://zitadel.com/docs/apis/openidoauth/endpoints). It demonstrates [token refresh](https://zitadel.com/docs/guides/integrate/login/oidc/logout) that performs [federated single sign-out](https://zitadel.com/docs/guides/integrate/login/oidc/logout), terminating both the local session and the remote Zitadel session through the [end-session endpoint](https://zitadel.com/docs/apis/openidoauth/endpoints). It demonstrates [token refresh](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) capabilities using [refresh tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint) with the `offline_access` [scope](https://zitadel.com/docs/guides/integrate/login/oidc/logout) that performs [federated single sign-out](https://zitadel.com/docs/guides/integrate/login/oidc/logout), terminating both the local session and the remote Zitadel session through the [end-session endpoint](https://zitadel.com/docs/apis/openidoauth/endpoints). It demonstrates [token refresh](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) capabilities using [refresh tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint) with the `offline_access` [scope](https://zitadel.com/docs/apis/openidoauth/scopes), allowing users to maintain authenticated sessions without repeated logins. The example also shows how to request additional [OIDC scopes](https://zitadel.com/docs/guides/integrate/login/oidc/logout) that performs [federated single sign-out](https://zitadel.com/docs/guides/integrate/login/oidc/logout), terminating both the local session and the remote Zitadel session through the [end-session endpoint](https://zitadel.com/docs/apis/openidoauth/endpoints). It demonstrates [token refresh](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) capabilities using [refresh tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint) with the `offline_access` [scope](https://zitadel.com/docs/apis/openidoauth/scopes) like `profile`, `email`, and Zitadel-specific scopes for [user metadata](https://zitadel.com/docs/guides/integrate/login/oidc/logout) that performs [federated single sign-out](https://zitadel.com/docs/guides/integrate/login/oidc/logout), terminating both the local session and the remote Zitadel session through the [end-session endpoint](https://zitadel.com/docs/apis/openidoauth/endpoints). It demonstrates [token refresh](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) capabilities using [refresh tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint) with the `offline_access` [scope](https://zitadel.com/docs/apis/openidoauth/scopes), allowing users to maintain authenticated sessions without repeated logins. The example also shows how to request additional [OIDC scopes](https://zitadel.com/docs/apis/openidoauth/scopes) like `profile`, `email`, and Zitadel-specific scopes for [user metadata](https://zitadel.com/docs/guides/manage/customize/user-metadata), [organization information](https://zitadel.com/docs/guides/integrate/login/oidc/logout) that performs [federated single sign-out](https://zitadel.com/docs/guides/integrate/login/oidc/logout), terminating both the local session and the remote Zitadel session through the [end-session endpoint](https://zitadel.com/docs/apis/openidoauth/endpoints). It demonstrates [token refresh](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) capabilities using [refresh tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint) with the `offline_access` [scope](https://zitadel.com/docs/apis/openidoauth/scopes), allowing users to maintain authenticated sessions without repeated logins. The example also shows how to request additional [OIDC scopes](https://zitadel.com/docs/apis/openidoauth/scopes) like `profile`, `email`, and Zitadel-specific scopes for [user metadata](https://zitadel.com/docs/guides/manage/customize/user-metadata), [organization information](https://zitadel.com/docs/guides/manage/console/organizations-overview), and [role assignments](https://zitadel.com/docs/guides/integrate/login/oidc/logout) that performs [federated single sign-out](https://zitadel.com/docs/guides/integrate/login/oidc/logout), terminating both the local session and the remote Zitadel session through the [end-session endpoint](https://zitadel.com/docs/apis/openidoauth/endpoints). It demonstrates [token refresh](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) capabilities using [refresh tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint) with the `offline_access` [scope](https://zitadel.com/docs/apis/openidoauth/scopes), allowing users to maintain authenticated sessions without repeated logins. The example also shows how to request additional [OIDC scopes](https://zitadel.com/docs/apis/openidoauth/scopes) like `profile`, `email`, and Zitadel-specific scopes for [user metadata](https://zitadel.com/docs/guides/manage/customize/user-metadata), [organization information](https://zitadel.com/docs/guides/manage/console/organizations-overview), and [role assignments](https://zitadel.com/docs/guides/manage/console/roles).
***
Getting started [#getting-started]
Prerequisites [#prerequisites]
Before running this example, you need to create and configure a PKCE application in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview). Follow the PKCE application setup guide to:
1. Create a new Web application in your [Zitadel project](https://zitadel.com/docs/guides/manage/console/projects-overview)
2. Configure it to use the PKCE authentication method
3. Set up your redirect URIs (e.g., `http://localhost:3000/api/auth/callback/zitadel` for development)
4. Configure post-logout redirect URIs (e.g., `http://localhost:3000/api/auth/logout/callback`)
5. Copy your **Client ID** from the application details
> **Note:** Make sure to enable **Dev Mode** in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview) if you're using HTTP URLs during local development. For production deployments, always use HTTPS URLs and disable Dev Mode.
Run the example [#run-the-example]
Once you have your [Zitadel application configured](https://zitadel.com/docs/guides/integrate/login/oidc/login-users):
1. Clone the [repository](https://github.com/zitadel/example-auth-astro).
2. Create a `.env` file based on `.env.example` and configure it with the values from your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview). Use these environment variable names exactly as shown:
```
PORT=3000
SESSION_DURATION=3600
SESSION_SECRET=your-very-secret-and-strong-session-key
ZITADEL_DOMAIN=https://your-zitadel-domain
ZITADEL_CLIENT_ID=your_client_id_from_console
ZITADEL_CLIENT_SECRET=your-randomly-generated-client-secret
ZITADEL_CALLBACK_URL=http://localhost:3000/api/auth/callback/zitadel
ZITADEL_POST_LOGOUT_URL=http://localhost:3000/api/auth/logout/callback
AUTH_TRUST_HOST=true
NEXTAUTH_URL=http://localhost:3000
```
Replace these values with:
* Your actual [Zitadel instance URL](https://zitadel.com/docs/guides/manage/console/default-settings) (the **ZITADEL\_DOMAIN**)
* The **Client ID** you copied when creating the application
* A randomly generated **SESSION\_SECRET** using: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`
* A randomly generated **ZITADEL\_CLIENT\_SECRET** (required by Auth.js even though PKCE doesn't require it)
* The **redirect URIs** you configured in the PKCE setup (must match exactly)
3. Install dependencies using [npm](https://www.npmjs.com) by running `npm install`, then start the development server with `npm run dev` to verify the authentication flow end-to-end at `http://localhost:3000`.
***
Learn more and resources [#learn-more-and-resources]
* [PKCE concept](https://oauth.net/2/pkce/)
* [Federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
* [OIDC integration guide](https://zitadel.com/docs/guides/integrate/login/oidc/login-users)
* [Astro documentation](https://astro.build)
* [Auth.js documentation](https://authjs.dev)
* [@auth/core package](https://www.npmjs.com/package/@auth/core)
* [auth-astro adapter](https://www.npmjs.com/package/auth-astro)
* [Example repository](https://github.com/zitadel/example-auth-astro)
# Django
Overview [#overview]
[Django](https://www.djangoproject.com) is a high-level Python web framework that encourages rapid development and clean, pragmatic design. It provides a robust set of features for web applications, making it one of the most popular choices for building server-side applications. This example demonstrates how to integrate **Zitadel** using the **[OAuth 2.0 PKCE flow](https://oauth.net/2/pkce/)** to authenticate users securely and maintain sessions across your application.
Auth library [#auth-library]
This example uses **[Authlib](https://authlib.org)**, the standard authentication library for Python web frameworks. Authlib implements the [OpenID Connect (OIDC)](https://zitadel.com/docs/apis/openidoauth/endpoints) flow, manages [PKCE](https://oauth.net/2/pkce/), performs secure token exchange, and provides session management helpers. The integration uses Authlib's Django client which seamlessly handles OAuth 2.0 flows within Django applications.
***
What this example demonstrates [#what-this-example-demonstrates]
This example shows a complete authentication implementation using [Django](https://www.djangoproject.com) with [Zitadel](https://zitadel.com/docs). Users start on a public landing page, click a login button to authenticate with Zitadel using the secure [PKCE flow](https://oauth.net/2/pkce/), and are redirected to a protected profile page displaying their [user information](https://zitadel.com/docs/apis/openidoauth/claims) after successful authentication.
The application implements server-side session management with Django's built-in session framework, storing authentication state securely in signed cookies. Protected routes use the `@require_auth` decorator to automatically redirect unauthenticated users to the sign-in flow, ensuring only authenticated users can access sensitive areas. The profile page displays comprehensive user information including [OIDC claims](https://zitadel.com/docs/apis/openidoauth/claims) and [session metadata](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint).
The application demonstrates proper **[federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)** by terminating sessions both locally and with Zitadel's [end-session endpoint](https://zitadel.com/docs/guides/integrate/login/oidc/logout), complete with CSRF protection using state parameters. Additionally, it includes automatic [token refresh](https://zitadel.com/docs/apis/openidoauth/endpoints) using [refresh tokens](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) to maintain long-lived sessions without requiring users to re-authenticate. The example uses [Zitadel-specific scopes](https://zitadel.com/docs/apis/openidoauth/scopes) like `urn:zitadel:iam:user:metadata` and `urn:zitadel:iam:org:projects:roles` to access extended user attributes and role information for implementing [role-based access control (RBAC)](https://zitadel.com/docs/guides/manage/console/roles).
***
Getting started [#getting-started]
Prerequisites [#prerequisites]
Before running this example, you need to create and configure a PKCE application in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview). Follow the PKCE application setup guide to:
1. Create a new Web application in your [Zitadel project](https://zitadel.com/docs/guides/manage/console/projects-overview)
2. Configure it to use the PKCE authentication method
3. Set up your redirect URIs (e.g., `http://localhost:3000/auth/callback` for development)
4. Configure post-logout redirect URIs (e.g., `http://localhost:3000/auth/logout/callback`)
5. Copy your **Client ID** for use in the next steps
6. Optionally enable [refresh tokens](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) in Token Settings for long-lived sessions
> **Note:** Make sure to enable **Dev Mode** in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview) if you're using HTTP URLs during local development. For production, always use HTTPS URLs and disable Dev Mode.
Run the example [#run-the-example]
Once you have your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview) configured:
1. Clone the [repository](https://github.com/zitadel/example-auth-django).
2. Create a `.env` file (copy from `.env.example`) and configure it with the values from your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview). **Use these exact environment variable names:**
```
PORT=3000
SESSION_SECRET=your-very-secret-and-strong-session-key
ZITADEL_DOMAIN=https://your-zitadel-domain
ZITADEL_CLIENT_ID=your-zitadel-application-client-id
ZITADEL_CLIENT_SECRET=your-randomly-generated-client-secret
ZITADEL_CALLBACK_URL=http://localhost:3000/auth/callback
ZITADEL_POST_LOGIN_URL=/profile
ZITADEL_POST_LOGOUT_URL=http://localhost:3000/auth/logout/callback
```
Replace these values with:
* Your actual [Zitadel instance URL](https://zitadel.com/docs/guides/manage/console/default-settings) for `ZITADEL_DOMAIN` (the issuer)
* The **Client ID** you copied when creating the application for `ZITADEL_CLIENT_ID`
* The **redirect URI** you configured in the PKCE setup for `ZITADEL_CALLBACK_URL` (must match exactly)
* The **post-logout redirect URI** for `ZITADEL_POST_LOGOUT_URL`
* A strong random string for `SESSION_SECRET` (generate using: `python -c "import secrets; print(secrets.token_hex(32))"`)
* A randomly generated string for `ZITADEL_CLIENT_SECRET` (generate using: `python -c "import secrets; print(secrets.token_hex(32))"`)
3. Install dependencies using [Poetry](https://python-poetry.org) with `poetry install` and start the development server with `poetry run python manage.py runserver localhost:3000` to verify the authentication flow end-to-end.
***
Learn more and resources [#learn-more-and-resources]
* [PKCE concept](https://oauth.net/2/pkce/)
* [Federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
* [OIDC integration guide](https://zitadel.com/docs/guides/integrate/login/oidc/login-users)
* [Django documentation](https://www.djangoproject.com)
* [Authlib documentation](https://authlib.org)
* [Example repository](https://github.com/zitadel/example-auth-django)
# ASP.NET Core
Overview [#overview]
[ASP.NET Core](https://learn.microsoft.com/aspnet/core/) is a production-ready framework for building web applications. This example integrates **Zitadel** with the built-in **OpenID Connect handler** using the **Authorization Code Flow + PKCE** to authenticate users securely and manage sessions.
Auth library [#auth-library]
This example uses **[Microsoft.AspNetCore.Authentication.OpenIdConnect](https://learn.microsoft.com/aspnet/core/security/authentication/openid-connect)**. The middleware handles the PKCE dance, exchanges authorization codes for tokens, keeps tokens available on the request, and plugs into the standard ASP.NET Core authentication/authorization pipeline.
***
What this example demonstrates [#what-this-example-demonstrates]
* Public landing page with “Sign in with Zitadel”.
* PKCE login handled by the ASP.NET Core OIDC middleware.
* Route protection via the auth/authorization middleware; profile page renders OIDC claims.
* Cookie-based session management with configurable idle timeout.
* Federated logout that clears the local session and redirects through Zitadel’s RP-initiated logout.
***
Getting started [#getting-started]
Prerequisites [#prerequisites]
* .NET SDK 8 or later
Account setup [#account-setup]
Create a Web application in Zitadel configured for Authorization Code + PKCE, then set:
> **Redirect URIs:** `http://localhost:3000/auth/callback`\
> **Post Logout Redirect URIs:** `http://localhost:3000/auth/logout/callback`
These values must match the sample configuration.
Run the example [#run-the-example]
1. Clone the repository:
```bash
git clone https://github.com/zitadel/example-auth-dotnet.git
cd example-auth-dotnet
```
2. Copy `.env.example` to `.env` and fill in your values:
```dotenv
PORT=3000
SESSION_DURATION=3600
ZITADEL_DOMAIN="https://your-zitadel-domain"
ZITADEL_CLIENT_ID="your-zitadel-application-client-id"
ZITADEL_CLIENT_SECRET="your-randomly-generated-client-secret"
ZITADEL_POST_LOGOUT_URL="http://localhost:3000/auth/logout/callback"
```
3. Start the dev server (restores dependencies automatically):
```bash
make start
```
The app will be available at `http://localhost:3000`.
***
Learn more and resources [#learn-more-and-resources]
* [PKCE concept](https://oauth.net/2/pkce/)
* [ASP.NET Core OIDC handler](https://learn.microsoft.com/aspnet/core/security/authentication/openid-connect)
* [Zitadel OIDC guide](https://zitadel.com/docs/guides/integrate/login/oidc/login-users)
* [Federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
* [Example repository](https://github.com/zitadel/example-auth-dotnet)
# Express.js
Overview [#overview]
[Express.js](https://expressjs.com) is a fast, unopinionated, minimalist web framework for [Node.js](https://nodejs.org) that provides a robust set of features for building web and mobile applications. This example demonstrates how to integrate **Zitadel** using the **[OAuth 2.0 PKCE flow](https://oauth.net/2/pkce/)** to authenticate users securely and maintain sessions across your Express application.
Auth library [#auth-library]
This example uses **[@auth/express](https://www.npmjs.com/package/@auth/express)** ([GitHub](https://github.com/nextauthjs/next-auth)), the Express.js adapter for [Auth.js](https://authjs.dev). This library implements the [OpenID Connect (OIDC)](https://zitadel.com/docs/apis/openidoauth/endpoints) protocol with [PKCE](https://oauth.net/2/pkce/) support, manages [token exchange](https://zitadel.com/docs/apis/openidoauth/grant-types), performs automatic [token refresh](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token), and provides session management with secure cookie-based storage.
***
What this example demonstrates [#what-this-example-demonstrates]
This Express.js example provides a complete authentication implementation using [Zitadel](https://zitadel.com/docs) as the identity provider. The application starts with a public landing page featuring a login button that initiates the [PKCE authentication flow](https://oauth.net/2/pkce/). When users click login, [@auth/express](https://www.npmjs.com/package/@auth/express) generates a cryptographic code verifier and challenge, then redirects to Zitadel's authorization endpoint. After successful authentication at Zitadel, users return to the application's [callback URL](https://zitadel.com/docs/guides/integrate/login/oidc/login-users) where the authorization code is exchanged for [access tokens](https://zitadel.com/docs/apis/openidoauth/endpoints) and an [ID token](https://zitadel.com/docs/apis/openidoauth/claims).
The example includes protected routes using [Express middleware](https://expressjs.com/en/guide/using-middleware.html) that automatically verify session state and redirect unauthenticated users to the sign-in page. Authenticated users can access their profile page displaying user information including email, name, and custom [metadata claims](https://expressjs.com/en/guide/using-middleware.html) that automatically verify session state and redirect unauthenticated users to the sign-in page. Authenticated users can access their profile page displaying user information including email, name, and custom [metadata claims](https://zitadel.com/docs/apis/openidoauth/claims) from Zitadel. The application maintains long-lived sessions through automatic [token refresh](https://expressjs.com/en/guide/using-middleware.html) that automatically verify session state and redirect unauthenticated users to the sign-in page. Authenticated users can access their profile page displaying user information including email, name, and custom [metadata claims](https://zitadel.com/docs/apis/openidoauth/claims) from Zitadel. The application maintains long-lived sessions through automatic [token refresh](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) using [refresh tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint), ensuring users remain authenticated without repeated logins.
Sign-out functionality implements **[federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)** by redirecting to Zitadel's end-session endpoint with the ID token hint, terminating both the local session and the Zitadel session. The logout flow includes [CSRF protection](https://authjs.dev/guides/basics/securing-pages-and-api-routes) through state parameter validation, and users are redirected to a success page after logout completion. All authentication flows use secure HTTP-only cookies for [session storage](https://authjs.dev/concepts/session-strategies) and implement proper security headers.
***
Getting started [#getting-started]
Prerequisites [#prerequisites]
Before running this example, you need to create and configure a PKCE application in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview). Follow the PKCE application setup guide to:
1. Create a new Web application in your [Zitadel project](https://zitadel.com/docs/guides/manage/console/projects-overview)
2. Configure it to use the PKCE authentication method
3. Set up your redirect URIs (e.g., `http://localhost:3000/auth/callback/zitadel` for development)
4. Configure post-logout redirect URIs (e.g., `http://localhost:3000/auth/logout/callback`)
5. Copy your **Client ID** for use in the next steps
> **Note:** Make sure to enable **Dev Mode** in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview) if you're using HTTP URLs during local development. For production, always use HTTPS URLs and disable Dev Mode.
Run the example [#run-the-example]
Once you have your [Zitadel application configured](https://zitadel.com/docs/guides/integrate/login/oidc/login-users):
1. Clone the [repository](https://github.com/zitadel/example-auth-expressjs).
2. Create a `.env.local` file and configure it with the values from your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview). Use the exact environment variable names from the repository:
```
NODE_ENV=development
PORT=3000
SESSION_SECRET=your-very-secret-and-strong-session-key
SESSION_DURATION=3600
ZITADEL_DOMAIN=https://your-instance.zitadel.cloud
ZITADEL_CLIENT_ID=your_client_id_from_console
ZITADEL_CLIENT_SECRET=your-randomly-generated-client-secret
ZITADEL_CALLBACK_URL=http://localhost:3000/auth/callback/zitadel
ZITADEL_POST_LOGIN_URL=/profile
ZITADEL_POST_LOGOUT_URL=http://localhost:3000/auth/logout/callback
```
Replace these values with:
* Your actual Zitadel instance URL (the **Issuer** from the PKCE setup guide)
* The **Client ID** you copied when creating the application
* A randomly generated client secret (generate using: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`)
* The **redirect URI** you configured in the PKCE setup (must match exactly)
* A strong session secret (generate using: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`)
3. Install dependencies using [npm](https://www.npmjs.com) and start the development server:
```bash
npm install
npm run dev
```
The application will be running at `http://localhost:3000`.
***
Learn more and resources [#learn-more-and-resources]
* [PKCE concept](https://oauth.net/2/pkce/)
* [Federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
* [OIDC integration guide](https://zitadel.com/docs/guides/integrate/login/oidc/login-users)
* [Framework docs](https://expressjs.com)
* [Auth library](https://www.npmjs.com/package/@auth/express)
* [Auth.js documentation](https://authjs.dev)
* [Example repository](https://github.com/zitadel/example-auth-expressjs)
# FastAPI
Overview [#overview]
[FastAPI](https://fastapi.tiangolo.com) is a modern, fast (high-performance) web framework for building APIs with Python based on standard Python type hints. This example demonstrates how to integrate **[Zitadel](https://zitadel.com/docs)** using the **[OAuth 2.0 PKCE flow](https://oauth.net/2/pkce/)** to authenticate users securely and maintain sessions across your application.
Auth library [#auth-library]
This example uses **[Authlib](https://authlib.org)**, the standard authentication library for Python web frameworks. Authlib implements the [OpenID Connect (OIDC)](https://zitadel.com/docs/apis/openidoauth/endpoints) flow, manages [PKCE](https://oauth.net/2/pkce/), performs secure token exchange, and provides session management helpers. The integration uses Authlib's Starlette client which seamlessly handles OAuth 2.0 flows within FastAPI applications built on [Starlette](https://www.starlette.io).
***
What this example demonstrates [#what-this-example-demonstrates]
This example shows a complete authentication implementation using [FastAPI](https://fastapi.tiangolo.com) with [Zitadel](https://zitadel.com/docs). Users start on a public landing page, click a login button to authenticate with Zitadel using the secure [PKCE flow](https://oauth.net/2/pkce/), and are redirected to a protected profile page displaying their [user information](https://zitadel.com/docs/apis/openidoauth/claims) after successful authentication.
The application implements server-side session management with Starlette's session middleware, storing authentication state securely in encrypted cookies. Protected routes use the `require_auth` dependency to automatically redirect unauthenticated users to the sign-in flow, ensuring only authenticated users can access sensitive areas. The profile page displays comprehensive user information including [OIDC claims](https://zitadel.com/docs/apis/openidoauth/claims) and [session metadata](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint).
The application demonstrates proper **[federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)** by terminating sessions both locally and with Zitadel's [end-session endpoint](https://zitadel.com/docs/guides/integrate/login/oidc/logout), complete with CSRF protection using state parameters. Additionally, it includes automatic [token refresh](https://zitadel.com/docs/apis/openidoauth/endpoints) using [refresh tokens](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) to maintain long-lived sessions without requiring users to re-authenticate. The example uses [Zitadel-specific scopes](https://zitadel.com/docs/apis/openidoauth/scopes) like `urn:zitadel:iam:user:metadata` and `urn:zitadel:iam:org:projects:roles` to access extended user attributes and role information for implementing [role-based access control (RBAC)](https://zitadel.com/docs/guides/manage/console/roles).
***
Getting started [#getting-started]
Prerequisites [#prerequisites]
Before running this example, you need to create and configure a PKCE application in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview). Follow the PKCE application setup guide to:
1. Create a new Web application in your [Zitadel project](https://zitadel.com/docs/guides/manage/console/projects-overview)
2. Configure it to use the PKCE authentication method
3. Set up your redirect URIs (e.g., `http://localhost:3000/auth/callback` for development)
4. Configure post-logout redirect URIs (e.g., `http://localhost:3000/auth/logout/callback`)
5. Copy your **Client ID** for use in the next steps
6. Optionally enable [refresh tokens](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) in Token Settings for long-lived sessions
> **Note:** Make sure to enable **Dev Mode** in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview) if you're using HTTP URLs during local development. For production, always use HTTPS URLs and disable Dev Mode.
Run the example [#run-the-example]
Once you have your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview) configured:
1. Clone the [repository](https://github.com/zitadel/example-auth-fastapi).
2. Create a `.env` file (copy from `.env.example`) and configure it with the values from your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview). **Use these exact environment variable names:**
```
PY_ENV=development
PORT=3000
SESSION_SECRET=your-very-secret-and-strong-session-key
ZITADEL_DOMAIN=https://your-zitadel-domain
ZITADEL_CLIENT_ID=your-zitadel-application-client-id
ZITADEL_CLIENT_SECRET=your-randomly-generated-client-secret
ZITADEL_CALLBACK_URL=http://localhost:3000/auth/callback
ZITADEL_POST_LOGIN_URL=/profile
ZITADEL_POST_LOGOUT_URL=http://localhost:3000/auth/logout/callback
```
Replace these values with:
* Your actual [Zitadel instance URL](https://zitadel.com/docs/guides/manage/console/default-settings) for `ZITADEL_DOMAIN` (the issuer)
* The **Client ID** you copied when creating the application for `ZITADEL_CLIENT_ID`
* The **redirect URI** you configured in the PKCE setup for `ZITADEL_CALLBACK_URL` (must match exactly)
* The **post-logout redirect URI** for `ZITADEL_POST_LOGOUT_URL`
* A strong random string for `SESSION_SECRET` (generate using: `python -c "import secrets; print(secrets.token_hex(32))"`)
* A randomly generated string for `ZITADEL_CLIENT_SECRET` (generate using: `python -c "import secrets; print(secrets.token_hex(32))"`)
3. Install dependencies using [Poetry](https://python-poetry.org) with `poetry install` and start the development server with `poetry run python run.py` to verify the authentication flow end-to-end.
***
Learn more and resources [#learn-more-and-resources]
* [PKCE concept](https://oauth.net/2/pkce/)
* [Federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
* [OIDC integration guide](https://zitadel.com/docs/guides/integrate/login/oidc/login-users)
* [FastAPI documentation](https://fastapi.tiangolo.com)
* [Authlib documentation](https://authlib.org)
* [Example repository](https://github.com/zitadel/example-auth-fastapi)
# Fastify
Overview [#overview]
[Fastify](https://fastify.dev) is a fast and low overhead web framework for Node.js, designed for building efficient server-side applications. This example demonstrates how to integrate **[Zitadel](https://zitadel.com/docs)** using the **[OAuth 2.0 PKCE flow](https://oauth.net/2/pkce/)** to authenticate users securely and maintain sessions across your application.
Auth library [#auth-library]
This example uses **[@mridang/fastify-auth](https://www.npmjs.com/package/@mridang/fastify-auth)**, a [Fastify](https://fastify.dev) plugin that wraps **[@auth/core](https://www.npmjs.com/package/@auth/core)** (formerly Auth.js). The underlying [@auth/core](https://authjs.dev) library implements the [OpenID Connect (OIDC)](https://zitadel.com/docs/apis/openidoauth/endpoints) flow, manages [PKCE](https://oauth.net/2/pkce/), performs secure token exchange, and provides session management helpers. This integration leverages the **[@auth/core/providers/zitadel](https://authjs.dev/reference/core/providers/zitadel)** provider specifically designed for Zitadel authentication.
***
What this example demonstrates [#what-this-example-demonstrates]
This example shows a complete authentication implementation using [Fastify](https://fastify.dev) with [server-side rendering](https://fastify.dev) via [@fastify/view](https://www.npmjs.com/package/@fastify/view) and [Handlebars](https://handlebarsjs.com) templates. The application implements secure user authentication through [Zitadel](https://zitadel.com/docs) using the industry-standard [PKCE flow](https://oauth.net/2/pkce/), which prevents authorization code interception attacks without requiring client secrets.
The example includes a custom sign-in page that initiates the [OAuth 2.0](https://zitadel.com/docs/guides/integrate/login/oidc/oauth-recommended-flows) authentication flow, automatic [callback handling](https://zitadel.com/docs/guides/integrate/login/oidc/oauth-recommended-flows) authentication flow, automatic [callback handling](https://zitadel.com/docs/guides/integrate/login/oidc/login-users) with secure token exchange, and [JWT-based session management](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint) with [@fastify/cookie](https://zitadel.com/docs/guides/integrate/login/oidc/oauth-recommended-flows) authentication flow, automatic [callback handling](https://zitadel.com/docs/guides/integrate/login/oidc/login-users) with secure token exchange, and [JWT-based session management](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint) with [@fastify/cookie](https://www.npmjs.com/package/@fastify/cookie). Protected routes use the `requireAuth` middleware to automatically redirect unauthenticated users to the sign-in flow, ensuring only authenticated users can access sensitive areas. The profile page displays comprehensive user information including [OIDC claims](https://zitadel.com/docs/guides/integrate/login/oidc/oauth-recommended-flows) authentication flow, automatic [callback handling](https://zitadel.com/docs/guides/integrate/login/oidc/login-users) with secure token exchange, and [JWT-based session management](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint) with [@fastify/cookie](https://www.npmjs.com/package/@fastify/cookie). Protected routes use the `requireAuth` middleware to automatically redirect unauthenticated users to the sign-in flow, ensuring only authenticated users can access sensitive areas. The profile page displays comprehensive user information including [OIDC claims](https://zitadel.com/docs/apis/openidoauth/claims) and [session metadata](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint).
The application also demonstrates proper **[federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)** by terminating sessions both locally and with Zitadel's [end-session endpoint](https://zitadel.com/docs/guides/integrate/login/oidc/logout), complete with CSRF protection using state parameters. Additionally, it includes automatic [token refresh](https://zitadel.com/docs/apis/openidoauth/endpoints) using [refresh tokens](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) to maintain long-lived sessions without requiring users to re-authenticate. The example uses [Zitadel-specific scopes](https://zitadel.com/docs/apis/openidoauth/scopes) like `urn:zitadel:iam:user:metadata` and `urn:zitadel:iam:org:projects:roles` to access extended user attributes and role information for implementing [role-based access control (RBAC)](https://zitadel.com/docs/guides/manage/console/roles).
***
Getting started [#getting-started]
Prerequisites [#prerequisites]
Before running this example, you need to create and configure a PKCE application in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview). Follow the PKCE application setup guide to:
1. Create a new Web application in your [Zitadel project](https://zitadel.com/docs/guides/manage/console/projects-overview)
2. Configure it to use the PKCE authentication method
3. Set up your redirect URIs (e.g., `http://localhost:3000/auth/callback/zitadel` for development)
4. Configure post-logout redirect URIs (e.g., `http://localhost:3000/auth/logout/callback`)
5. Copy your **Client ID** for use in the next steps
6. Optionally enable [refresh tokens](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) in Token Settings for long-lived sessions
> **Note:** Make sure to enable **Dev Mode** in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview) if you're using HTTP URLs during local development. For production, always use HTTPS URLs and disable Dev Mode.
Run the example [#run-the-example]
Once you have your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview) configured:
1. Clone the [repository](https://github.com/zitadel/example-auth-fastify).
2. Create a `.env` file (copy from `.env.example`) and configure it with the values from your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview). **Use these exact environment variable names:**
```
NODE_ENV=development
PORT=3000
SESSION_SECRET=your-very-secret-and-strong-session-key
SESSION_DURATION=3600
ZITADEL_DOMAIN=https://your-zitadel-domain
ZITADEL_CLIENT_ID=your-zitadel-application-client-id
ZITADEL_CLIENT_SECRET=
ZITADEL_CALLBACK_URL=http://localhost:3000/auth/callback/zitadel
ZITADEL_POST_LOGIN_URL=/profile
ZITADEL_POST_LOGOUT_URL=http://localhost:3000/auth/logout/callback
```
Replace these values with:
* Your actual [Zitadel instance URL](https://zitadel.com/docs/guides/manage/console/default-settings) for `ZITADEL_DOMAIN` (the issuer)
* The **Client ID** you copied when creating the application for `ZITADEL_CLIENT_ID`
* The **redirect URI** you configured in the PKCE setup for `ZITADEL_CALLBACK_URL` (must match exactly)
* The **post-logout redirect URI** for `ZITADEL_POST_LOGOUT_URL`
* A strong random string for `SESSION_SECRET` (generate using: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`)
3. Install dependencies using [npm](https://www.npmjs.com) with `npm install` and start the development server with `npm run dev` to verify the authentication flow end-to-end.
***
Learn more and resources [#learn-more-and-resources]
* [PKCE concept](https://oauth.net/2/pkce/)
* [Federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
* [OIDC integration guide](https://zitadel.com/docs/guides/integrate/login/oidc/login-users)
* [Fastify documentation](https://fastify.dev)
* [Auth.js (Auth/Core)](https://www.npmjs.com/package/@auth/core)
* [Fastify Auth plugin](https://www.npmjs.com/package/@mridang/fastify-auth)
* [Example repository](https://github.com/zitadel/example-auth-fastify)
# Flask
Overview [#overview]
[Flask](https://flask.palletsprojects.com) is a lightweight WSGI web application framework for Python. It's designed to make getting started quick and easy, with the ability to scale up to complex applications. This example demonstrates how to integrate **Zitadel** using the **[OAuth 2.0 PKCE flow](https://oauth.net/2/pkce/)** to authenticate users securely and maintain sessions across your application.
Auth library [#auth-library]
This example uses **[Authlib](https://authlib.org)**, the standard authentication library for Python web frameworks. Authlib implements the [OpenID Connect (OIDC)](https://zitadel.com/docs/apis/openidoauth/endpoints) flow, manages [PKCE](https://oauth.net/2/pkce/), performs secure token exchange, and provides session management helpers. The integration uses Authlib's Flask client which seamlessly handles OAuth 2.0 flows within Flask applications.
***
What this example demonstrates [#what-this-example-demonstrates]
This example shows a complete authentication implementation using [Flask](https://flask.palletsprojects.com) with [Zitadel](https://zitadel.com/docs). Users start on a public landing page, click a login button to authenticate with Zitadel using the secure [PKCE flow](https://oauth.net/2/pkce/), and are redirected to a protected profile page displaying their [user information](https://zitadel.com/docs/apis/openidoauth/claims) after successful authentication.
The application implements server-side session management with Flask's built-in session handling, storing authentication state securely in encrypted cookies. Protected routes use the `@require_auth` decorator to automatically redirect unauthenticated users to the sign-in flow, ensuring only authenticated users can access sensitive areas. The profile page displays comprehensive user information including [OIDC claims](https://zitadel.com/docs/apis/openidoauth/claims) and [session metadata](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint).
The application demonstrates proper **[federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)** by terminating sessions both locally and with Zitadel's [end-session endpoint](https://zitadel.com/docs/guides/integrate/login/oidc/logout), complete with CSRF protection using state parameters. Additionally, it includes automatic [token refresh](https://zitadel.com/docs/apis/openidoauth/endpoints) using [refresh tokens](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) to maintain long-lived sessions without requiring users to re-authenticate. The example uses [Zitadel-specific scopes](https://zitadel.com/docs/apis/openidoauth/scopes) like `urn:zitadel:iam:user:metadata` and `urn:zitadel:iam:org:projects:roles` to access extended user attributes and role information for implementing [role-based access control (RBAC)](https://zitadel.com/docs/guides/manage/console/roles).
***
Getting started [#getting-started]
Prerequisites [#prerequisites]
Before running this example, you need to create and configure a PKCE application in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview). Follow the PKCE application setup guide to:
1. Create a new Web application in your [Zitadel project](https://zitadel.com/docs/guides/manage/console/projects-overview)
2. Configure it to use the PKCE authentication method
3. Set up your redirect URIs (e.g., `http://localhost:3000/auth/callback` for development)
4. Configure post-logout redirect URIs (e.g., `http://localhost:3000/auth/logout/callback`)
5. Copy your **Client ID** for use in the next steps
6. Optionally enable [refresh tokens](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) in Token Settings for long-lived sessions
> **Note:** Make sure to enable **Dev Mode** in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview) if you're using HTTP URLs during local development. For production, always use HTTPS URLs and disable Dev Mode.
Run the example [#run-the-example]
Once you have your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview) configured:
1. Clone the [repository](https://github.com/zitadel/example-auth-flask).
2. Create a `.env` file (copy from `.env.example`) and configure it with the values from your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview). **Use these exact environment variable names:**
```
PY_ENV=development
PORT=3000
SESSION_SECRET=your-very-secret-and-strong-session-key
ZITADEL_DOMAIN=https://your-zitadel-domain
ZITADEL_CLIENT_ID=your-zitadel-application-client-id
ZITADEL_CLIENT_SECRET=your-randomly-generated-client-secret
ZITADEL_CALLBACK_URL=http://localhost:3000/auth/callback
ZITADEL_POST_LOGIN_URL=/profile
ZITADEL_POST_LOGOUT_URL=http://localhost:3000/auth/logout/callback
```
Replace these values with:
* Your actual [Zitadel instance URL](https://zitadel.com/docs/guides/manage/console/default-settings) for `ZITADEL_DOMAIN` (the issuer)
* The **Client ID** you copied when creating the application for `ZITADEL_CLIENT_ID`
* The **redirect URI** you configured in the PKCE setup for `ZITADEL_CALLBACK_URL` (must match exactly)
* The **post-logout redirect URI** for `ZITADEL_POST_LOGOUT_URL`
* A strong random string for `SESSION_SECRET` (generate using: `python -c "import secrets; print(secrets.token_hex(32))"`)
* A randomly generated string for `ZITADEL_CLIENT_SECRET` (generate using: `python -c "import secrets; print(secrets.token_hex(32))"`)
3. Install dependencies using [Poetry](https://python-poetry.org) with `poetry install` and start the development server with `poetry run python run.py` to verify the authentication flow end-to-end.
***
Learn more and resources [#learn-more-and-resources]
* [PKCE concept](https://oauth.net/2/pkce/)
* [Federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
* [OIDC integration guide](https://zitadel.com/docs/guides/integrate/login/oidc/login-users)
* [Flask documentation](https://flask.palletsprojects.com)
* [Authlib documentation](https://authlib.org)
* [Example repository](https://github.com/zitadel/example-auth-flask)
# Go
Go is an open-source, compiled programming language that is known for its simplicity, efficiency, and concurrency capabilities.
Get started integrating authentication to your Go Application by checking out our zitadel-go SDK.
Resources [#resources]
* [Example App Repository](https://github.com/zitadel/zitadel-go)
* [Go SDK](https://github.com/zitadel/zitadel-go)
* [Web APP Step-By-Step Guide](/examples/login/go)
* [API APP Step-By-Step Guide](/examples/secure-api/go)
* [Go OIDC Library](https://github.com/zitadel/oidc)
Go SDK [#go-sdk]
The [zitadel-go](https://github.com/zitadel/zitadel-go) SDK is a wrapper around the [zitadel/oidc](https://github.com/zitadel/oidc) to integrate Login into your Web App and abstracts the handling of specific settings for ZITADEL.
Additionally secure your business APIs and handle permission checks for your users.
Last part is the integration of the ZITADEL APIs to handle user and resource management.
The following features are covered by the SDK:
* Authentication in your Web App
* Authenticate your user with ZITADEL using OIDC
* Requesting ZITADEL userinfo endpoint to get user data
* Refresh Token
* Requesting User Roles from userinfo
* Check if user has specified role
* Logout
* Secure your APIs
* Authorization Check using OAuth2 Introspection
* Check User Roles on Endpoint
* Manage Resources through ZITADEL APIs
* Authenticate Service Account
* Generated gRPC Clients for integrating ZITADEL API
* User, Organization, Project, etc. Management
The goal is to extend the SDK over the time with the following features:
* Build your own login UI using our Session API
Go Examples [#go-examples]
You can find different examples for building your Go application in the following package of the repository:
[zitadel-go/example](https://github.com/zitadel/zitadel-go/tree/next/example)
Web Application Example [#web-application-example]
What does the Web Application Example include:
* Home Page with Login Button
* Authenticating user with OIDC PKCE Flow
* Public Page: Accessible without authentication
* Private Page: Shows user information of authenticated user, only accessible after login
* Logout
[Example Web App](https://github.com/zitadel/zitadel-go/tree/next/example/app)
API Application Example [#api-application-example]
What does the API Application Example include:
* REST API Application secured with Spring Security and OAuth2
* Public Endpoint: Accessible without authentication
* Private Endpoint: Accessible with a token
* Administrator Endpoint: Accessible with a token of a user with an administrator role
[Example API App](https://github.com/zitadel/zitadel-go/tree/next/example/api/http)
Step-By-Step Guide [#step-by-step-guide]
For Go, we do have two different Step-By-Step Guides.
One to create your web application with integrated login and one to create your API with permission checks for calling users.
The guides lead you through the whole process from configuring the right application in ZITADEL to a ready application with integrated login or authentication checks.
Web Application Guide [#web-application-guide]
After completing the Step-By-Step Guide you will have:
1. Example Web Application with integrated ZITADEL Login
2. Example page accessible by authenticated user showing retrieved user information
3. Logout
4. Correct setup for your application in ZITADEL
[Web APP Step-By-Step Guide](/examples/login/go)
API Application Guide [#api-application-guide]
After completing the Step-By-Step Guide you will have:
1. Example REST API checking tokens against ZITADEL with OAuth2
2. Public Endpoint accessible by any user
3. Private Endpoint accessible by authenticated user
4. Private Endpoint accessible by user with role 'admin'
5. Correct setup for your application in ZITADEL
[API APP Step-By-Step Guide](/examples/secure-api/go)
# Hono
Overview [#overview]
[Hono](https://hono.dev) is a small, simple, and ultra-fast web framework for the Edges that works on Cloudflare Workers, Fastly Compute, Deno, Bun, Vercel, Netlify, AWS Lambda, Lambda\@Edge, and Node.js. This example demonstrates how to integrate **Zitadel** using the **[OAuth 2.0 PKCE flow](https://oauth.net/2/pkce/)** to authenticate users securely and maintain sessions across your Hono application.
Auth library [#auth-library]
This example uses **[Auth.js](https://authjs.dev)** (formerly NextAuth.js), the standard authentication library for modern web frameworks. Auth.js implements the [OpenID Connect](https://openid.net/connect/) (OIDC) protocol with [PKCE](https://oauth.net/2/pkce/), performs secure token exchange, and provides session management helpers. The integration uses **[@hono/auth-js](https://www.npmjs.com/package/@hono/auth-js)**, the official Hono adapter for Auth.js, along with **[@auth/core](https://www.npmjs.com/package/@auth/core)** which handles the core OIDC flows and the **[Zitadel provider](https://authjs.dev/reference/core/providers/zitadel)**.
***
What this example demonstrates [#what-this-example-demonstrates]
This example showcases a complete web application authentication pattern with [Hono](https://hono.dev) and [Zitadel](https://zitadel.com/docs). Users start on a public landing page, initiate authentication with Zitadel using the secure [PKCE flow](https://oauth.net/2/pkce/), and are redirected to a protected profile page displaying their [user information](https://zitadel.com/docs/apis/openidoauth/claims) after successful authentication. The application demonstrates several key features including sign-in with [authorization code flow + PKCE](https://oauth.net/2/pkce/), OAuth callback handling with secure [session storage](https://authjs.dev/concepts/session-strategies) using [JWT strategy](https://authjs.dev/concepts/session-strategies#jwt-session), route protection through [authentication middleware](https://hono.dev/docs/guides/middleware) that guards sensitive pages, automatic [token refresh](https://authjs.dev/guides/refresh-token-rotation) to maintain long-lived sessions without requiring re-authentication, access to user profile information including [OIDC standard claims](https://zitadel.com/docs/apis/openidoauth/claims) and [Zitadel-specific claims](https://zitadel.com/docs/apis/openidoauth/claims#reserved-claims) such as organization roles and metadata, and secure sign-out with [federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout) that terminates both the local session and the Zitadel session through the end-session endpoint.
The implementation uses [Handlebars templates](https://handlebarsjs.com/) for server-side rendering, [@hono/node-server](https://www.npmjs.com/package/@hono/node-server) for running on Node.js, secure [HTTP-only cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#security) for session management, and [CSRF protection](https://authjs.dev/concepts/session-strategies#csrf-protection) through Auth.js built-in tokens. The authentication flow follows OAuth 2.0 best practices with [Proof Key for Code Exchange](https://zitadel.com/docs/guides/integrate/login/oidc/oauth-recommended-flows) to prevent authorization code interception attacks.
***
Getting started [#getting-started]
Prerequisites [#prerequisites]
Before running this example, you need to create and configure a PKCE application in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview). Follow the PKCE application setup guide to:
1. Create a new Web application in your [Zitadel project](https://zitadel.com/docs/guides/manage/console/projects-overview)
2. Configure it to use the PKCE authentication method
3. Set up your redirect URIs (e.g., `http://localhost:3000/auth/callback/zitadel` for development)
4. Configure post-logout redirect URIs (e.g., `http://localhost:3000/auth/logout/callback`)
5. Copy your **Client ID** for use in the next steps
> **Note:** Make sure to enable **Dev Mode** in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview) if you're using HTTP URLs during local development. For production, always use HTTPS URLs and disable Dev Mode.
Run the example [#run-the-example]
Once you have your Zitadel application configured:
1. Clone the [repository](https://github.com/zitadel/example-auth-hono).
2. Create a `.env` file (copy from `.env.example`) and configure it with the values from your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview). **Use the exact environment variable names from the repository:**
```
NODE_ENV=development
PORT=3000
SESSION_SECRET=your-very-secret-and-strong-session-key
SESSION_DURATION=3600
ZITADEL_DOMAIN=https://your-instance.zitadel.cloud
ZITADEL_CLIENT_ID=your_client_id_from_console
ZITADEL_CLIENT_SECRET=
ZITADEL_CALLBACK_URL=http://localhost:3000/auth/callback/zitadel
ZITADEL_POST_LOGIN_URL=/profile
ZITADEL_POST_LOGOUT_URL=http://localhost:3000/auth/logout/callback
```
Replace these values with:
* Your actual **Zitadel instance URL** (the Issuer from your [instance settings](https://zitadel.com/docs/guides/manage/console/default-settings))
* The **Client ID** you copied when creating the application in the PKCE setup
* The **redirect URI** you configured in the PKCE setup (must match exactly)
* The **post-logout redirect URI** for handling logout callbacks
* A strong random **session secret** (generate using: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`)
> **Note:** While PKCE doesn't require a client secret for public clients, Auth.js requires a value for internal settings. You can leave `ZITADEL_CLIENT_SECRET` empty or provide a random string.
3. Install dependencies using [npm](https://www.npmjs.com) and start the development server:
```bash
npm install
npm run dev
```
The application will be running at `http://localhost:3000`. Visit the homepage to test the authentication flow.
***
Learn more and resources [#learn-more-and-resources]
* [PKCE concept](https://oauth.net/2/pkce/)
* [Federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
* [OIDC integration guide](https://zitadel.com/docs/guides/integrate/login/oidc/login-users)
* [OAuth recommended flows](https://zitadel.com/docs/guides/integrate/login/oidc/oauth-recommended-flows)
* [Framework docs](https://hono.dev)
* [Auth.js](https://authjs.dev)
* [Auth.js Hono adapter](https://www.npmjs.com/package/@hono/auth-js)
* [Example repository](https://github.com/zitadel/example-auth-hono)
# Examples and SDKs for ZITADEL
You can integrate Zitadel quickly into your application and be up and running within minutes.
To achieve your goals as fast as possible, we provide you with SDKs, Example Repositories and Guides.
The SDKs and integration depend on the framework and language you are using.
In addition to our officially maintained examples, we also list community-contributed implementations.
These examples are provided by external developers and are not maintained by us.
While we believe they can be valuable resources and showcase diverse approaches, we cannot guarantee their completeness, functionality, or continued support.
If you encounter issues with a community-contributed example, please contact the respective maintainers directly.
We provide this list for informational purposes and to foster community engagement, but we do not assume responsibility for these external implementations.
Management Clients [#management-clients]
Automate and manage your ZITADEL instance programmatically. These clients are built for Machine-to-Machine (M2M) communication, allowing your backend services to perform administrative tasks without human intervention.
ZITADEL SDKs [#zitadel-sd-ks]
ZITADEL SDKs are purpose-built to provide a deep, idiomatic integration into your specific framework.
While standard OIDC libraries handle basic authentication, Zitadel SDKs act as a powerful abstraction layer, streamlining complex identity tasks and providing direct access to the ZITADEL core platform.
OIDC Libraries [#oidc-libraries]
OIDC is a standard for authentication and most languages and frameworks do provide a OIDC library which can be easily integrated to your application.
If we do not provide a specific example, SDK or guide, we strongly recommend using existing authentication libraries for your
language or framework instead of building your own.
Certified libraries have undergone rigorous testing and validation to ensure high security and reliability.
There are many recommended libraries available, this saves time and ensures that users' data is well-protected.
Example Applications [#example-applications]
* [B2B customer portal](https://github.com/zitadel/zitadel-nextjs-b2b): Showcase the use of personal access tokens in a B2B environment. Uses Next.js Framework.
* [Introspection](https://github.com/zitadel/examples-api-access-and-token-introspection): Python examples for securing an API and invoking it as a service account
* [Fine-grained authorization](https://github.com/zitadel/example-fine-grained-authorization): Leverage actions, custom metadata, and claims for attribute-based access control
Search for the "example" tag in our repository to [explore all examples](https://github.com/search?q=topic%3Aexamples+org%3Azitadel\&type=repositories).
Missing Management Client [#missing-management-client]
Is your language/framework missing? Fear not, you can generate your gRPC API Client with ease.
1. Make sure to install [buf](https://buf.build/docs/installation/)
2. Create a `buf.gen.yaml` and configure the [plugins](https://buf.build/plugins) you need
3. Run `buf generate https://github.com/zitadel/zitadel#format=git,tag=v2.23.1` (change the versions to your needs)
Let us make an example with Ruby. Any other supported language by buf will work as well. Consult
the [buf plugin registry](https://buf.build/plugins) for more ideas.
Example with Ruby [#example-with-ruby]
With gRPC, we usually need to generate the client stub and the messages/types. This is why we need two plugins.
The plugin `grpc/ruby` generates the client stub and the plugin `protocolbuffers/ruby` takes care of the messages/types.
```yaml
version: v1
plugins:
- plugin: buf.build/grpc/ruby
out: gen
- plugin: buf.build/protocolbuffers/ruby
out: gen
```
If you now run `buf generate https://github.com/zitadel/zitadel#format=git,tag=v2.23.1` in the folder where
your `buf.gen.yaml` is located you should see the folder `gen` appear.
If you run `ls -la gen/zitadel/` you should see something like this:
```bash
ffo@ffo-pc:~/git/zitadel/ruby$ ls -la gen/zitadel/
total 704
drwxr-xr-x 2 ffo ffo 4096 Apr 11 16:49 .
drwxr-xr-x 3 ffo ffo 4096 Apr 11 16:49 ..
-rw-r--r-- 1 ffo ffo 4397 Apr 11 16:49 action_pb.rb
-rw-r--r-- 1 ffo ffo 141097 Apr 11 16:49 admin_pb.rb
-rw-r--r-- 1 ffo ffo 25151 Apr 11 16:49 admin_services_pb.rb
-rw-r--r-- 1 ffo ffo 6537 Apr 11 16:49 app_pb.rb
-rw-r--r-- 1 ffo ffo 1134 Apr 11 16:49 auth_n_key_pb.rb
-rw-r--r-- 1 ffo ffo 32881 Apr 11 16:49 auth_pb.rb
-rw-r--r-- 1 ffo ffo 6896 Apr 11 16:49 auth_services_pb.rb
-rw-r--r-- 1 ffo ffo 1571 Apr 11 16:49 change_pb.rb
-rw-r--r-- 1 ffo ffo 2488 Apr 11 16:49 event_pb.rb
-rw-r--r-- 1 ffo ffo 14782 Apr 11 16:49 idp_pb.rb
-rw-r--r-- 1 ffo ffo 5031 Apr 11 16:49 instance_pb.rb
-rw-r--r-- 1 ffo ffo 223348 Apr 11 16:49 management_pb.rb
-rw-r--r-- 1 ffo ffo 44402 Apr 11 16:49 management_services_pb.rb
-rw-r--r-- 1 ffo ffo 3020 Apr 11 16:49 member_pb.rb
-rw-r--r-- 1 ffo ffo 855 Apr 11 16:49 message_pb.rb
-rw-r--r-- 1 ffo ffo 1445 Apr 11 16:49 metadata_pb.rb
-rw-r--r-- 1 ffo ffo 2370 Apr 11 16:49 object_pb.rb
-rw-r--r-- 1 ffo ffo 621 Apr 11 16:49 options_pb.rb
-rw-r--r-- 1 ffo ffo 4425 Apr 11 16:49 org_pb.rb
-rw-r--r-- 1 ffo ffo 8538 Apr 11 16:49 policy_pb.rb
-rw-r--r-- 1 ffo ffo 8223 Apr 11 16:49 project_pb.rb
-rw-r--r-- 1 ffo ffo 1022 Apr 11 16:49 quota_pb.rb
-rw-r--r-- 1 ffo ffo 5872 Apr 11 16:49 settings_pb.rb
-rw-r--r-- 1 ffo ffo 20985 Apr 11 16:49 system_pb.rb
-rw-r--r-- 1 ffo ffo 4784 Apr 11 16:49 system_services_pb.rb
-rw-r--r-- 1 ffo ffo 28759 Apr 11 16:49 text_pb.rb
-rw-r--r-- 1 ffo ffo 24170 Apr 11 16:49 user_pb.rb
-rw-r--r-- 1 ffo ffo 13568 Apr 11 16:49 v1_pb.rb
```
Import these files into your project to start interacting with Zitadel's APIs.
# Java Spring Boot
Java is a general-purpose programming language designed for object-oriented programming.
Spring Security is used to protect your applications from unauthorized access, protect sensitive data, and enforce access control policies.
Get started integrating authentication to your Java Web App or API by checking out our zitadel-java Example
Resources [#resources]
* [Example App Repository with Spring Security](https://github.com/zitadel/zitadel-java)
* [Example Web App with Spring Security](https://github.com/zitadel/zitadel-java/tree/main/web)
* [Example API App with Spring Security](https://github.com/zitadel/zitadel-java/tree/main/api)
* [Web APP Step-By-Step Guide](/examples/login/java-spring)
* [API APP Step-By-Step Guide](/examples/secure-api/java-spring)
Java SDK [#java-sdk]
Java Spring Security is a widely used and common framework to integrate Authentication and Authorization into your Applications.
As of this at the moment there is no specific ZITADEL SDK, but we do show you how to integrate ZITADEL with Java Spring Security.
You can use this for both your Web as for your API Applications.
The following features are covered by Java Spring Security:
* Authenticate your user using OIDC
* Requesting ZITADEL userinfo endpoint to get user data
* Refresh Token
* Requesting User Roles from userinfo
* Check if user has specified role
* Logout
The goal is to have a ZITADEL Java SDK in the future which will cover the following:
* Wrapper around Java Spring Security
* Authentication with OIDC
* Authorization and checking Roles
* Integrate ZITADEL APIs to read and manage resources
* Integrate ZITADEL Session API to create your own login UI
Java Examples [#java-examples]
Web Application Example [#web-application-example]
What does the Web Application Example include:
* Home Page with Login Button
* Authenticating user with OIDC PKCE Flow
* Public Page: Accessible without authentication
* Private Page: Shows user information of authenticated user, only accessible after login
* Task Page: Only accessible after login and uses the API example. Requires the administrator role for the application for some interaction.
* Logout
[Example Web App with Spring Security](https://github.com/zitadel/zitadel-java/web)
API Application Example [#api-application-example]
What does the API Application Example include:
* REST API Application secured with Spring Security and OAuth2
* Public Endpoint: Accessible without authentication
* Private Endpoint: Accessible with a token
* Administrator Endpoint: Accessible with a token of a user with an administrator role
[Example API App with Spring Security](https://github.com/zitadel/zitadel-java/api)
Step-By-Step Guide [#step-by-step-guide]
For Java Spring we do have two different Step-By-Step Guides.
One to create your web application with integrated login and one to create your API with permission checks for calling users.
The guides lead you through the whole process from configuring the right application in ZITADEL to a ready application with integrated login or authentication checks.
Web Application Guide [#web-application-guide]
After completing the Step-By-Step Guide, you will have:
1. Example Web Application with integrated ZITADEL Login
2. Example page accessible by authenticated user showing retrieved user information
3. Example page accessible by an authenticated user showing a task list
* Task list can be read by an authenticated user
* New tasks can be created by a user with an administrator role
4. Logout
5. Correct setup for your application in ZITADEL
[Web APP Step-By-Step Guide](/examples/login/java-spring)
API Application Guide [#api-application-guide]
After completing the Step-By-Step Guide, you will have:
1. Example REST API checking tokens against ZITADEL with OAuth2
2. Public Endpoint accessible by any user
3. Private Endpoint accessible by authenticated user
4. Private Endpoint accessible by the user with the role 'admin'
5. Correct setup for your application in ZITADEL
[API APP Step-By-Step Guide](/examples/secure-api/java-spring)
# Laravel
Overview [#overview]
[Laravel](https://laravel.com) is a web application framework with expressive, elegant syntax. It provides a robust set of features for web applications, making it one of the most popular choices for building server-side applications. This example demonstrates how to integrate **[Zitadel](https://zitadel.com/docs)** using the **[OAuth 2.0 PKCE flow](https://oauth.net/2/pkce/)** to authenticate users securely and maintain sessions across your application.
Auth library [#auth-library]
This example uses **[Laravel Socialite](https://laravel.com/docs/socialite)**, the standard authentication library for Laravel applications. Laravel Socialite implements the [OpenID Connect (OIDC)](https://zitadel.com/docs/apis/openidoauth/endpoints) flow through a custom Zitadel provider, manages [PKCE](https://oauth.net/2/pkce/), performs secure token exchange, and provides session management through Laravel's authentication system.
***
What this example demonstrates [#what-this-example-demonstrates]
This example shows a complete authentication implementation using [Laravel](https://laravel.com) with [Zitadel](https://zitadel.com/docs). Users start on a public landing page, click a login button to authenticate with Zitadel using the secure [PKCE flow](https://oauth.net/2/pkce/), and are redirected to a protected profile page displaying their [user information](https://zitadel.com/docs/apis/openidoauth/claims) after successful authentication.
The application implements server-side session management with Laravel's built-in session handling, storing authentication state securely in encrypted cookies. Protected routes use Laravel middleware to automatically redirect unauthenticated users to the sign-in flow, ensuring only authenticated users can access sensitive areas. The profile page displays comprehensive user information including [OIDC claims](https://zitadel.com/docs/apis/openidoauth/claims) and [session metadata](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint).
The application demonstrates proper **[federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)** by terminating sessions both locally and with Zitadel's [end-session endpoint](https://zitadel.com/docs/guides/integrate/login/oidc/logout), complete with CSRF protection using state parameters. Additionally, it includes automatic [token refresh](https://zitadel.com/docs/apis/openidoauth/endpoints) using [refresh tokens](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) to maintain long-lived sessions without requiring users to re-authenticate. The example uses [Zitadel-specific scopes](https://zitadel.com/docs/apis/openidoauth/scopes) like `urn:zitadel:iam:user:metadata` and `urn:zitadel:iam:org:projects:roles` to access extended user attributes and role information for implementing [role-based access control (RBAC)](https://zitadel.com/docs/guides/manage/console/roles).
***
Getting started [#getting-started]
Prerequisites [#prerequisites]
Before running this example, you need to create and configure a PKCE application in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview). Follow the PKCE application setup guide to:
1. Create a new Web application in your [Zitadel project](https://zitadel.com/docs/guides/manage/console/projects-overview)
2. Configure it to use the PKCE authentication method
3. Set up your redirect URIs (e.g., `http://localhost:3000/auth/callback` for development)
4. Configure post-logout redirect URIs (e.g., `http://localhost:3000/auth/logout/callback`)
5. Copy your **Client ID** for use in the next steps
6. Optionally enable [refresh tokens](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) in Token Settings for long-lived sessions
> **Note:** Make sure to enable **Dev Mode** in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview) if you're using HTTP URLs during local development. For production, always use HTTPS URLs and disable Dev Mode.
Run the example [#run-the-example]
Once you have your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview) configured:
1. Clone the [repository](https://github.com/zitadel/example-auth-laravel).
2. Create a `.env` file (copy from `.env.example`) and configure it with the values from your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview). **Use these exact environment variable names:**
```
APP_KEY=your-app-key
APP_ENV=local
APP_DEBUG=true
SERVER_URL=http://localhost:3000
SERVER_PORT=3000
DB_CONNECTION=sqlite
ZITADEL_DOMAIN=https://your-zitadel-domain
ZITADEL_CLIENT_ID=your-zitadel-application-client-id
ZITADEL_CLIENT_SECRET=your-randomly-generated-client-secret
ZITADEL_POST_LOGOUT_URL=http://localhost:3000/auth/logout/callback
```
Replace these values with:
* Your actual [Zitadel instance URL](https://zitadel.com/docs/guides/manage/console/default-settings) for `ZITADEL_DOMAIN` (the issuer)
* The **Client ID** you copied when creating the application for `ZITADEL_CLIENT_ID`
* The **post-logout redirect URI** for `ZITADEL_POST_LOGOUT_URL`
* A strong random string for `APP_KEY` (generate using: `php artisan key:generate`)
* A randomly generated string for `ZITADEL_CLIENT_SECRET` (generate using: `php -r "echo bin2hex(random_bytes(32));"`)
3. Install dependencies using [Composer](https://getcomposer.org) with `composer install` and start the development server with `composer run dev` to verify the authentication flow end-to-end.
***
Learn more and resources [#learn-more-and-resources]
* [PKCE concept](https://oauth.net/2/pkce/)
* [Federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
* [OIDC integration guide](https://zitadel.com/docs/guides/integrate/login/oidc/login-users)
* [Laravel documentation](https://laravel.com/docs)
* [Laravel Socialite documentation](https://laravel.com/docs/socialite)
* [Example repository](https://github.com/zitadel/example-auth-laravel)
# NestJS
Overview [#overview]
[NestJS](https://nestjs.com) is a progressive Node.js framework for building efficient, reliable, and scalable server-side applications using [TypeScript](https://www.typescriptlang.org). This example demonstrates how to integrate **Zitadel** using the **[OAuth 2.0 PKCE flow](https://oauth.net/2/pkce/)** to authenticate users securely and maintain sessions across your NestJS application.
Auth library [#auth-library]
This example uses **[@auth/core](https://www.npmjs.com/package/@auth/core)**, the core authentication library powering [Auth.js](https://authjs.dev), which implements the [OpenID Connect (OIDC)](https://openid.net/connect/) flow, manages [PKCE](https://oauth.net/2/pkce/), performs token exchange, and exposes helpers for [session state](https://zitadel.com/docs/guides/integrate/login/oidc/login-users). The example integrates this through the **[@mridang/nestjs-auth](https://www.npmjs.com/package/@mridang/nestjs-auth)** [NestJS module](https://docs.nestjs.com/modules), which provides [decorators](https://docs.nestjs.com/custom-decorators), [guards](https://docs.nestjs.com/guards), and middleware for seamless authentication within the NestJS ecosystem.
***
What this example demonstrates [#what-this-example-demonstrates]
This example implements a complete authentication flow using [PKCE](https://oauth.net/2/pkce/) with [Zitadel](https://zitadel.com/docs) as the identity provider. Users begin on a public landing page and click a login button to authenticate through Zitadel's authorization server. After successful authentication, they're redirected to a protected profile page displaying their user information retrieved from the [ID token](https://oauth.net/2/pkce/) with [Zitadel](https://zitadel.com/docs) as the identity provider. Users begin on a public landing page and click a login button to authenticate through Zitadel's authorization server. After successful authentication, they're redirected to a protected profile page displaying their user information retrieved from the [ID token](https://zitadel.com/docs/apis/openidoauth/claims) and [access token](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint).
The application leverages [NestJS controllers](https://docs.nestjs.com/controllers) to handle authentication routes and [@mridang/nestjs-auth guards](https://docs.nestjs.com/controllers) to handle authentication routes and [@mridang/nestjs-auth guards](https://www.npmjs.com/package/@mridang/nestjs-auth) to protect routes requiring authentication. [Session management](https://docs.nestjs.com/controllers) to handle authentication routes and [@mridang/nestjs-auth guards](https://www.npmjs.com/package/@mridang/nestjs-auth) to protect routes requiring authentication. [Session management](https://zitadel.com/docs/guides/integrate/login/oidc/login-users) is handled through encrypted [JWT-based sessions](https://docs.nestjs.com/controllers) to handle authentication routes and [@mridang/nestjs-auth guards](https://www.npmjs.com/package/@mridang/nestjs-auth) to protect routes requiring authentication. [Session management](https://zitadel.com/docs/guides/integrate/login/oidc/login-users) is handled through encrypted [JWT-based sessions](https://authjs.dev/concepts/session-strategies#jwt-session) stored in secure, HTTP-only cookies. The example includes automatic [token refresh](https://docs.nestjs.com/controllers) to handle authentication routes and [@mridang/nestjs-auth guards](https://www.npmjs.com/package/@mridang/nestjs-auth) to protect routes requiring authentication. [Session management](https://zitadel.com/docs/guides/integrate/login/oidc/login-users) is handled through encrypted [JWT-based sessions](https://authjs.dev/concepts/session-strategies#jwt-session) stored in secure, HTTP-only cookies. The example includes automatic [token refresh](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) functionality using [refresh tokens](https://oauth.net/2/pkce/), ensuring users maintain their sessions without interruption when access tokens expire.
The logout implementation demonstrates [federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout) by redirecting users to Zitadel's end-session endpoint, terminating both the local application session and the Zitadel session. [CSRF protection](https://zitadel.com/docs/guides/integrate/login/oidc/logout) by redirecting users to Zitadel's end-session endpoint, terminating both the local application session and the Zitadel session. [CSRF protection](https://owasp.org/www-community/attacks/csrf) during logout is achieved through a state parameter validated in the callback. The example also showcases accessing Zitadel's [UserInfo endpoint](https://zitadel.com/docs/apis/openidoauth/endpoints#userinfo_endpoint) to fetch real-time user data, including [custom claims](https://oauth.net/2/pkce/) with [Zitadel](https://zitadel.com/docs) as the identity provider. Users begin on a public landing page and click a login button to authenticate through Zitadel's authorization server. After successful authentication, they're redirected to a protected profile page displaying their user information retrieved from the [ID token](https://zitadel.com/docs/apis/openidoauth/claims), [roles](https://zitadel.com/docs/guides/integrate/login/oidc/logout) by redirecting users to Zitadel's end-session endpoint, terminating both the local application session and the Zitadel session. [CSRF protection](https://owasp.org/www-community/attacks/csrf) during logout is achieved through a state parameter validated in the callback. The example also showcases accessing Zitadel's [UserInfo endpoint](https://zitadel.com/docs/apis/openidoauth/endpoints#userinfo-endpoint) to fetch real-time user data, including [custom claims](https://zitadel.com/docs/apis/openidoauth/claims), [roles](https://zitadel.com/docs/guides/manage/console/roles), and organization membership.
All protected routes are secured using the [@mridang/nestjs-auth global guard](https://www.npmjs.com/package/@mridang/nestjs-auth), with the `@Public()` decorator marking routes that don't require authentication. The application uses [Handlebars templates](https://handlebarsjs.com) for server-side rendering and [Tailwind CSS](https://tailwindcss.com) for styling, providing a complete reference implementation for NestJS developers.
***
Getting started [#getting-started]
Prerequisites [#prerequisites]
Before running this example, you need to create and configure a PKCE application in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview). Follow the PKCE application setup guide to:
1. Create a new Web application in your [Zitadel project](https://zitadel.com/docs/guides/manage/console/projects-overview)
2. Configure it to use the PKCE authentication method
3. Set up your redirect URIs (e.g., `http://localhost:3000/auth/callback/zitadel` for development)
4. Configure post-logout redirect URIs (e.g., `http://localhost:3000/auth/logout/callback`)
5. Copy your **Client ID** for use in the next steps
> **Note:** Make sure to enable **Dev Mode** in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview) if you're using HTTP URLs during local development. For production, always use HTTPS URLs and disable Dev Mode.
Run the example [#run-the-example]
Once you have your Zitadel application configured:
1. Clone the [repository](https://github.com/zitadel/example-auth-nestjs).
2. Create a `.env` file in the project root and configure it with the values from your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview). **Use the exact environment variable names from the repository:**
```
NODE_ENV=development
PORT=3000
SESSION_SECRET=your-very-secret-and-strong-session-key
SESSION_SALT=your-cryptographic-salt
SESSION_DURATION=3600
ZITADEL_DOMAIN=https://your-instance.zitadel.cloud
ZITADEL_CLIENT_ID=your_client_id_from_console
ZITADEL_CLIENT_SECRET=your_randomly_generated_secret
ZITADEL_CALLBACK_URL=http://localhost:3000/auth/callback/zitadel
ZITADEL_POST_LOGIN_URL=/profile
ZITADEL_POST_LOGOUT_URL=http://localhost:3000/auth/logout/callback
```
Replace these values with:
* Your actual Zitadel instance URL (the **Issuer**)
* The **Client ID** you copied when creating the application
* A randomly generated **Client Secret** (generate using: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`)
* A secure **Session Secret** (generate using the same command)
* A cryptographic **Session Salt** for cookie encryption
* The **redirect URI** you configured in the PKCE setup (must match exactly)
* The **post-logout redirect URI** for the logout callback
3. Install dependencies using [npm](https://www.npmjs.com) and start the development server:
```bash
npm install
npm run dev
```
The application will be running at `http://localhost:3000`. Visit the URL to verify the authentication flow end-to-end.
***
Learn more and resources [#learn-more-and-resources]
* [PKCE concept](https://oauth.net/2/pkce/)
* [Federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
* [OIDC integration guide](https://zitadel.com/docs/guides/integrate/login/oidc/login-users)
* [Framework docs](https://nestjs.com)
* [Auth library (@auth/core)](https://www.npmjs.com/package/@auth/core)
* [NestJS auth module](https://www.npmjs.com/package/@mridang/nestjs-auth)
* [Example repository](https://github.com/zitadel/example-auth-nestjs)
# Next.js
Overview [#overview]
[Next.js](https://nextjs.org) is a React framework for building full-stack web applications. This example demonstrates how to integrate **Zitadel** using the **[OAuth 2.0 PKCE flow](https://oauth.net/2/pkce/)** to authenticate users securely and maintain sessions across your Next.js application.
Auth library [#auth-library]
This example uses **[next-auth](https://www.npmjs.com/package/next-auth)** with **[openid-client](https://www.npmjs.com/package/openid-client)** to implement the [OpenID Connect (OIDC)](https://zitadel.com/docs/apis/openidoauth/endpoints) protocol, manage [PKCE](https://oauth.net/2/pkce/) code challenge generation, perform secure token exchange, and provide session management helpers.
***
What this example demonstrates [#what-this-example-demonstrates]
This Next.js application demonstrates a secure authentication flow using Zitadel with the industry-standard [PKCE flow](https://oauth.net/2/pkce/). Users start on a public landing page, initiate login via the Zitadel authorization server, and after successful authentication are redirected back to a protected profile page displaying their [user information and claims](https://zitadel.com/docs/apis/openidoauth/claims).
The application implements session management using NextAuth.js with a JWT-based session strategy. It includes automatic callback handling with secure token exchange, route protection for authenticated-only pages, and an example of requesting additional [OIDC scopes](https://zitadel.com/docs/apis/openidoauth/scopes) (including `offline_access` for refresh tokens and Zitadel-specific scopes for metadata and roles).
The logout flow implements **[federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)** via the end-session endpoint, terminating both the local session and the Zitadel session before redirecting the user back to the application. It also demonstrates automatic token refresh to maintain long-lived sessions without requiring users to re-authenticate.
***
Getting started [#getting-started]
Prerequisites [#prerequisites]
Before running this example, you need to create and configure a PKCE application in the [Zitadel Console](https://zitadel.com/docs/guides/manage/console/console-overview). Follow the PKCE application setup guide to:
1. Create a new Web application in your [Zitadel project](https://zitadel.com/docs/guides/manage/console/projects-overview)
2. Configure it to use the PKCE authentication method
3. Set up your redirect URIs (e.g., `http://localhost:3000/api/auth/callback/zitadel` for development)
4. Configure post-logout redirect URIs (e.g., `http://localhost:3000/api/auth/logout/callback`)
5. Copy your **Client ID** for use in the next steps
6. Optionally enable [refresh tokens](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) in Token Settings for long-lived sessions
> **Note:** Make sure to enable **Dev Mode** in the [Zitadel Console](https://zitadel.com/docs/guides/manage/console/console-overview) if you're using HTTP URLs during local development. For production deployments, always use HTTPS URLs and disable Dev Mode.
Run the example [#run-the-example]
Once you have your [Zitadel application configured](https://zitadel.com/docs/guides/integrate/login/oidc/login-users):
1. Clone the [repository](https://github.com/zitadel/example-auth-nextjs).
2. Create a `.env.local` file (copy from `.env.example`) and configure it with the values from your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview). Use these environment variable names exactly as shown:
```
NODE_ENV=development
PORT=3000
SESSION_SECRET=your-very-secret-and-strong-session-key
SESSION_DURATION=3600
ZITADEL_DOMAIN=https://your-zitadel-domain
ZITADEL_CLIENT_ID=your-zitadel-application-client-id
ZITADEL_CLIENT_SECRET=your-randomly-generated-client-secret
ZITADEL_CALLBACK_URL=http://localhost:3000/api/auth/callback/zitadel
ZITADEL_POST_LOGIN_URL=/profile
ZITADEL_POST_LOGOUT_URL=http://localhost:3000/api/auth/logout/callback
NEXTAUTH_URL=http://localhost:3000
```
Replace these values with:
* Your actual [Zitadel instance URL](https://zitadel.com/docs/guides/manage/console/default-settings) (the **ZITADEL\_DOMAIN**)
* The **Client ID** you copied when creating the application
* A randomly generated **SESSION\_SECRET** using: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`
* A randomly generated **ZITADEL\_CLIENT\_SECRET** (required by Auth.js / NextAuth.js even though PKCE doesn't require it)
* The **redirect URIs** you configured in the PKCE setup (must match exactly)
3. Install dependencies using [npm](https://www.npmjs.com) by running `npm install`, then start the development server with `npm run dev` to verify the authentication flow end-to-end at `http://localhost:3000`.
***
Learn more and resources [#learn-more-and-resources]
* [PKCE concept](https://oauth.net/2/pkce/)
* [Federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
* [OIDC integration guide](https://zitadel.com/docs/guides/integrate/login/oidc/login-users)
* [Next.js documentation](https://nextjs.org)
* [NextAuth.js documentation](https://next-auth.js.org/)
* [Example repository](https://github.com/zitadel/example-auth-nextjs)
# Nuxt.js
Overview [#overview]
[Nuxt.js](https://nuxt.com) is the intuitive [Vue.js](https://vuejs.org) framework that enables you to create full-stack web applications with server-side rendering, file-based routing, and auto-imports. This example demonstrates how to integrate **Zitadel** using the **[OAuth 2.0 PKCE flow](https://oauth.net/2/pkce/)** to authenticate users securely and maintain sessions across your application.
Auth library [#auth-library]
This example uses **[@sidebase/nuxt-auth](https://www.npmjs.com/package/@sidebase/nuxt-auth)**, a Nuxt module that wraps **[NextAuth.js/Auth.js](https://next-auth.js.org)** for seamless authentication integration. Under the hood, it leverages **[next-auth](https://www.npmjs.com/package/next-auth)** with the **[openid-client](https://www.npmjs.com/package/openid-client)** library to implement the [OpenID Connect (OIDC)](https://zitadel.com/docs/apis/openidoauth/endpoints) protocol, manage [PKCE](https://oauth.net/2/pkce/), perform token exchange, and handle [session management](https://authjs.dev/concepts/session-strategies).
***
What this example demonstrates [#what-this-example-demonstrates]
This [Nuxt.js](https://nuxt.com) application showcases a complete authentication pattern using [Zitadel](https://zitadel.com/docs) with the **[PKCE flow](https://oauth.net/2/pkce/)**. Users begin on a public landing page where they can initiate sign-in with Zitadel through the **[@sidebase/nuxt-auth](https://www.npmjs.com/package/@sidebase/nuxt-auth)** module. After successful authentication, they're redirected to a protected profile page displaying their [user information and claims](https://zitadel.com/docs/apis/openidoauth/claims).
The example implements secure [session management](https://authjs.dev/concepts/session-strategies) using [JWT tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint) with automatic [token refresh](https://authjs.dev/concepts/session-strategies) using [JWT tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint) with automatic [token refresh](https://zitadel.com/docs/apis/openidoauth/endpoints) capabilities through [refresh tokens](https://authjs.dev/concepts/session-strategies) using [JWT tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint) with automatic [token refresh](https://zitadel.com/docs/apis/openidoauth/endpoints) capabilities through [refresh tokens](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token). Protected routes automatically redirect unauthenticated users to the sign-in flow using [Nuxt middleware](https://authjs.dev/concepts/session-strategies) using [JWT tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint) with automatic [token refresh](https://zitadel.com/docs/apis/openidoauth/endpoints) capabilities through [refresh tokens](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token). Protected routes automatically redirect unauthenticated users to the sign-in flow using [Nuxt middleware](https://nuxt.com/docs/guide/directory-structure/middleware), ensuring sensitive areas remain secure. The application also includes complete \*\*[federated logout](https://authjs.dev/concepts/session-strategies) using [JWT tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint) with automatic [token refresh](https://zitadel.com/docs/apis/openidoauth/endpoints) capabilities through [refresh tokens](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token). Protected routes automatically redirect unauthenticated users to the sign-in flow using [Nuxt middleware](https://nuxt.com/docs/guide/directory-structure/middleware), ensuring sensitive areas remain secure. The application also includes complete **[federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)** functionality that properly terminates both the local session and the Zitadel session through the [end-session endpoint](https://authjs.dev/concepts/session-strategies) using [JWT tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint) with automatic [token refresh](https://zitadel.com/docs/apis/openidoauth/endpoints) capabilities through [refresh tokens](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token). Protected routes automatically redirect unauthenticated users to the sign-in flow using [Nuxt middleware](https://nuxt.com/docs/guide/directory-structure/middleware), ensuring sensitive areas remain secure. The application also includes complete \*\*[federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout), redirecting users back to the application with proper CSRF protection using state parameters.
***
Getting started [#getting-started]
Prerequisites [#prerequisites]
Before running this example, you need to create and configure a PKCE application in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview). Follow the PKCE application setup guide to:
1. Create a new Web application in your [Zitadel project](https://zitadel.com/docs/guides/manage/console/projects-overview)
2. Configure it to use the PKCE authentication method
3. Set up your redirect URIs (e.g., `http://localhost:3000/api/auth/callback/zitadel` for development)
4. Configure post-logout redirect URIs (e.g., `http://localhost:3000`)
5. Copy your **Client ID** for use in the next steps
> **Note:** Make sure to enable **Dev Mode** in the ZITADEL Management Console if you're using HTTP URLs during local development. For production, always use HTTPS URLs and disable Dev Mode.
Run the example [#run-the-example]
Once you have your Zitadel application configured:
1. Clone the [repository](https://github.com/zitadel/example-auth-nuxtjs).
2. Create a `.env` file and configure it with the values from your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview). **Use these exact environment variable names:**
```
PORT=3000
SESSION_DURATION=3600
NUXT_AUTH_SECRET=your-very-secret-and-strong-session-key
ZITADEL_DOMAIN=https://your-zitadel-domain
ZITADEL_CLIENT_ID=your-client-id
ZITADEL_CLIENT_SECRET=your-randomly-generated-client-secret
ZITADEL_CALLBACK_URL=http://localhost:3000/api/auth/callback/zitadel
ZITADEL_POST_LOGOUT_URL=http://localhost:3000
AUTH_ORIGIN=http://localhost:3000
```
Replace these values with:
* Your actual Zitadel instance URL (the **Issuer** from your [Zitadel instance settings](https://zitadel.com/docs/guides/manage/console/console-overview))
* The **Client ID** you copied when creating the application
* A randomly generated **NUXT\_AUTH\_SECRET** for session encryption (generate using: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`)
* The **callback URI** you configured in the PKCE setup (must match exactly)
* The **post-logout redirect URI** you configured in the PKCE setup
3. Install dependencies using [npm](https://www.npmjs.com) and start the development server:
```bash
npm install
npm run dev
```
The application will be running at `http://localhost:3000` where you can test the complete authentication flow.
***
Learn more and resources [#learn-more-and-resources]
* [PKCE concept](https://oauth.net/2/pkce/)
* [Federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
* [OIDC integration guide](https://zitadel.com/docs/guides/integrate/login/oidc/login-users)
* [Framework docs](https://nuxt.com)
* [@sidebase/nuxt-auth](https://www.npmjs.com/package/@sidebase/nuxt-auth)
* [NextAuth.js/Auth.js](https://next-auth.js.org)
* [Example repository](https://github.com/zitadel/example-auth-nuxtjs)
# Integrating ZITADEL with Standard OIDC & OAuth2 Libraries
Since ZITADEL is a certified provider, you can use industry-standard tools to handle authentication and API protection in any language or framework without vendor lock-in.
# Qwik
Overview [#overview]
[Qwik](https://qwik.dev) is a framework for building resumable web applications with instant-on interactivity and optimal performance. This example demonstrates how to integrate **Zitadel** using the **[OAuth 2.0 PKCE flow](https://oauth.net/2/pkce/)** to authenticate users securely and maintain sessions across the app.
Auth library [#auth-library]
This example uses **[@auth/qwik](https://www.npmjs.com/package/@auth/qwik)**, a Qwik adapter for **[@auth/core](https://www.npmjs.com/package/@auth/qwik)**, a Qwik adapter for **[@auth/core](https://www.npmjs.com/package/@auth/core)** (formerly Auth.js/NextAuth.js), which implements the [OpenID Connect (OIDC)](https://www.npmjs.com/package/@auth/qwik)**, a Qwik adapter for **[@auth/core](https://www.npmjs.com/package/@auth/core)** (formerly Auth.js/NextAuth.js), which implements the [OpenID Connect (OIDC)](https://zitadel.com/docs/apis/openidoauth/endpoints) flow, manages [PKCE](https://www.npmjs.com/package/@auth/qwik), performs token exchange, and exposes helpers for session state. The core library handles authentication across multiple frameworks and provides secure [token storage](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint) with automatic [token refresh](https://www.npmjs.com/package/@auth/qwik)**, a Qwik adapter for **[@auth/core](https://www.npmjs.com/package/@auth/core)** (formerly Auth.js/NextAuth.js), which implements the [OpenID Connect (OIDC)](https://zitadel.com/docs/apis/openidoauth/endpoints) flow, manages [PKCE](https://oauth.net/2/pkce/), performs token exchange, and exposes helpers for session state. The core library handles authentication across multiple frameworks and provides secure [token storage](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint) with automatic [token refresh](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) capabilities.
***
What this example demonstrates [#what-this-example-demonstrates]
This [Qwik](https://qwik.dev) application showcases a complete authentication implementation using [Zitadel](https://qwik.dev) application showcases a complete authentication implementation using [Zitadel](https://zitadel.com/docs) with the [Authorization Code Flow with PKCE](https://qwik.dev). The example includes secure sign-in functionality that redirects users to Zitadel's hosted login page, handles the OAuth callback to exchange authorization codes for [access tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint), and manages sessions with [JWT tokens](https://qwik.dev) application showcases a complete authentication implementation using [Zitadel](https://zitadel.com/docs) with the [Authorization Code Flow with PKCE](https://oauth.net/2/pkce/). The example includes secure sign-in functionality that redirects users to Zitadel's hosted login page, handles the OAuth callback to exchange authorization codes for [access tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint), and manages sessions with [JWT tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token-jwt-response) stored in encrypted server-side cookies.
Route protection is implemented using [Qwik City's](https://qwik.dev/docs/qwikcity) `onRequest` handler, which performs server-side authentication checks before rendering protected pages. Unauthenticated users attempting to access secured routes are automatically redirected to the login page with a callback URL to return them to their intended destination after successful authentication. The application displays comprehensive user profile information including standard [OIDC claims](https://zitadel.com/docs/apis/openidoauth/claims) like name and email, as well as Zitadel-specific claims such as [organization membership](https://zitadel.com/docs/guides/manage/console/organizations-overview) and [project roles](https://zitadel.com/docs/guides/manage/console/projects-overview).
Sign-out functionality implements **[federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)** through Zitadel's end-session endpoint, ensuring that users are logged out from both the application and Zitadel's single sign-on session. The logout process includes [CSRF protection](https://zitadel.com/docs/guides/integrate/login/oidc/logout#csrf-protection) using state parameters validated through secure cookies, followed by proper cleanup of session data and redirection back to the application.
***
Getting started [#getting-started]
Prerequisites [#prerequisites]
Before running this example, you need to create and configure a PKCE application in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview). Follow the PKCE application setup guide to:
1. Create a new Web application in your [Zitadel project](https://zitadel.com/docs/guides/manage/console/projects-overview)
2. Configure it to use the PKCE authentication method
3. Set up your redirect URIs (e.g., `http://localhost:3000/auth/callback/zitadel` for development)
4. Configure post-logout redirect URIs (e.g., `http://localhost:3000/api/auth/logout/callback`)
5. Copy your **Client ID** for use in the next steps
> **Note:** Make sure to enable **Dev Mode** in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview) if you're using HTTP URLs during local development. For production, always use HTTPS URLs and disable Dev Mode.
Run the example [#run-the-example]
Once you have your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview) configured:
1. Clone the [repository](https://github.com/zitadel/example-auth-qwik).
2. Create a `.env.local` file and configure it with the values from your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview):
```
VITE_PORT=3000
VITE_SESSION_SECRET=your-very-secret-and-strong-session-key
VITE_SESSION_DURATION=3600
VITE_ZITADEL_DOMAIN=https://your-instance.zitadel.cloud
VITE_ZITADEL_CLIENT_ID=your_client_id_from_console
VITE_ZITADEL_CLIENT_SECRET=your-randomly-generated-client-secret
VITE_ZITADEL_CALLBACK_URL=http://localhost:3000/auth/callback/zitadel
VITE_ZITADEL_POST_LOGOUT_URL=http://localhost:3000/api/auth/logout/callback
```
Replace these values with:
* Your actual Zitadel instance URL (the **Issuer** from your [application settings](https://zitadel.com/docs/guides/integrate/login/oidc/login-users))
* The **Client ID** you copied when creating the application
* A randomly generated session secret (generate using: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`)
* A randomly generated client secret value (Auth.js requires this for internal settings)
* The **redirect URI** you configured in the PKCE setup (must match exactly)
* The **post-logout redirect URI** you configured (must match exactly)
3. Install dependencies using [npm](https://www.npmjs.com) and start the development server:
```bash
npm install
npm run dev
```
The application will be available at `http://localhost:3000`.
***
Learn more and resources [#learn-more-and-resources]
* [PKCE concept](https://oauth.net/2/pkce/)
* [Federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
* [OIDC integration guide](https://zitadel.com/docs/guides/integrate/login/oidc/login-users)
* [Framework docs](https://qwik.dev)
* [Auth library (@auth/core)](https://www.npmjs.com/package/@auth/core)
* [Qwik adapter (@auth/qwik)](https://www.npmjs.com/package/@auth/qwik)
* [Example repository](https://github.com/zitadel/example-auth-qwik)
# React
Overview [#overview]
[React](https://react.dev) is a JavaScript library for building user interfaces with component composition, declarative views, and a rich ecosystem. This example demonstrates how to integrate **Zitadel** using the **[OAuth 2.0 PKCE flow](https://oauth.net/2/pkce/)** to authenticate users securely and maintain sessions across your React application.
Auth library [#auth-library]
This example uses **[react-oidc-context](https://www.npmjs.com/package/react-oidc-context)**, a React wrapper around **[oidc-client-ts](https://www.npmjs.com/package/react-oidc-context)**, a React wrapper around **[oidc-client-ts](https://www.npmjs.com/package/oidc-client-ts)**. The underlying [oidc-client-ts](https://www.npmjs.com/package/react-oidc-context)\*\*, a React wrapper around **[oidc-client-ts](https://www.npmjs.com/package/oidc-client-ts) library implements the [OpenID Connect (OIDC)](https://www.npmjs.com/package/react-oidc-context)**, a React wrapper around **[oidc-client-ts](https://www.npmjs.com/package/oidc-client-ts)**. The underlying [oidc-client-ts](https://github.com/authts/oidc-client-ts) library implements the [OpenID Connect (OIDC)](https://zitadel.com/docs/apis/openidoauth/endpoints) protocol, manages [PKCE](https://www.npmjs.com/package/react-oidc-context) code challenge generation, performs secure [token exchange](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint), and exposes React hooks for managing authentication state throughout your application.
***
What this example demonstrates [#what-this-example-demonstrates]
This [React](https://react.dev) application showcases a complete authentication flow using [Zitadel](https://react.dev) application showcases a complete authentication flow using [Zitadel](https://zitadel.com/docs) with the secure [PKCE](https://react.dev) standard. Users begin on a public landing page where they can initiate login through [react-oidc-context](https://react.dev) application showcases a complete authentication flow using [Zitadel](https://zitadel.com/docs) with the secure [PKCE](https://oauth.net/2/pkce/) standard. Users begin on a public landing page where they can initiate login through [react-oidc-context](https://www.npmjs.com/package/react-oidc-context), which redirects them to [Zitadel's authorization endpoint](https://react.dev) application showcases a complete authentication flow using [Zitadel](https://zitadel.com/docs) with the secure [PKCE](https://oauth.net/2/pkce/) standard. Users begin on a public landing page where they can initiate login through [react-oidc-context](https://www.npmjs.com/package/react-oidc-context), which redirects them to [Zitadel's authorization endpoint](https://zitadel.com/docs/apis/openidoauth/endpoints) for authentication. After successful authentication, the application handles the OAuth callback, exchanges the authorization code for [access tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint), and establishes a secure [session](https://react.dev) application showcases a complete authentication flow using [Zitadel](https://zitadel.com/docs) with the secure [PKCE](https://oauth.net/2/pkce/) standard. Users begin on a public landing page where they can initiate login through [react-oidc-context](https://www.npmjs.com/package/react-oidc-context), which redirects them to [Zitadel's authorization endpoint](https://zitadel.com/docs/apis/openidoauth/endpoints).
Protected routes are secured using [react-oidc-context's](https://react.dev) application showcases a complete authentication flow using [Zitadel](https://zitadel.com/docs) with the secure [PKCE](https://oauth.net/2/pkce/) standard. Users begin on a public landing page where they can initiate login through [react-oidc-context](https://www.npmjs.com/package/react-oidc-context) `withAuthenticationRequired` higher-order component, which automatically redirects unauthenticated users to the login flow. The profile page displays user information retrieved from [OIDC claims](https://www.npmjs.com/package/react-oidc-context) `withAuthenticationRequired` higher-order component, which automatically redirects unauthenticated users to the login flow. The profile page displays user information retrieved from [OIDC claims](https://zitadel.com/docs/apis/openidoauth/claims), including profile details, email, and metadata. The application implements complete [federated logout](https://www.npmjs.com/package/react-oidc-context) `withAuthenticationRequired` higher-order component, which automatically redirects unauthenticated users to the login flow. The profile page displays user information retrieved from [OIDC claims](https://zitadel.com/docs/apis/openidoauth/claims), including profile details, email, and metadata. The application implements complete [federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout) functionality, properly terminating both the local session and the Zitadel session through the end-session endpoint.
The example leverages [React Router](https://reactrouter.com) for client-side navigation and [Vite](https://reactrouter.com) for client-side navigation and [Vite](https://vite.dev) as the build tool, demonstrating modern React development patterns with [TypeScript](https://reactrouter.com) for client-side navigation and [Vite](https://vite.dev) as the build tool, demonstrating modern React development patterns with [TypeScript](https://www.typescriptlang.org). All authentication state management, including [token refresh](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint) with offline access, is handled automatically by the [oidc-client-ts](https://reactrouter.com) for client-side navigation and [Vite](https://vite.dev) as the build tool, demonstrating modern React development patterns with [TypeScript](https://www.typescriptlang.org). All authentication state management, including [token refresh](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint) with offline access, is handled automatically by the [oidc-client-ts](https://www.npmjs.com/package/oidc-client-ts) library under the hood.
***
Getting started [#getting-started]
Prerequisites [#prerequisites]
Before running this example, you need to create and configure a PKCE application in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview). Follow the PKCE application setup guide to:
1. Create a new Web application in your [Zitadel project](https://zitadel.com/docs/guides/manage/console/projects-overview)
2. Configure it to use the PKCE authentication method
3. Set up your redirect URIs (e.g., `http://localhost:3000/auth/callback` for development)
4. Configure post-logout redirect URIs (e.g., `http://localhost:3000`)
5. Copy your **Client ID** for use in the next steps
> **Note:** Make sure to enable **Dev Mode** in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview) if you're using HTTP URLs during local development. For production, always use HTTPS URLs and disable Dev Mode.
Run the example [#run-the-example]
Once you have your Zitadel application configured:
1. Clone the [repository](https://github.com/zitadel/example-auth-react).
2. Create a `.env` file (copy from `.env.example`) and configure it with the values from your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview). **Use these exact environment variable names:**
```
VITE_ZITADEL_DOMAIN=https://your-instance.zitadel.cloud
VITE_ZITADEL_CLIENT_ID=your_client_id_from_console
VITE_ZITADEL_CALLBACK_URL=http://localhost:3000/auth/callback
VITE_ZITADEL_POST_LOGOUT_URL=http://localhost:3000
VITE_POST_LOGIN_URL=/profile
VITE_PORT=3000
```
Replace these values with:
* Your actual Zitadel instance URL (the **Issuer** from your [Zitadel application settings](https://zitadel.com/docs/guides/integrate/login/oidc/login-users))
* The **Client ID** you copied when creating the application
* The **redirect URI** you configured in the PKCE setup (must match exactly)
* The **post-logout redirect URI** you configured
3. Install dependencies using [npm](https://www.npmjs.com) and start the development server:
```bash
npm install
npm run dev
```
The application will be running at `http://localhost:3000` where you can test the complete authentication flow.
***
Learn more and resources [#learn-more-and-resources]
* [PKCE concept](https://oauth.net/2/pkce/)
* [Federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
* [OIDC integration guide](https://zitadel.com/docs/guides/integrate/login/oidc/login-users)
* [React documentation](https://react.dev)
* [react-oidc-context](https://www.npmjs.com/package/react-oidc-context)
* [oidc-client-ts](https://www.npmjs.com/package/oidc-client-ts)
* [React Router](https://reactrouter.com)
* [Example repository](https://github.com/zitadel/example-auth-react)
# SolidStart
Overview [#overview]
[SolidStart](https://start.solidjs.com) is a full-stack web framework built on top of [SolidJS](https://solidjs.com) that enables you to create performant web applications with a modern development experience. This example demonstrates how to integrate **[Zitadel](https://zitadel.com/docs)** using the **[OAuth 2.0 PKCE flow](https://oauth.net/2/pkce/)** to authenticate users securely and maintain [sessions](https://authjs.dev/concepts/session-strategies) across the app.
Auth library [#auth-library]
This example uses **[@auth/solid-start](https://www.npmjs.com/package/@auth/solid-start)**, the SolidStart adapter for [Auth.js](https://authjs.dev), which builds on **[@auth/core](https://www.npmjs.com/package/@auth/core)**. Auth.js implements the [OpenID Connect (OIDC)](https://zitadel.com/docs/apis/openidoauth/endpoints) flow, manages [PKCE](https://oauth.net/2/pkce/), performs [token exchange](https://zitadel.com/docs/apis/openidoauth/grant-types), and exposes helpers for [session state](https://authjs.dev/concepts/session-strategies). The example also uses **[openid-client](https://www.npmjs.com/package/openid-client)** ([GitHub](https://github.com/panva/node-openid-client)) for manual token refresh operations and logout URL construction.
***
What this example demonstrates [#what-this-example-demonstrates]
This example shows a complete authentication implementation using [SolidStart](https://start.solidjs.com) with [Zitadel](https://zitadel.com/docs). Users start on a public landing page and click a login button to authenticate with Zitadel using the secure **[PKCE flow](https://oauth.net/2/pkce/)**. The [Auth.js](https://authjs.dev) library handles the [OAuth 2.0](https://zitadel.com/docs/guides/integrate/login/oidc/oauth-recommended-flows) authorization code exchange and manages secure [session storage](https://authjs.dev/concepts/session-strategies) using encrypted [JWT tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint). After successful authentication, users are redirected to a protected profile page displaying their user information including [OIDC claims](https://zitadel.com/docs/apis/openidoauth/claims) like name, email, and custom [Zitadel metadata](https://zitadel.com/docs/apis/openidoauth/claims#reserved-claims).
The application implements automatic [token refresh](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) to maintain long-lived sessions without requiring users to re-authenticate. When an [access token](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint) expires, the refresh logic seamlessly exchanges the [refresh token](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) for new tokens in the background. [Route protection](https://start.solidjs.com/api/createAsync) is implemented using [SolidStart's server functions](https://start.solidjs.com/api/server$), ensuring only authenticated users can access sensitive areas. The logout flow implements [federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout) with CSRF protection using state parameters, properly terminating both the local session and the Zitadel session before redirecting users back to the application.
***
Getting started [#getting-started]
Prerequisites [#prerequisites]
Before running this example, you need to create and configure a PKCE application in the [Zitadel Console](https://zitadel.com/docs/guides/manage/console/console-overview). Follow the PKCE application setup guide to:
1. Create a new Web application in your [Zitadel project](https://zitadel.com/docs/guides/manage/console/projects-overview)
2. Configure it to use the PKCE authentication method
3. Set up your redirect URIs (e.g., `http://localhost:3000/api/auth/callback/zitadel` for development)
4. Configure post-logout redirect URIs (e.g., `http://localhost:3000/api/auth/logout/callback`)
5. Copy your **Client ID** for use in the next steps
> **Note:** Make sure to enable **Dev Mode** in the [Zitadel Console](https://zitadel.com/docs/guides/manage/console/console-overview) if you're using HTTP URLs during local development. For production, always use HTTPS URLs and disable Dev Mode.
Run the example [#run-the-example]
Once you have your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview) configured:
1. Clone the [repository](https://github.com/zitadel/example-auth-solidstart).
2. Create a `.env` file and configure it with the values from your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview). Use the exact environment variable names from the repository:
```
NODE_ENV=development
PORT=3000
SESSION_SECRET="your-very-secret-and-strong-session-key"
SESSION_DURATION=3600
ZITADEL_DOMAIN="https://your-zitadel-domain"
ZITADEL_CLIENT_ID="your-client-id"
ZITADEL_CLIENT_SECRET="your-randomly-generated-client-secret"
ZITADEL_CALLBACK_URL="http://localhost:3000/api/auth/callback/zitadel"
ZITADEL_POST_LOGIN_URL="/profile"
ZITADEL_POST_LOGOUT_URL="http://localhost:3000/api/auth/logout/callback"
NEXTAUTH_URL="http://localhost:3000"
```
Replace these values with:
* Your actual [Zitadel instance URL](https://zitadel.com/docs/guides/manage/console/default-settings) (the **Issuer**)
* The **Client ID** you copied when creating the application
* The **redirect URIs** you configured in the PKCE setup (must match exactly)
* A secure random string for `SESSION_SECRET` (generate using: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`)
* A secure random string for `ZITADEL_CLIENT_SECRET` for [Auth.js](https://authjs.dev) configuration compatibility
3. Install dependencies using [npm](https://www.npmjs.com) and start the development server to verify the authentication flow end-to-end:
```bash
npm install
npm run dev
```
***
Learn more and resources [#learn-more-and-resources]
* [PKCE concept](https://oauth.net/2/pkce/)
* [Federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
* [OIDC integration guide](https://zitadel.com/docs/guides/integrate/login/oidc/login-users)
* [SolidStart documentation](https://start.solidjs.com)
* [Auth.js documentation](https://authjs.dev)
* [@auth/solid-start](https://www.npmjs.com/package/@auth/solid-start)
* [@auth/core](https://www.npmjs.com/package/@auth/core)
* [openid-client](https://www.npmjs.com/package/openid-client)
* [Example repository](https://github.com/zitadel/example-auth-solidstart)
# Spring Boot
Overview [#overview]
[Spring Boot](https://spring.io/projects/spring-boot) is a powerful, production-ready framework for building Java applications. This example shows how to integrate **Zitadel** into a Spring Boot web application using **OpenID Connect (OIDC)** and the **Authorization Code Flow + PKCE**.
It demonstrates a common web app pattern: users start on a public landing page, authenticate with Zitadel via a login button, and are then redirected to a protected profile page that displays user information. Logout is also implemented to end both the local session and the Zitadel session.
Auth library [#auth-library]
This example uses **[Spring Security](https://spring.io/projects/spring-security)**, the standard framework for authentication and access control in Spring applications. Spring Security supports OIDC natively and manages the PKCE flow for you.
***
What this example demonstrates [#what-this-example-demonstrates]
This example shows a complete authentication implementation using Spring Security’s OAuth 2.0 / OIDC support:
* **Public landing page** (`/`) accessible without authentication.
* **Sign-in with Zitadel** via Spring Security’s OAuth2 login entrypoint.
* **Route protection** so authenticated access is required for protected routes (e.g. `/profile`).
* **Profile page** that renders the authenticated user’s OIDC claims.
* **Server-side session management** handled by the servlet container + Spring Security.
* **Federated logout** that clears the local session and triggers RP-initiated logout against Zitadel, returning the user to a post-logout callback route.
***
Getting started [#getting-started]
Prerequisites [#prerequisites]
Before you begin, ensure you have the following:
System Requirements [#system-requirements]
* Java Development Kit (JDK) 17 or later
* Maven (or use the included `mvnw` wrapper)
Account Setup [#account-setup]
You’ll need a Zitadel account and an application configured for a Web app using Authorization Code + PKCE.
> **Important:** Configure the following URLs in your Zitadel application settings:
>
> * **Redirect URIs:** `http://localhost:3000/auth/callback`
> * **Post Logout Redirect URIs:** `http://localhost:3000/auth/logout/callback`
>
> These URLs must exactly match what your Spring Boot application uses. For production, add your production URLs.
Run the example [#run-the-example]
1. Clone the repository.
2. Copy `.env.example` to `.env` and fill in your Zitadel details.
```dotenv
# Port number where your Spring Boot server will listen for incoming HTTP requests.
PORT=3000
# Session timeout in seconds. Users will be automatically logged out after this
# duration of inactivity. 3600 seconds = 1 hour.
SESSION_DURATION=3600
# Your Zitadel instance domain URL. Include the full https:// URL.
ZITADEL_DOMAIN="https://your-zitadel-domain"
# Application Client ID from your Zitadel application settings.
ZITADEL_CLIENT_ID="your-client-id"
# Provide a randomly generated string here.
ZITADEL_CLIENT_SECRET="your-randomly-generated-client-secret"
```
3. Build and start the server:
```bash
# 1. Clone the repository
git clone git@github.com:zitadel/example-auth-spring.git
cd example-auth-spring
# 2. Build the project and download dependencies
mvn clean install
# 3. Start the development server
mvn spring-boot:run
```
4. Open `http://localhost:3000` and walk through login → profile → logout.
***
Learn more and resources [#learn-more-and-resources]
* [PKCE concept](https://oauth.net/2/pkce/)
* [Federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
* [OIDC integration guide](https://zitadel.com/docs/guides/integrate/login/oidc/login-users)
* [Spring Boot documentation](https://spring.io/projects/spring-boot)
* [Spring Security documentation](https://spring.io/projects/spring-security)
* [Example repository](https://github.com/zitadel/example-auth-spring)
# SvelteKit
Overview [#overview]
[SvelteKit](https://kit.svelte.dev) is a framework for building web applications with flexible filesystem-based routing and a unified approach to server-side and client-side logic. This example demonstrates how to integrate **Zitadel** using the **[OAuth 2.0 PKCE flow](https://oauth.net/2/pkce/)** to authenticate users securely and maintain sessions across your application.
Auth library [#auth-library]
This example uses **[@auth/sveltekit](https://www.npmjs.com/package/@auth/sveltekit)**, the SvelteKit integration of **[@auth/core](https://www.npmjs.com/package/@auth/core)** (formerly NextAuth.js). Auth.js provides a complete authentication solution that handles the [OpenID Connect](https://zitadel.com/docs/apis/openidoauth/endpoints) protocol, manages [PKCE](https://oauth.net/2/pkce/) code generation and verification, performs secure token exchange, and provides session management utilities across your SvelteKit application.
***
What this example demonstrates [#what-this-example-demonstrates]
This example provides a complete authentication implementation showing how to secure a [SvelteKit](https://kit.svelte.dev) application with [Zitadel](https://zitadel.com/docs). Users start on a public landing page and click a login button to authenticate through Zitadel's [authorization server](https://zitadel.com/docs/apis/openidoauth/endpoints) using the **[PKCE flow](https://oauth.net/2/pkce/)**. After successful authentication, they're redirected to a protected profile page that displays their [user information and claims](https://zitadel.com/docs/apis/openidoauth/claims).
The example demonstrates route protection using [SvelteKit's server-side load functions](https://kit.svelte.dev/docs/load) that verify [session state](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint) before rendering protected pages. Authentication state is managed through [@auth/sveltekit hooks](https://kit.svelte.dev/docs/load) that verify [session state](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint) before rendering protected pages. Authentication state is managed through [@auth/sveltekit hooks](https://www.npmjs.com/package/@auth/sveltekit), which integrate with [SvelteKit's hooks system](https://kit.svelte.dev/docs/load) that verify [session state](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint) before rendering protected pages. Authentication state is managed through [@auth/sveltekit hooks](https://www.npmjs.com/package/@auth/sveltekit), which integrate with [SvelteKit's hooks system](https://kit.svelte.dev/docs/hooks) to provide session data throughout your application. The implementation includes automatic [access token refresh](https://kit.svelte.dev/docs/load) that verify [session state](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint) before rendering protected pages. Authentication state is managed through [@auth/sveltekit hooks](https://www.npmjs.com/package/@auth/sveltekit), which integrate with [SvelteKit's hooks system](https://kit.svelte.dev/docs/hooks) to provide session data throughout your application. The implementation includes automatic [access token refresh](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) to maintain long-lived sessions without requiring users to re-authenticate.
The logout flow implements **[federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)** with proper CSRF protection, terminating both the local application session and the Zitadel session before redirecting users back to your application. Custom error pages provide user-friendly messages for authentication failures, and the example includes comprehensive security headers configured through [SvelteKit's hooks](https://kit.svelte.dev/docs/hooks).
***
Getting started [#getting-started]
Prerequisites [#prerequisites]
Before running this example, you need to create and configure a PKCE application in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview). Follow the PKCE application setup guide to:
1. Create a new Web application in your [Zitadel project](https://zitadel.com/docs/guides/manage/console/projects-overview)
2. Configure it to use the PKCE authentication method
3. Set up your redirect URIs (e.g., `http://localhost:3000/auth/callback/zitadel` for development)
4. Configure post-logout redirect URIs (e.g., `http://localhost:3000/auth/logout/callback`)
5. Copy your **Client ID** for use in the next steps
> **Note:** Make sure to enable **Dev Mode** in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview) if you're using HTTP URLs during local development. For production, always use HTTPS URLs and disable Dev Mode.
Run the example [#run-the-example]
Once you have your Zitadel application configured:
1. Clone the [repository](https://github.com/zitadel/example-auth-sveltekit).
2. Create a `.env.local` file in the project root and configure it with the values from your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview). **Use these exact environment variable names:**
```
SESSION_SECRET=your-very-secret-and-strong-session-key
SESSION_DURATION=3600
ZITADEL_DOMAIN=https://your-instance.zitadel.cloud
ZITADEL_CLIENT_ID=your_client_id_from_console
ZITADEL_CLIENT_SECRET=your-randomly-generated-client-secret
ZITADEL_CALLBACK_URL=http://localhost:3000/auth/callback/zitadel
ZITADEL_POST_LOGOUT_URL=http://localhost:3000/auth/logout/callback
NEXTAUTH_URL=http://localhost:3000
```
Replace these values with:
* A secure random string for `SESSION_SECRET` (generate using: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`)
* Your actual Zitadel instance URL for `ZITADEL_DOMAIN` (the issuer URL)
* The **Client ID** you copied when creating the application
* A random string for `ZITADEL_CLIENT_SECRET` (Auth.js requires this even though PKCE doesn't strictly need it)
* The **redirect URI** you configured in the PKCE setup (must match exactly)
* The **post-logout redirect URI** you configured
3. Install dependencies using [npm](https://www.npmjs.com) and start the development server:
```bash
npm install
npm run dev
```
4. Navigate to `http://localhost:3000` to verify the authentication flow end-to-end.
***
Learn more and resources [#learn-more-and-resources]
* [PKCE concept](https://oauth.net/2/pkce/)
* [Federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
* [OIDC integration guide](https://zitadel.com/docs/guides/integrate/login/oidc/login-users)
* [SvelteKit documentation](https://kit.svelte.dev)
* [Auth.js (SvelteKit)](https://www.npmjs.com/package/@auth/sveltekit)
* [Auth.js Core](https://www.npmjs.com/package/@auth/core)
* [Example repository](https://github.com/zitadel/example-auth-sveltekit)
# Symfony
Overview [#overview]
[Symfony](https://symfony.com) is a set of reusable PHP components and a PHP framework for web projects. It provides a robust set of features for web applications, making it one of the most popular choices for building server-side applications. This example demonstrates how to integrate **[Zitadel](https://zitadel.com/docs)** using the **[OAuth 2.0 PKCE flow](https://oauth.net/2/pkce/)** to authenticate users securely and maintain sessions across your application.
Auth library [#auth-library]
This example uses **[Symfony Security](https://symfony.com/doc/current/security.html)**, the standard authentication component for Symfony applications. Symfony Security implements the [OpenID Connect (OIDC)](https://zitadel.com/docs/apis/openidoauth/endpoints) flow through a custom authenticator, manages [PKCE](https://oauth.net/2/pkce/), performs secure token exchange, and provides session management through Symfony's firewall system.
***
What this example demonstrates [#what-this-example-demonstrates]
This example shows a complete authentication implementation using [Symfony](https://symfony.com) with [Zitadel](https://zitadel.com/docs). Users start on a public landing page, click a login button to authenticate with Zitadel using the secure [PKCE flow](https://oauth.net/2/pkce/), and are redirected to a protected profile page displaying their [user information](https://zitadel.com/docs/apis/openidoauth/claims) after successful authentication.
The application implements server-side session management with Symfony's security system, storing authentication state securely through the framework's user provider pattern. Protected routes use Symfony Security's firewall configuration to automatically redirect unauthenticated users to the sign-in flow, ensuring only authenticated users can access sensitive areas. The profile page displays comprehensive user information including [OIDC claims](https://zitadel.com/docs/apis/openidoauth/claims) and [session metadata](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint).
The application demonstrates proper **[federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)** by terminating sessions both locally and with Zitadel's [end-session endpoint](https://zitadel.com/docs/guides/integrate/login/oidc/logout), complete with CSRF protection using state parameters. Additionally, it includes automatic [token refresh](https://zitadel.com/docs/apis/openidoauth/endpoints) using [refresh tokens](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) to maintain long-lived sessions without requiring users to re-authenticate. The example uses [Zitadel-specific scopes](https://zitadel.com/docs/apis/openidoauth/scopes) like `urn:zitadel:iam:user:metadata` and `urn:zitadel:iam:org:projects:roles` to access extended user attributes and role information for implementing [role-based access control (RBAC)](https://zitadel.com/docs/guides/manage/console/roles).
***
Getting started [#getting-started]
Prerequisites [#prerequisites]
Before running this example, you need to create and configure a PKCE application in the [Zitadel Console](https://zitadel.com/docs/guides/manage/console/console-overview). Follow the PKCE application setup guide to:
1. Create a new Web application in your [Zitadel project](https://zitadel.com/docs/guides/manage/console/projects-overview)
2. Configure it to use the PKCE authentication method
3. Set up your redirect URIs (e.g., `http://localhost:3000/auth/callback` for development)
4. Configure post-logout redirect URIs (e.g., `http://localhost:3000/auth/logout/callback`)
5. Copy your **Client ID** for use in the next steps
6. Optionally enable [refresh tokens](https://zitadel.com/docs/apis/openidoauth/grant-types#refresh-token) in Token Settings for long-lived sessions
> **Note:** Make sure to enable **Dev Mode** in the [Zitadel Console](https://zitadel.com/docs/guides/manage/console/console-overview) if you're using HTTP URLs during local development. For production, always use HTTPS URLs and disable Dev Mode.
Run the example [#run-the-example]
Once you have your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview) configured:
1. Clone the [repository](https://github.com/zitadel/example-auth-symfony).
2. Create a `.env` file (copy from `.env.example`) and configure it with the values from your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview). **Use these exact environment variable names:**
```
APP_SECRET=your-app-secret-key
SERVER_URL=http://localhost:3000
SERVER_PORT=3000
ZITADEL_DOMAIN=https://your-zitadel-domain
ZITADEL_CLIENT_ID=your-zitadel-application-client-id
ZITADEL_CLIENT_SECRET=your-randomly-generated-client-secret
ZITADEL_POST_LOGOUT_URL=http://localhost:3000/auth/logout/callback
```
Replace these values with:
* Your actual [Zitadel instance URL](https://zitadel.com/docs/guides/manage/console/default-settings) for `ZITADEL_DOMAIN` (the issuer)
* The **Client ID** you copied when creating the application for `ZITADEL_CLIENT_ID`
* The **post-logout redirect URI** for `ZITADEL_POST_LOGOUT_URL`
* A strong random string for `APP_SECRET` (generate using: `php -r "echo bin2hex(random_bytes(32));"`)
* A randomly generated string for `ZITADEL_CLIENT_SECRET` (generate using: `php -r "echo bin2hex(random_bytes(32));"`)
3. Install dependencies using [Composer](https://getcomposer.org) with `composer install` and start the development server with `composer run dev` to verify the authentication flow end-to-end.
***
Learn more and resources [#learn-more-and-resources]
* [PKCE concept](https://oauth.net/2/pkce/)
* [Federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
* [OIDC integration guide](https://zitadel.com/docs/guides/integrate/login/oidc/login-users)
* [Symfony documentation](https://symfony.com/doc)
* [Symfony Security documentation](https://symfony.com/doc/current/security.html)
* [Example repository](https://github.com/zitadel/example-auth-symfony)
# Vue.js
Overview [#overview]
[Vue.js](https://vuejs.org) is a progressive JavaScript framework for building user interfaces with reactive data binding, component composition, and a rich ecosystem. This example demonstrates how to integrate **Zitadel** using the **[OAuth 2.0 PKCE flow](https://oauth.net/2/pkce/)** to authenticate users securely and maintain sessions across your Vue.js application.
Auth library [#auth-library]
This example uses **[vue-oidc-context](https://www.npmjs.com/package/vue-oidc-context)**, a Vue 3 wrapper around **[oidc-client-ts](https://www.npmjs.com/package/vue-oidc-context)**, a Vue 3 wrapper around **[oidc-client-ts](https://www.npmjs.com/package/oidc-client-ts)**, which implements the [OpenID Connect](https://www.npmjs.com/package/vue-oidc-context) (OIDC) flow. The underlying [oidc-client-ts](https://www.npmjs.com/package/vue-oidc-context)\*\*, a Vue 3 wrapper around \*\*[oidc-client-ts](https://www.npmjs.com/package/oidc-client-ts) library manages [PKCE](https://www.npmjs.com/package/vue-oidc-context), performs token exchange, and exposes helpers for [session state](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint) management.
***
What this example demonstrates [#what-this-example-demonstrates]
This [Vue.js](https://vuejs.org) application showcases a complete authentication pattern using [Zitadel](https://zitadel.com/docs) with [PKCE](https://oauth.net/2/pkce/). Users begin on a public landing page where they can initiate sign-in with Zitadel's authorization server. After successful authentication, the app handles the [OAuth callback](https://zitadel.com/docs/apis/openidoauth/endpoints) and redirects users to a protected profile page displaying their [user information and claims](https://zitadel.com/docs/apis/openidoauth/claims).
The application uses [vue-oidc-context](https://www.npmjs.com/package/vue-oidc-context) to wrap authentication logic around [Vue Router](https://www.npmjs.com/package/vue-oidc-context) to wrap authentication logic around [Vue Router](https://router.vuejs.org) navigation guards, automatically protecting routes and ensuring only authenticated users can access sensitive areas. The [withAuthenticationRequired](https://www.npmjs.com/package/vue-oidc-context) to wrap authentication logic around [Vue Router](https://router.vuejs.org) navigation guards, automatically protecting routes and ensuring only authenticated users can access sensitive areas. The [withAuthenticationRequired](https://github.com/authts/vue-oidc-context#protecting-routes) higher-order component secures individual routes by checking [session state](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint) before rendering protected views.
The example includes secure sign-out functionality implementing **[federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)** through Zitadel's [end-session endpoint](https://zitadel.com/docs/apis/openidoauth/endpoints#end_session_endpoint), which terminates both the local application session and the Zitadel session. The app demonstrates proper handling of [refresh tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token_endpoint) using the `offline_access` [scope](https://zitadel.com/docs/guides/integrate/login/oidc/logout)\*\* through Zitadel's [end-session endpoint](https://zitadel.com/docs/apis/openidoauth/endpoints#end-session-endpoint), which terminates both the local application session and the Zitadel session. The app demonstrates proper handling of [refresh tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint) using the `offline_access` [scope](https://zitadel.com/docs/apis/openidoauth/scopes), and includes comprehensive [error handling](https://zitadel.com/docs/guides/integrate/login/oidc/logout)\*\* through Zitadel's [end-session endpoint](https://zitadel.com/docs/apis/openidoauth/endpoints#end-session-endpoint), which terminates both the local application session and the Zitadel session. The app demonstrates proper handling of [refresh tokens](https://zitadel.com/docs/apis/openidoauth/endpoints#token-endpoint) using the `offline_access` [scope](https://zitadel.com/docs/apis/openidoauth/scopes), and includes comprehensive [error handling](https://github.com/authts/vue-oidc-context#error-handling) for authentication failures.
***
Getting started [#getting-started]
Prerequisites [#prerequisites]
Before running this example, you need to create and configure a PKCE application in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview). Follow the PKCE application setup guide to:
1. Create a new Web application in your [Zitadel project](https://zitadel.com/docs/guides/manage/console/projects-overview)
2. Configure it to use the PKCE authentication method
3. Set up your redirect URIs (e.g., `http://localhost:3000/auth/callback` for development)
4. Configure post-logout redirect URIs (e.g., `http://localhost:3000`)
5. Copy your **Client ID** for use in the next steps
> **Note:** Make sure to enable **Dev Mode** in the [ZITADEL Management Console](https://zitadel.com/docs/guides/manage/console/console-overview) if you're using HTTP URLs during local development. For production, always use HTTPS URLs and disable Dev Mode.
Run the example [#run-the-example]
Once you have your [Zitadel application configured](https://zitadel.com/docs/guides/integrate/login/oidc/login-users), follow these steps:
1. Clone the [repository](https://github.com/zitadel/example-auth-vuejs).
2. Create a `.env` file in the project root and configure it with the values from your [Zitadel application](https://zitadel.com/docs/guides/manage/console/applications-overview):
```
VITE_ZITADEL_DOMAIN=https://your-instance.zitadel.cloud
VITE_ZITADEL_CLIENT_ID=your_client_id_from_console
VITE_ZITADEL_CALLBACK_URL=http://localhost:3000/auth/callback
VITE_ZITADEL_POST_LOGOUT_URL=http://localhost:3000
VITE_POST_LOGIN_URL=/profile
```
Replace these values with:
* Your actual [Zitadel instance URL](https://zitadel.com/docs/guides/manage/console/default-settings) (the **Issuer**)
* The **Client ID** you copied when creating the application in the PKCE setup
* The **redirect URI** you configured (must match exactly)
* The **post-logout redirect URI** for [federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
3. Install dependencies using [npm](https://www.npmjs.com) and start the development server:
```bash
npm install
npm run dev
```
***
Learn more and resources [#learn-more-and-resources]
* [PKCE concept](https://oauth.net/2/pkce/)
* [Federated logout](https://zitadel.com/docs/guides/integrate/login/oidc/logout)
* [OIDC integration guide](https://zitadel.com/docs/guides/integrate/login/oidc/login-users)
* [Vue.js documentation](https://vuejs.org)
* [Vue Router documentation](https://router.vuejs.org)
* [vue-oidc-context (wrapper)](https://www.npmjs.com/package/vue-oidc-context)
* [oidc-client-ts (primary library)](https://www.npmjs.com/package/oidc-client-ts)
* [oidc-client-ts GitHub](https://github.com/authts/oidc-client-ts)
* [Example repository](https://github.com/zitadel/example-auth-vuejs)
# Code examples
Actions are a powerful tool to extend ZITADEL, and you might wonder what use cases actions can be used for.
This page provides a non-exhaustive list of possibilities which is provided by [examples](https://github.com/zitadel/actions/tree/main/examples). If a use case is missing, feel free to contribute an issue or pull request to the repository, thanks in advance 🤗.
Customize OIDC response [#customize-oidc-response]
Append claims returned on OIDC requests.
Triggers [#triggers]
* Complement token
* [Pre Userinfo creation](./complement-token#pre-userinfo-creation-id-token-userinfo-introspection-endpoint)
* [Pre access token creation](./complement-token#pre-access-token-creation)
Set a hardcoded claim [#set-a-hardcoded-claim]
Extend the claims by a hardcoded value.
Code example
```js reference
https://github.com/zitadel/actions/blob/main/examples/add_claim.js
```
Set dynamic claim from user metadata [#set-dynamic-claim-from-user-metadata]
Extend the claims by dynamically reading metadata from a user and sets the picture-claim if idpPicture-metadata value is present.
{props.summary ? props.summary : 'Code example'}
```js reference
https://github.com/zitadel/actions/blob/main/examples/add_picture_claim_from_idp_metadata.js
```
Set dynamic claim from organization metadata [#set-dynamic-claim-from-organization-metadata]
Extend the claims by dynamically reading metadata from an organization and sets the present metadata.
Code example
```js reference
https://github.com/zitadel/actions/blob/main/examples/org_metadata_claim.js
```
Custom role mapping in claims [#custom-role-mapping-in-claims]
Some products require specific role mapping from ZITADEL, no worries we got you covered 😉
Code example
```js reference
https://github.com/zitadel/actions/blob/main/examples/custom_roles.js
```
Custom role mapping including org metadata in claims [#custom-role-mapping-including-org-metadata-in-claims]
There's even a possibility to use the metadata of organizations the user is granted to
Code example
```js reference
https://github.com/zitadel/actions/blob/main/examples/custom_roles_org_metadata.js
```
Customize SAML response [#customize-saml-response]
Append attributes returned on SAML requests.
Triggers [#triggers]
* Complement SAMLResponse
* [Pre SAMLResponse creation](./customize-samlresponse#pre-saml-response-creation)
Custom role mapping in attributes [#custom-role-mapping-in-attributes]
Some products require specific role mapping from ZITADEL, no worries we got you covered 😉
Code example
```js reference
https://github.com/zitadel/actions/blob/main/examples/set_custom_attribute.js
```
Set dynamic attribute from organization metadata [#set-dynamic-attribute-from-organization-metadata]
Extend the attributes by dynamically reading metadata from an organization and sets the present metadata.
Code example
```js reference
https://github.com/zitadel/actions/blob/main/examples/org_metadata_attribute.js
```
Manipulate user [#manipulate-user]
You can automate manual tasks such as assigning default roles during user creation.
Set email always verified [#set-email-always-verified]
Useful if you trust the provided information or don't want the users to verify their e-mail addresses.
Triggers [#triggers]
* Internal Authentication
* [Pre Creation](/apis/actions/internal-authentication#pre-creation)
* External Authentication
* [Pre Creation](/apis/actions/external-authentication#pre-creation)
Code example
```js reference
https://github.com/zitadel/actions/blob/main/examples/verify_email.js
```
Assign roles to users [#assign-roles-to-users]
Allows you to assign default roles to a user after the user was created or federated.
Triggers [#triggers]
* Internal Authentication
* [Post Creation](/apis/actions/internal-authentication#post-creation)
* External Authentication
* [Post Creation](/apis/actions/external-authentication#post-creation)
Code example
```js reference
https://github.com/zitadel/actions/blob/main/examples/add_user_grant.js
```
Add metadata to users [#add-metadata-to-users]
Adding metadata to users allows you to set default metadata on users.
Triggers [#triggers]
* Internal Authentication
* [Pre Creation](/apis/actions/internal-authentication#pre-creation)
* [Post Authentication](/apis/actions/internal-authentication#post-authentication)
* External Authentication
* [Pre Creation](/apis/actions/internal-authentication#pre-creation)
* [Post Authentication](/apis/actions/internal-authentication#post-authentication)
Code example
```js reference
https://github.com/zitadel/actions/blob/main/examples/add_metadata.js
```
Use provided fields of identity providers [#use-provided-fields-of-identity-providers]
If you want to ensure that the data of a user are always up to date, you can automatically update user fields during authentication and save time of your customers and your team.
Trigger [#trigger]
* External Authentication
* [Post Authentication](./external-authentication#post-authentication)
Fields provided by Okta as OIDC IdP [#fields-provided-by-okta-as-oidc-id-p]
If you use [Okta as an identity provider](/guides/integrate/identity-providers/okta-oidc), you can improve the onboarding experience of new users by prefilling some basic information during authentication.
Code example
```js reference
https://github.com/zitadel/actions/blob/main/examples/okta_identity_provider.js
```
Fields provided by Gitlab [#fields-provided-by-gitlab]
If you use [Gitlab as an identity provider](/guides/integrate/identity-providers/gitlab), you can improve the onboarding experience of new users by prefilling some basic information during authentication.
Code example
```js reference
https://github.com/zitadel/actions/blob/main/examples/gitlab_identity_provider.js
```
Fields provided by GitHub [#fields-provided-by-git-hub]
If you use [GitHub as an identity provider](/guides/integrate/identity-providers/github), you can improve the onboarding experience of new users by prefilling some basic information during authentication.
Code example
```js reference
https://github.com/zitadel/actions/blob/main/examples/github_identity_provider.js
```
Claims provided by a generic OIDC identity provider [#claims-provided-by-a-generic-oidc-identity-provider]
If you use a [generic OIDC identity provider](/guides/integrate/identity-providers/migrate#migrate-generic-oidc-provider), you can improve the onboarding experience of new users by prefilling some basic information during authentication.
Code example
```js reference
https://github.com/zitadel/actions/blob/main/examples/set_idp_picture_metadata.js
```
Attributes provided by Okta as SAML IDP [#attributes-provided-by-okta-as-saml-idp]
If you use [Okta as an identity provider](/guides/integrate/identity-providers/okta_saml#add-attribute-statements), you can improve the onboarding experience of new users by prefilling some basic information during authentication.
Code example
```js reference
https://github.com/zitadel/actions/blob/main/examples/okta_saml_prefil_register_form.js
```
Attributes provided by Microsoft Entra as SAML IDP [#attributes-provided-by-microsoft-entra-as-saml-idp]
If you use [Microsoft Entra as SAML identity provider](/guides/integrate/identity-providers/azure-ad-saml), you can improve the onboarding experience of new users by prefilling some basic information during authentication.
Code example
```js reference
https://github.com/zitadel/actions/blob/main/examples/entra_id_saml_prefil_register_form.js
```
Attributes provided by a generic SAML identity provider [#attributes-provided-by-a-generic-saml-identity-provider]
If you use a [SAML identity provider like mocksaml](/guides/integrate/identity-providers/mocksaml), you can improve the onboarding experience of new users by prefilling some basic information during authentication.
Code example
```js reference
https://github.com/zitadel/actions/blob/main/examples/post_auth_saml.js
```
Context-aware execution [#context-aware-execution]
Based on the context, the execution path of an action can change. ZITADEL allows complex execution paths, of course. 😎
Based on auth request information [#based-on-auth-request-information]
Execution paths might change based on the application initiating the authentication.
Triggers [#triggers]
* Internal Authentication
* [Pre Creation](/apis/actions/internal-authentication#pre-creation)
* [Post Creation](/apis/actions/internal-authentication#post-creation)
* [Post Authentication](/apis/actions/internal-authentication#post-authentication)
* External Authentication
* [Pre Creation](/apis/actions/external-authentication#pre-creation)
* [Post Creation](/apis/actions/external-authentication#post-creation)
* [Post Authentication](/apis/actions/external-authentication#post-authentication)
Code example
```js reference
https://github.com/zitadel/actions/blob/main/examples/execute_action_on_specific_app.js
```
This example uses [zitadel's log module](/apis/actions/modules#log)
Check authentication error [#check-authentication-error]
Your action can also check for errors during the login process.
Triggers [#triggers]
* Internal Authentication
* [Post Authentication](/apis/actions/internal-authentication#post-authentication)
* External Authentication
* [Post Authentication](/apis/actions/external-authentication#post-authentication)
Code example
```js reference
https://github.com/zitadel/actions/blob/main/examples/post_auth_log.js
```
This example uses [zitadel's log module](/apis/actions/modules#log)
Throw an error [#throw-an-error]
Allows you to limit the user interaction. The error thrown will be shown to the user if the action is not [allowed to fail](/concepts/features/actions#how-it-works).
Code example
```js reference
https://github.com/zitadel/actions/blob/main/examples/throw_error.js
```
# Complement Token Flow
This flow is executed during the creation of tokens and token introspection.
The flow is represented by the following Ids in the API: `2`
Pre Userinfo creation (id_token / userinfo / introspection endpoint) [#pre-userinfo-creation-id-token-userinfo-introspection-endpoint]
This trigger is called before userinfo are set in the id\_token or userinfo and introspection endpoint response.
The trigger is represented by the following Ids in the API: `4`
Parameters of Pre Userinfo creation [#parameters-of-pre-userinfo-creation]
* `ctx`
The first parameter contains the following fields:
* `v1`
* `claims` [*Claims*](./objects#claims)
* `getUser()` [*User*](./objects#user)
* `user`
* `getMetadata()` [*metadataResult*](./objects#metadata-result)
* `grants` [*UserGrantList*](./objects#user-grant-list)
* `org`
* `getMetadata()` [*metadataResult*](./objects#metadata-result)
* `api`
The second parameter contains the following fields:
* `v1`
* `userinfo`
This function is deprecated, please use `api.v1.claims`
* `setClaim(string, Any)`
Sets any value if the key is not already present. If it's already present there is a message added to `urn:zitadel:iam:action:${action.name}:log`
Note that keys with prefix `urn:zitadel:iam` will be ignored.
* `claims`
* `setClaim(string, Any)`
Sets any value if the key is not already present. If it's already present there is a message added to `urn:zitadel:iam:action:${action.name}:log`
Note that keys with prefix `urn:zitadel:iam` will be ignored.
* `user`
* `setMetadata(string, Any)`
Key of the metadata and any value
Pre access token creation [#pre-access-token-creation]
This trigger is called before the claims are set in the access token and the token type is `jwt`.
The trigger is represented by the following Ids in the API: `5`
Parameters of Pre access token creation [#parameters-of-pre-access-token-creation]
* `ctx`
The first parameter contains the following fields:
* `v1`
* `claims` [*Claims*](./objects#claims)
* `getUser()` [*User*](./objects#user)
* `user`
* `getMetadata()` [*metadataResult*](./objects#metadata-result)
* `grants` [*UserGrantList*](./objects#user-grant-list)
* `org`
* `getMetadata()` [*metadataResult*](./objects#metadata-result)
* `api`
The second parameter contains the following fields:
* `v1`
* `claims`
* `setClaim(string, Any)`
Sets any value if the key is not already present. If it's already present there is a message added to `urn:zitadel:iam:action:${action.name}:log`
Note that keys with prefix `urn:zitadel:iam` will be ignored.
* `appendLogIntoClaims(string)`
Appends the entry into the claim `urn:zitadel:action:{action.name}:log` the value of the claim is an Array of *string*
* `user`
* `setMetadata(string, Any)`
Key of the metadata and any value
# Complement SAMLResponse
This flow is executed before the return of the SAML Response.
The flow is represented by the following Ids in the API: `4`
Pre SAMLResponse creation [#pre-saml-response-creation]
This trigger is called before attributes are set in the SAMLResponse.
The trigger is represented by the following Ids in the API: `6`.
Parameters of Pre SAMLResponse creation [#parameters-of-pre-saml-response-creation]
* `ctx`
The first parameter contains the following fields:
* `v1`
* `getUser()` [*User*](./objects#user)
* `user`
* `getMetadata()` [*metadataResult*](./objects#metadata-result)
* `grants` [*UserGrantList*](./objects#user-grant-list)
* `org`
* `getMetadata()` [*metadataResult*](./objects#metadata-result)
* `api`
The second parameter contains the following fields:
* `v1`
* `attributes`
* `setCustomAttribute(string, string, ...string)`
Sets any value as attribute in addition to the default attributes, if the key is not already present. The parameters represent the key, nameFormat and the attributeValue(s).
* `user`
* `setMetadata(string, Any)`
Key of the metadata and any value
# External Authentication Flow
This flow is executed if the user logs in using an [identity provider](/guides/integrate/identity-providers/introduction).
The flow is represented by the following Ids in the API: `FLOW_TYPE_EXTERNAL_AUTHENTICATION` and `1`
Post Authentication [#post-authentication]
A user has authenticated externally. ZITADEL retrieved and mapped the external information.
The trigger is represented by the following Ids in the API: `TRIGGER_TYPE_POST_AUTHENTICATION` or `1`.
Parameters of Post Authentication Action [#parameters-of-post-authentication-action]
* `ctx`
The first parameter contains the following fields
* `accessToken` *string*
The access token returned by the identity provider. This can be an opaque token or a JWT
* `refreshToken` *string*
The refresh token returned by the identity provider if there is one. This is most likely to be an opaque token.
* `claimsJSON()` [*idTokenClaims*](../openidoauth/claims)
Returns all claims of the id token
* `getClaim(key)` *Any*
Returns the requested [id token claim](../openidoauth/claims)
* `idToken` *string*
The id token provided by the identity provider.
* `v1`
* `externalUser` [*externalUser*](./objects#external-user)
* `authError` *string*
This is a verification errors string representation. If the verification succeeds, this is "none"
* `authRequest` [*auth request*](/apis/actions/objects#auth-request)
* `httpRequest` [*http request*](/apis/actions/objects#http-request)
* `providerInfo` *Any*
Returns the response of the provider. In case the provider is a Generic OAuth Provider, the information is accessible through:
* `rawInfo` *Any*
* `org`
* `getMetadata()` [*metadataResult*](./objects#metadata-result)
* `api`
The second parameter contains the following fields
* `v1`
* `user`
* `appendMetadata(string, Any)`
The first parameter represents the key and the second a value which will be stored
* `setFirstName(string)`
Sets the first name
* `setLastName(string)`
Sets the last name
* `setNickName(string)`
Sets the nickname
* `setDisplayName(string)`
Sets the display name
* `setPreferredLanguage(string)`
Sets the preferred language. Please use the format defined in [RFC 5646](https://www.rfc-editor.org/rfc/rfc5646)
* `setPreferredUsername(string)`
Sets the preferred username
* `setEmail(string)`
Sets the email address of the user
* `setEmailVerified(boolean)`
Sets the email address verified or unverified
* `setPhone(string)`
Sets the phone number of the user
* `setPhoneVerified(boolean)`
Sets the phone number verified or unverified
* `metadata`
Array of [*metadata*](./objects#metadata-with-value-as-bytes). This function is deprecated, please use `api.v1.user.appendMetadata`
Pre Creation [#pre-creation]
A user selected **Register** on the overview page after external authentication. ZITADEL did not create the user yet.
The trigger is represented by the following Ids in the API: `TRIGGER_TYPE_PRE_CREATION` or `2`.
Parameters of Pre Creation [#parameters-of-pre-creation]
* `ctx`
The first parameter contains the following fields
* `v1`
* `user` [*(human)*](./objects#human-user)
* `authRequest` [*auth request*](/apis/actions/objects#auth-request)
* `httpRequest` [*http request*](/apis/actions/objects#http-request)
* `org`
* `getMetadata()` [*metadataResult*](./objects#metadata-result)
* `api`
The second parameter contains the following fields
* `metadata`
Array of [*metadata*](./objects#metadata-with-value-as-bytes). This function is deprecated, please use `api.v1.user.appendMetadata`
* `setFirstName(string)`
Sets the first name
* `setLastName(string)`
Sets the last name
* `setNickName(string)`
Sets the nickname
* `setDisplayName(string)`
Sets the display name
* `setPreferredLanguage(string)`
Sets the preferred language, the string has to be a valid language tag as defined in [RFC 5646](https://www.rfc-editor.org/rfc/rfc5646)
* `setGender(int)`
Sets the gender.
0: unspecified
1: female
2: male
3: diverse
* `setUsername(string)`
Sets the username
* `setEmail(string)`
Sets the email
* `setEmailVerified(bool)`
If true the email set is verified without user interaction
* `setPhone(string)`
Sets the phone number
* `setPhoneVerified(bool)`
If true the phone number set is verified without user interaction
* `v1`
* `user`
* `appendMetadata(string, Any)`
The first parameter represents the key and the second a value which will be stored
Post Creation [#post-creation]
A user selected **Register** on the overview page after external authentication and ZITADEL successfully created the user.
The trigger is represented by the following Ids in the API: `TRIGGER_TYPE_POST_CREATION` or `3`.
Parameters of Post Creation [#parameters-of-post-creation]
* `ctx`
The first parameter contains the following fields
* `v1`
* `getUser()` [*user*](./objects#user)
* `authRequest` [*auth request*](/apis/actions/objects#auth-request)
* `httpRequest` [*http request*](/apis/actions/objects#http-request)
* `org`
* `getMetadata()` [*metadataResult*](./objects#metadata-result)
* `api`
The second parameter contains the following fields
* `userGrants` Array of [*userGrant*](./objects#user-grant)'s
* `v1`
* `appendUserGrant(`[`userGrant`](./objects#user-grant)`)`
# Internal Authentication Flow
This flow is executed if the user logs in using the login UI hosted by ZITADEL.
The flow is represented by the following Ids in the API: `3`
Post Authentication [#post-authentication]
A user has authenticated directly at ZITADEL.
ZITADEL validated the users inputs for password, OTP or passkey.
Each validation step triggers the action.
The trigger is represented by the following Ids in the API: `TRIGGER_TYPE_POST_AUTHENTICATION` or `1`.
Parameters of Post Authentication Action [#parameters-of-post-authentication-action]
* `ctx`
The first parameter contains the following fields
* `v1`
* `authMethod` *string*
This is one of "password", "OTP", "U2F" or "passwordless"
* `authError` *string*
This is a verification errors string representation. If the verification succeeds, this is "none"
* `authRequest` [*auth request*](/apis/actions/objects#auth-request)
* `httpRequest` [*http request*](/apis/actions/objects#http-request)
* `api`
The second parameter contains the following fields
* `metadata`
Array of [*metadata*](./objects#metadata-with-value-as-bytes). This function is deprecated, please use `api.v1.user.appendMetadata`
* `v1`
* `user`
* `appendMetadata(string, Any)`
The first parameter represents the key and the second a value which will be stored
Pre Creation [#pre-creation]
A user registers directly at ZITADEL.
ZITADEL did not create the user yet.
The trigger is represented by the following Ids in the API: `TRIGGER_TYPE_PRE_CREATION` or `2`.
Parameters of Pre Creation [#parameters-of-pre-creation]
* `ctx`
The first parameter contains the following fields
* `v1`
* `user` [*human*](./objects#human-user)
* `authRequest` [*auth request*](/apis/actions/objects#auth-request)
* `httpRequest` [*http request*](/apis/actions/objects#http-request)
* `api`
The second parameter contains the following fields
* `metadata`
Array of [*metadata*](./objects#metadata-with-value-as-bytes). This function is deprecated, please use `api.v1.user.appendMetadata`
* `setFirstName(string)`
Sets the first name
* `setLastName(string)`
Sets the last name
* `setNickName(string)`
Sets the nickname
* `setDisplayName(string)`
Sets the display name
* `setPreferredLanguage(string)`
Sets the preferred language, the string has to be a valid language tag as defined in [RFC 5646](https://www.rfc-editor.org/rfc/rfc5646)
* `setGender(int)`
Sets the gender.
0: unspecified
1: female
2: male
3: diverse
* `setUsername(string)`
Sets the username
* `setEmail(string)`
Sets the email
* `setEmailVerified(bool)`
If true the email set is verified without user interaction
* `setPhone(string)`
Sets the phone number
* `setPhoneVerified(bool)`
If true the phone number set is verified without user interaction
* `v1`
* `user`
* `appendMetadata(string, Any)`
The first parameter represents the key and the second a value which will be stored
Post Creation [#post-creation]
A user registers directly at ZITADEL.
ZITADEL successfully created the user.
The trigger is represented by the following Ids in the API: `TRIGGER_TYPE_POST_CREATION` or `3`.
Parameters of Post Creation [#parameters-of-post-creation]
* `ctx`
The first parameter contains the following fields
* `v1`
* `getUser()` [*user*](./objects#user)
* `authRequest` [*auth request*](/apis/actions/objects#auth-request)
* `httpRequest` [*http request*](/apis/actions/objects#http-request)
* `api`
The second parameter contains the following fields
* `userGrants` Array of [*userGrant*](./objects#user-grant)'s
* `v1`
* `appendUserGrant(`[`userGrant`](./objects#user-grant)`)`
# Modules
ZITADEL provides the following modules.
HTTP [#http]
This module provides functionality to call REST APIs.
Import [#import]
```js
let http = require('zitadel/http')
```
`fetch()` function [#fetch-function]
This function allows to call HTTP servers. The function does NOT fulfil the [Fetch API specification](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
Parameters [#parameters]
* `url` *string*
* `options`
**Optional**, containing custom settings that you want to apply to the request.
* `headers`
Overwrites the default headers. One of the following types
* *map\[string] string*
The value is split into separate values after each comma `,`.
* *map\[string] Array of string*
The value is a string array
* default:
* `Content-Type`: `application/json`
* `Accept`: `application/json`
* `method`
The request method. Allowed values are `GET`, `POST`, `PUT`, `DELETE`
* `body` *Object*
JSON representation
Response [#response]
If the request was invalid, an error will be thrown, otherwise a Response object will be returned.
The object has the following fields and methods:
* `status` *number*
Status code of response
* `body` *string*
Return value
* `json()` *Object*
Returns the body as JSON object, or throws an error if the body is not a json object.
* `text()` *string*
Returns the body
Example [#example]
```js reference
https://github.com/zitadel/actions/blob/main/examples/make_api_call.js#L10-L20
```
Log [#log]
The log module provides you with the functionality to log to stdout.
Import [#import]
```js
let logger = require("zitadel/log")
```
`log()`, `warn()`, `error()` function [#log-warn-error-function]
The logger offers three distinct log levels (info, warn, and error) to effectively communicate messages based on their severity, enhancing debugging and troubleshooting efficiency.
Use the function that reflects your log level.
* log()
* warn()
* error()
Example [#example]
```js
logger.log("This is an info log.")
logger.warn("This is a warn log.")
logger.error("This is an error log.")
```
Parameters [#parameters]
* `msg` *string*
The message you want to print out.
UUID [#uuid]
This module provides functionality to generate a UUID
Import [#import]
```js
let uuid = require("zitadel/uuid")
```
`uuid.vX()` function [#uuid-v-x-function]
This function generates a UUID using [google/uuid](https://github.com/google/uuid). `vX` allows to define the UUID version:
* `uuid.v1()` *string*
Generates a UUID version 1, based on date-time and MAC address
* `uuid.v3(namespace, data)` *string*
Generates a UUID version 3, based on the provided namespace using MD5
* `uuid.v4()` *string*
Generates a UUID version 4, which is randomly generated
* `uuid.v5(namespace, data)` *string*
Generates a UUID version 5, based on the provided namespace using SHA1
Parameters [#parameters]
* `namespace` *UUID*/*string*
Namespace to be used in the hashing function. Either provide one of defined [namespaces](#namespaces) or a string representing a UUID.
* `data` *\[]byte*/*string*
data to be used in the hashing function. Possible types are \[]byte or string.
Namespaces [#namespaces]
The following predefined namespaces can be used for `uuid.v3` and `uuid.v5`:
* `uuid.namespaceDNS` *UUID*
6ba7b810-9dad-11d1-80b4-00c04fd430c8
* `uuid.namespaceURL` *UUID*
6ba7b811-9dad-11d1-80b4-00c04fd430c8
* `uuid.namespaceOID` *UUID*
6ba7b812-9dad-11d1-80b4-00c04fd430c8
* `uuid.namespaceX500` *UUID*
6ba7b814-9dad-11d1-80b4-00c04fd430c8
Example [#example]
```js
let uuid = require("zitadel/uuid")
function setUUID(ctx, api) {
if (api.metadata === undefined) {
return;
}
api.v1.user.appendMetadata('custom-id', uuid.v4());
}
```
# Objects
External User [#external-user]
* `externalId` *string*
User id from the identity provider
* `externalIdpId` *string*
ID of the identity provider
* `human`
* `firstName` *string*
* `lastName` *string*
* `nickName` *string*
* `displayName` *string*
* `preferredLanguage` *string*
In [RFC 5646](https://www.rfc-editor.org/rfc/rfc5646) format
* `email` *string*
* `isEmailVerified` *boolean*
* `phone` *string*
* `isPhoneVerified` *boolean*
metadata with value as bytes [#metadata-with-value-as-bytes]
* `key` *string*
* `value` Array of *byte*
metadata result [#metadata-result]
* `count` *number*
* `sequence` *number*
* `timestamp` *Date*
* `metadata` Array of [*metadata*](#metadata)
metadata [#metadata]
* `creationDate` *Date*
* `changeDate` *Date*
* `resourceOwner` *string*
* `sequence` *number*
* `key` *string*
* `value` `Any`
user grant [#user-grant]
A user grant is a role assignment for a user.
* `projectID` *string*
Required. ID of the project for the role assignment.
* `projectGrantID` *string*
Optional. If the role assignment is for a project grant, include projectGrantID
* `roles` Array of *string*
Roles to assign to the user.
user [#user]
* `id` *string*
* `creationDate` *Date*
* `changeDate` *Date*
* `resourceOwner` *string*
* `sequence` *number*
Unsigned 64-bit integer
* `state` *number*
0: unspecified
1: active
2: inactive
3: deleted
4: locked
5: suspended
6: initial
* `username` *string*
* `loginNames` Array of *string*
* `preferredLoginName` *string*
* `human`\
Set if User (Human)
* `firstName` *string*
* `lastName` *string*
* `nickName` *string*
* `displayName` *string*
* `avatarKey` *string*
* `preferredLanguage` *string*
In [RFC 5646](https://www.rfc-editor.org/rfc/rfc5646) format
* `gender` *number*
0: unspecified
1: female
2: male
3: diverse
* `email` *string*
* `isEmailVerified` *boolean*
* `phone` *string*
* `isPhoneVerified` *boolean*
* `machine`
Set if the user is a machine
* `name` *string*
* `description` *string*
human user [#human-user]
* `id` *string*
* `creationDate` *Date*
* `changeDate` *Date*
* `resourceOwner` *string*
* `sequence` *number*
* `state` *number*
* `selectedIdpConfigId` *string*
* `linkingUsers` Array of [*ExternalUser*](#external-user)
* `passwordVerified` *bool*
* `mfasVerified` Array of *Number*
0: OTP
1: U2F
2: U2F User verification
* `audience` Array of *string*
* `authTime` *Date*
HTTP Request [#http-request]
This object is based on the Golang struct [http.Request](https://pkg.go.dev/net/http#Request), some attributes are removed as not all provided information is usable in this context.
* `method` *string*
* `url` *string*
* `proto` *string*
* `contentLength` *number*
* `host` *string*
* `form` Map *string* of Array of *string*
* `postForm` Map *string* of Array of *string*
* `remoteAddr` *string*
* `headers` Map *string* of Array of *string*
Claims [#claims]
This object represents [the claims](../openidoauth/claims) which will be written into the oidc token.
* `sub` *string*
* `name` *string*
* `email` *string*
* `locale` *string*
* `given_name` *string*
* `family_name` *string*
* `preferred_username` *string*
* `email_verified` *bool*
* `updated_at` *Number*
There could be additional fields depending on the settings of your [project](../../guides/manage/console/projects-overview#role-settings) and your [application](../../guides/manage/console/applications-overview#token-settings)
user grant list [#user-grant-list]
This object represents a list of user grants (role assignments) stored in ZITADEL.
* `count` *Number*
* `sequence` *Number*
* `timestamp` *Date*
* `grants` Array of
* `id` *string*
* `projectGrantId` *string*
The id of the [project grant](/guides/solution-scenarios/saas#project-grant)
* `state` *Number*
0: unspecified
1: active
2: inactive
3: removed
* `creationDate` *Date*
* `changeDate` *Date*
* `sequence` *Number*
* `userId` *string*
* `roles` Array of *string*
* `userResourceOwner` *string*
The id of the organization of the user
* `userGrantResourceOwner` *string*
The id of the organization, where the user was granted
* `userGrantResourceOwnerName` *string*
The name of the organization, where the user was granted
* `projectId` *string*
* `projectName` *string*
* `getOrgMetadata()` [*metadataResult*](#metadata-result)
Get the metadata of the organization where the user was granted
# Core Resources
ZITADEL provides multiple APIs to manage the system, instances and resources such as users, projects and more.
There are different versions and multiple services available:
# zitadel/assets
{/*
Code generated by assets generator. DO NOT EDIT.
This file is checked in to support building docs without a Go environment.
*/}
AssetsService [#assets-service]
UploadDefaultLabelPolicyFont() [#upload-default-label-policy-font]
> UploadDefaultLabelPolicyFont()
POST: /instance/policy/label/font
GetDefaultLabelPolicyFont() [#get-default-label-policy-font]
> GetDefaultLabelPolicyFont()
GET: /instance/policy/label/font
GetPreviewDefaultLabelPolicyFont() [#get-preview-default-label-policy-font]
> GetPreviewDefaultLabelPolicyFont()
GET: /instance/policy/label/font/\_preview
UploadDefaultLabelPolicyIcon() [#upload-default-label-policy-icon]
> UploadDefaultLabelPolicyIcon()
POST: /instance/policy/label/icon
UploadDefaultLabelPolicyIconDark() [#upload-default-label-policy-icon-dark]
> UploadDefaultLabelPolicyIconDark()
POST: /instance/policy/label/icon/dark
GetDefaultLabelPolicyIcon() [#get-default-label-policy-icon]
> GetDefaultLabelPolicyIcon()
GET: /instance/policy/label/icon
GetDefaultLabelPolicyIconDark() [#get-default-label-policy-icon-dark]
> GetDefaultLabelPolicyIconDark()
GET: /instance/policy/label/icon/dark
GetPreviewDefaultLabelPolicyIcon() [#get-preview-default-label-policy-icon]
> GetPreviewDefaultLabelPolicyIcon()
GET: /instance/policy/label/icon/\_preview
GetPreviewDefaultLabelPolicyIconDark() [#get-preview-default-label-policy-icon-dark]
> GetPreviewDefaultLabelPolicyIconDark()
GET: /instance/policy/label/icon/dark/\_preview
UploadDefaultLabelPolicyLogo() [#upload-default-label-policy-logo]
> UploadDefaultLabelPolicyLogo()
POST: /instance/policy/label/logo
UploadDefaultLabelPolicyLogoDark() [#upload-default-label-policy-logo-dark]
> UploadDefaultLabelPolicyLogoDark()
POST: /instance/policy/label/logo/dark
GetDefaultLabelPolicyLogo() [#get-default-label-policy-logo]
> GetDefaultLabelPolicyLogo()
GET: /instance/policy/label/logo
GetDefaultLabelPolicyLogoDark() [#get-default-label-policy-logo-dark]
> GetDefaultLabelPolicyLogoDark()
GET: /instance/policy/label/logo/dark
GetPreviewDefaultLabelPolicyLogo() [#get-preview-default-label-policy-logo]
> GetPreviewDefaultLabelPolicyLogo()
GET: /instance/policy/label/logo/\_preview
GetPreviewDefaultLabelPolicyLogoDark() [#get-preview-default-label-policy-logo-dark]
> GetPreviewDefaultLabelPolicyLogoDark()
GET: /instance/policy/label/logo/dark/\_preview
UploadOrgLabelPolicyFont() [#upload-org-label-policy-font]
> UploadOrgLabelPolicyFont()
POST: /org/policy/label/font
GetOrgLabelPolicyFont() [#get-org-label-policy-font]
> GetOrgLabelPolicyFont()
GET: /org/policy/label/font
GetPreviewOrgLabelPolicyFont() [#get-preview-org-label-policy-font]
> GetPreviewOrgLabelPolicyFont()
GET: /org/policy/label/font/\_preview
UploadOrgLabelPolicyIcon() [#upload-org-label-policy-icon]
> UploadOrgLabelPolicyIcon()
POST: /org/policy/label/icon
UploadOrgLabelPolicyIconDark() [#upload-org-label-policy-icon-dark]
> UploadOrgLabelPolicyIconDark()
POST: /org/policy/label/icon/dark
GetOrgLabelPolicyIcon() [#get-org-label-policy-icon]
> GetOrgLabelPolicyIcon()
GET: /org/policy/label/icon
GetOrgLabelPolicyIconDark() [#get-org-label-policy-icon-dark]
> GetOrgLabelPolicyIconDark()
GET: /org/policy/label/icon/dark
GetPreviewOrgLabelPolicyIcon() [#get-preview-org-label-policy-icon]
> GetPreviewOrgLabelPolicyIcon()
GET: /org/policy/label/icon/\_preview
GetPreviewOrgLabelPolicyIconDark() [#get-preview-org-label-policy-icon-dark]
> GetPreviewOrgLabelPolicyIconDark()
GET: /org/policy/label/icon/dark/\_preview
UploadOrgLabelPolicyLogo() [#upload-org-label-policy-logo]
> UploadOrgLabelPolicyLogo()
POST: /org/policy/label/logo
UploadOrgLabelPolicyLogoDark() [#upload-org-label-policy-logo-dark]
> UploadOrgLabelPolicyLogoDark()
POST: /org/policy/label/logo/dark
GetOrgLabelPolicyLogo() [#get-org-label-policy-logo]
> GetOrgLabelPolicyLogo()
GET: /org/policy/label/logo
GetOrgLabelPolicyLogoDark() [#get-org-label-policy-logo-dark]
> GetOrgLabelPolicyLogoDark()
GET: /org/policy/label/logo/dark
GetPreviewOrgLabelPolicyLogo() [#get-preview-org-label-policy-logo]
> GetPreviewOrgLabelPolicyLogo()
GET: /org/policy/label/logo/\_preview
GetPreviewOrgLabelPolicyLogoDark() [#get-preview-org-label-policy-logo-dark]
> GetPreviewOrgLabelPolicyLogoDark()
GET: /org/policy/label/logo/dark/\_preview
UploadMyUserAvatar() [#upload-my-user-avatar]
> UploadMyUserAvatar()
POST: /users/me/avatar
GetMyUserAvatar() [#get-my-user-avatar]
> GetMyUserAvatar()
GET: /users/me/avatar
# Benchmarks
Benchmarks are crucial to understand if ZITADEL fulfills your expected workload and what resources it needs to do so.
This document explains the process and goals of load-testing zitadel in a cloud environment.
The results can be found on sub-pages.
Goals [#goals]
The primary goal is to assess if ZITADEL can scale to required proportion. The goals might change over time and maturity of ZITADEL. At the moment the goal is to assess how the application’s performance scales. There are some concrete goals we have to meet:
1. [https://github.com/zitadel/zitadel/issues/8352](https://github.com/zitadel/zitadel/issues/8352) defines 1000 JWT profile auth/sec
2. [https://github.com/zitadel/zitadel/issues/4424](https://github.com/zitadel/zitadel/issues/4424) defines 1200 logins / sec.
Procedure [#procedure]
First we determine the “target” of our load-test. The target is expressed as a make recipe in the load-test [Makefile](https://github.com/zitadel/zitadel/blob/main/load-test/Makefile). See also the load-test [readme](https://github.com/zitadel/zitadel/blob/main/load-test/README.md) on how to configure and run load-tests.
A target should be tested for longer periods of time, as it might take time for certain metrics to show up. For example, cloud SQL samples query insights. A runtime of at least **30 minutes** is advised at the moment.
After each iteration of load-test, we should consult the [After test procedure](#after-test-procedure) to conclude an outcome:
1. Scale
2. Log potential issuer and scale
3. Terminate testing and resolve issues
Methodology [#methodology]
Benchmark definition [#benchmark-definition]
Tests are implemented in the ecosystem of [k6](https://k6.io). The tests are publicly available in the [zitadel repository](https://github.com/zitadel/zitadel/tree/main/load-test). Custom extensions of k6 are implemented in the [xk6-modules repository](https://github.com/zitadel/xk6-modules).
The tests must at least measure the request duration for each API call. This gives an indication on how zitadel behaves over the duration of the load test.
Metrics [#metrics]
The following metrics must be collected for each test iteration. The metrics are used to follow the decision path of the [After test procedure](https://drive.google.com/open?id=1WVr7aA8dGgV1zd2jUg1y1h_o37mkZF2O6M5Mhafn_NM):
| Metric | Type | Description | Unit |
| :------------------------------------ | :---------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Baseline | Comparison | Defines the baseline the test is compared against. If not specified the baseline defined in this document is used. | Link to test result |
| Purpose | Description | Description what should been proved with this test run | text |
| Test start | Setup | Timestamp when the test started. This is useful for gathering additional data like metrics or logs later | Date |
| Test duration | Setup | Duration of the test | Duration |
| Executed test | Setup | Name of the make recipe executed. Further information about specific test cases can be found [here](?tab=t.0#heading=h.xav4f3s5r2f3). | Name of the make recipe |
| k6 version | Setup | Version of the test client (k6) used | semantic version |
| VUs | Setup | Virtual Users which execute the test scenario in parallel | Number |
| Client location | Setup | Region or location of the machine which executed the test client. If not further specified the hoster is Google Cloud | Location / Region |
| Client machine specification | Setup | Definition of the client machine the test client ran on. The resources of the machine could be maxed out during tests therefore we collect this metric as well. The description must at least clarify the following metrics: vCPU Memory egress bandwidth | **vCPU**: Amount of threads ([additional info](https://cloud.google.com/compute/docs/cpu-platforms)) **memory**: GB **egress bandwidth**:Gbps |
| ZITADEL location | Setup | Region or location of the deployment of zitadel. If not further specified the hoster is Google Cloud | Location / Region |
| ZITADEL container specification | Setup | As ZITADEL is mainly run in cloud environments it should also be run as a container during the load tests. The description must at least clarify the following metrics: vCPU Memory egress bandwidth Scale | **vCPU**: Amount of threads ([additional info](https://cloud.google.com/compute/docs/cpu-platforms)) **memory**: GB **egress bandwidth**:Gbps **scale**: The amount of containers running during the test. The amount must not vary during the tests |
| ZITADEL Version | Setup | The version of zitadel deployed | Semantic version or commit |
| ZITADEL Configuration | Setup | Configuration of zitadel which deviates from the defaults and is not secret | yaml |
| ZITADEL feature flags | Setup | Changed feature flags | yaml |
| Database | Setup | Database type and version | **type**: psql **version**: semantic version |
| Database location | Setup | Region or location of the deployment of the database. If not further specified the hoster is Google Cloud SQL | Location / Region |
| Database specification | Setup | The description must at least clarify the following metrics: vCPU, Memory and egress bandwidth (Scale) | **vCPU**: Amount of threads ([additional info](https://cloud.google.com/compute/docs/cpu-platforms)) **memory**: GB **egress bandwidth**:Gbps |
| ZITADEL metrics during test | Result | This metric helps understanding the bottlenecks of the executed test. At least the following metrics must be provided: CPU usage Memory usage | **CPU usage** in percent **Memory usage** in percent |
| Observed errors | Result | Errors worth mentioning, mostly unexpected errors | description |
| Top 3 most expensive database queries | Result | The execution plan of the top 3 most expensive database queries during the test execution | database execution plan |
| Database metrics during test | Result | This metric helps understanding the bottlenecks of the executed test. At least the following metrics must be provided: CPU usage Memory usage | **CPU usage** in percent **Memory usage** in percent |
| k6 Iterations per second | Result | How many test iterations were done per second | Number |
| k6 overview | Result | Shows some basic metrics aggregated over the test run At least the following metrics must be included: duration per request (min, max, avg, p50, p95, p99) VUS For simplicity just add the whole test result printed to the terminal | terminal output |
| k6 output | Result | Trends and metrics generated during the test, this contains detailed information for each step executed during each iteration | csv |
Test setup [#test-setup]
Make recipes [#make-recipes]
Details about the tests implemented can be found in [this readme](https://github.com/zitadel/zitadel/blob/main/load-test/README.md#test).
Test conclusion [#test-conclusion]
After each iteration of load-test, we should consult the [Flowchart](#after-test-procedure) to conclude an outcome:
1. [Scale](#scale)
2. [Log potential issue and scale](#potential-issues)
3. [Terminate testing](#termination) and resolve issues
Scale [#scale]
An outcome of scale means that the service hit some kind of resource limit, like CPU or RAM which can be increased. In such cases we increase the suggested parameter and rerun the load-test for the same target. On the next test we should analyze if the increase in scale resulted in a performance improvement proportional to the scale parameter. For example if we scale from 1 to 2 containers, it might be reasonable to expect a doubling of iterations / sec. If such an increase is not noticed, there might be another bottleneck or underlying issue, such as locking.
Potential issues [#potential-issues]
A potential issue has an impact on performance, but does not prevent us to scale. Such issues must be logged in GH issues and load-testing can continue. The issue can be resolved at a later time and the load-tests repeated when it is. This is primarily for issues which require big changes to ZITADEL.
Termination [#termination]
Scaling no longer improves iterations / second, or some kind of critical error or bug is experienced. The root cause of the issue must be resolved before we can continue with increasing scale.
After test procedure [#after-test-procedure]
This flowchart shows the procedure after running a test.
Baseline [#baseline]
Will be established as soon as the goal described above is reached.
Test results [#test-results]
This chapter provides a table linking to the detailed test results.
# ZITADEL Ready and Health Endpoints
ZITADEL exposes a `Ready`- and `Healthy` endpoint to allow external systems like load balancers, orchestration systems, uptime probes and others to check the status.
Ready [#ready]
The `Ready` endpoint is located on the path `/debug/ready` and allows systems to probe if a ZITADEL process is ready to serve and accept traffic.
This endpoint is useful for operations like [zero downtime upgrade](../../concepts/architecture/solution#zero-downtime-updates) since it allows systems like Kubernetes to verify that ZITADEL is working on something (e.g. database schema migration) but is not yet ready to accept traffic.
In Kubernetes this is called the `readinessProbe`.
Healthy [#healthy]
The `Health` endpoint is located on the path `/debug/healthz` and allows systems to probe if a ZITADEL process is still alive.
This helps system like kubernetes or a load balancer to observe if the process is still alive to accept traffic.
In Kubernetes this is called the `livenessProbe`.
# ZITADEL Metrics
ZITADEL provides a `metrics` endpoint with the help of the [opentelemetry-go](https://github.com/open-telemetry/opentelemetry-go) package.
If you are self-hosting ZITADEL, you can access this endpoint with on the path `/debug/metrics`.
For example when running ZITADEL locally the endpoint is accessible on `http://localhost:8080/debug/metrics`.
The metrics endpoint can be scrubbed by any tool of choice that supports the `otel` format, e.g. an existing Prometheus.
For our [Kubernetes/Helm](/self-hosting/deploy/kubernetes) users, we provide an out-of-the-box support for the [ServiceMonitor](https://github.com/zitadel/zitadel-charts/blob/main/charts/zitadel/templates/servicemonitor.yaml) custom resource.
By default, metrics are enabled but can be turned off through ZITADEL's [settings](/self-hosting/manage/configure).
The (default) settings are located in the [defaults.yaml](https://github.com/zitadel/zitadel/blob/main/cmd/defaults.yaml).
# Authentication Methods in ZITADEL
Client Secret Basic [#client-secret-basic]
When using `client_secret_basic` on token or introspection endpoints, provide an`Authorization` header with a Basic auth value in the following form:
```markdown
Authorization: "Basic " + base64( formUrlEncode(client_id) + ":" + formUrlEncode(client_secret) )
```
Given the client\_id `78366401571920522@amce` and client\_secret `veryweaksecret!`, this would result in the following `Authorization` header:
`Basic NzgzNjY0MDE1NzE5MjA1MjIlNDBhbWNlOnZlcnl3ZWFrc2VjcmV0JTIx`
JWT with Private Key [#jwt-with-private-key]
When using `private_key_jwt` (`urn:ietf:params:oauth:client-assertion-type:jwt-bearer`) for token or introspection endpoints, provide a JWT as assertion generated with the following structure and signed with a downloaded key:
***
Key JSON
| Key | Example | Description |
| :------- | :------------------------------------------------------------------ | :----------------------------------------------------------------------------- |
| type | `"application"` | The type of account, right now only application is valid |
| keyId | `"81693565968962154"` | This is unique ID of the key |
| key | `"-----BEGIN RSA PRIVATE KEY-----...-----END RSA PRIVATE KEY-----"` | The private key generated by ZITADEL, this can not be regenerated! |
| clientId | `78366401571920522@acme` | The client\_id of the application, this is the same as the subject from tokens |
| appId | `78366403256846242` | The id of the application (just for completeness, not used for JWT) |
```json
{
"type": "application",
"keyId": "81693565968962154",
"key": "-----BEGIN RSA PRIVATE KEY-----...-----END RSA PRIVATE KEY-----",
"clientId": "78366401571920522@acme",
"appId": "78366403256846242"
}
```
***
JWT
| Claim | Example | Description |
| :---- | :--------------------------- | :-------------------------------------------------------------------------------------------------------------- |
| aud | `"https://${CUSTOM_DOMAIN}"` | String or Array of intended audiences MUST include ZITADEL's issuing domain |
| exp | `1605183582` | Unix timestamp of the expiry |
| iat | `1605179982` | Unix timestamp of the creation singing time of the JWT, MUST NOT be older than 1h |
| iss | `"78366401571920522@acme"` | String which represents the requesting party (owner of the key), normally the `clientID` from the json key file |
| sub | `"78366401571920522@acme"` | The subject ID of the application, normally the `clientID` from the json key file |
```json
{
"iss": "78366401571920522@acme",
"sub": "78366401571920522@acme",
"aud": "https://${CUSTOM_DOMAIN}",
"exp": 1605183582,
"iat": 1605179982
}
```
> To identify your key, it is necessary that you provide a JWT with a `kid` header claim representing your keyId from the Key JSON:
>
> ```json
> {
> "alg": "RS256",
> "kid": "81693565968962154"
> }
> ```
# ZITADEL OIDC Authentication Request Playground
The OIDC Playground is for testing OpenID Authentication Requests, giving you more insight how OpenID Connect works and how you can customize ZITADEL behavior with different parameters.
An OpenID Connect (OIDC) [authentication request](https://openid.net/specs/openid-connect-core-1_0.html) is an OAuth 2.0 Authorization Request using additional parameters and scopes to request that the end-user be authenticated by ZITADEL.
Go to the OIDC Playground
Why this OIDC playground? [#why-this-oidc-playground]
Currently, ZITADEL requires human users to authenticate through the hosted login page.
Your application should initiate a login by issuing an authentication request and redirecting the user to the login page. You can customize the behavior of ZITADEL by providing additional parameters and scopes in the request.
This playground should help you to initially craft an authentication request and to explore the behavior of ZITADEL in more depth.
Request parameters explained [#request-parameters-explained]
Not all request parameters are available in the playground. Please refer to the full documentation of the [authorization endpoint](/apis/openidoauth/endpoints#authorization-endpoint).
Your Domain [#your-domain]
The Custom Domain to your ZITADEL instance. Use the base-path, the playground will add the required path to the request.
Required Parameters [#required-parameters]
Client ID is the resource id of an
application. It's the application where you want your users to login. You can
find the Client ID in the Console. When using project grants, use the
Client ID from the origin organization.
Redirect URI be one of the
pre-configured redirect uris for your application. You must add the redirect
uri for your application, else you will receive an error.
Response Type defines whether a code,
id\_token token or just id\_token will be returned. Most use cases will need
code.
More in the documentation about required Parameters.
Authentication methods [#authentication-methods]
Depending on the authentication and authorization flow of your application you might need to append some information to the authentication request.
Authentication method "(none) PKCE" is recommended
for most application types. The playground appends automatically a code challenge
for PKCE flows.
You need to append a "Code Challenge" by providing a random Code Verifier that is being hashed and encoded in the request to the token endpoint, please see our [guide](/guides/integrate/login/oidc/oauth-recommended-flows#our-recommended-authorization-flows) for more details.
More in the [documentation](/apis/openidoauth/authn-methods) about authentication methods.
Additional Parameters [#additional-parameters]
Prompt defines if and how the user
should be prompted on login. For example:
select\_account: user is prompted to select one of the
existing sessions or create a new one
create: present the register form
login: requires the user to re-authenticate
none: user must be authenticated without interaction, an
error is returned otherwise; use for silent-refresh
Login hint must be a valid logon name
of a user. You can skip the account picker by providing the Login hint.
There are many more additional parameters. Please refer to the [documentation](/apis/openidoauth/endpoints#additional-parameters) about additional parameters.
Standard Scopes [#standard-scopes]
Used to request additional information from ZITADEL.
These scopes are defined in the OpenID Connect specification.
The `openid` scope is mandatory.
Not all scopes are available in the playground. Please refer to the full [documentation](/apis/openidoauth/scopes) for the exhaustive list of available standard and reserved scopes.
Reserved Scopes [#reserved-scopes]
You can request additional information that is specific to ZITADEL or customize the behavior of ZITADEL by including reserved scopes.
Please refer to the [documentation](/apis/openidoauth/scopes#reserved-scopes) for a full list of available reserved scopes.
Organization policies and branding [#organization-policies-and-branding]
Enforce an organization's policies and branding as well as membership of the user by passing the scope `urn:zitadel:iam:org:id:{id}` with the required Organization ID.
Please refer to the full [guide on branding](/guides/manage/customize/branding).
Get user metadata [#get-user-metadata]
Pass the scope `urn:zitadel:iam:user:metadata` to request a user's metadata.
Please refer to the full [guide on user-metadata](/guides/manage/customize/user-metadata) for further details.
Access core APIs [#access-core-ap-is]
Calling the [core API](/apis/introduction) with the authenticated user, requires that the projectID of ZITADEL is included in the audience claim.
This can be achieved by adding the scope `urn:zitadel:iam:org:project:id:zitadel:aud` to your applications authorization request.
How to use ZITADEL in your project [#how-to-use-zitadel-in-your-project]
Please refer to our [guide](/guides/integrate/login/oidc/login-users) on how to login users.
OpenID Connect certified libraries should allow you to customize the parameters and define scopes for the authorization request. You can also continue by using one of our [example applications](/sdk-examples/introduction).
# Claims in ZITADEL
ZITADEL asserts claims on different places according to the corresponding specifications or project and applications settings.
Please check below the matrix for an overview where which scope is asserted.
| Claims | Userinfo | Introspection | ID Token | Access Token |
| :------------------------------------------------- | :------------- | --------------------------------------- | ----------------------------------------------- | ---------------------------------------------------- |
| acr | No | No | Yes | No |
| act | No | After Token Exchange with `actor_token` | After Token Exchange with `actor_token` | When JWT and after Token Exchange with `actor_token` |
| address | When requested | When requested | When requested and response\_type `id_token` | No |
| amr | No | No | Yes | No |
| aud | No | Yes | Yes | When JWT |
| auth\_time | No | No | Yes | No |
| azp (client\_id when Introspect) | No | Yes | Yes | When JWT |
| email | When requested | When requested | When requested and response\_type `id_token` | No |
| email\_verified | When requested | When requested | When requested and response\_type `id_token` | No |
| exp | No | Yes | Yes | When JWT |
| family\_name | When requested | When requested | When requested and response\_type `id_token` | No |
| gender | When requested | When requested | When requested and response\_type `id_token` | No |
| given\_name | When requested | When requested | When requested and response\_type `id_token` | No |
| iat | No | Yes | Yes | When JWT |
| iss | No | Yes | Yes | When JWT |
| jti | No | Yes | No | When JWT |
| locale | When requested | When requested | When requested and response\_type `id_token` | No |
| name | When requested | When requested | When requested and response\_type `id_token` | No |
| nbf | No | Yes | No | When JWT |
| nonce | No | No | When provided in the authorization request [^1] | No |
| phone | When requested | When requested | When requested and response\_type `id_token` | No |
| phone\_verified | When requested | When requested | When requested and response\_type `id_token` | No |
| preferred\_username (username when Introspect) | When requested | When requested | Yes | No |
| sid | No | No | Yes | No |
| sub | Yes | Yes | Yes | When JWT |
| urn:zitadel:iam:org:domain:primary:\{domainname} | When requested | When requested | When requested | When JWT and requested |
| urn:zitadel:iam:org:project:roles | When requested | When requested | When requested or configured | When JWT and requested or configured |
| urn:zitadel:iam:user:metadata | When requested | When requested | When requested | When JWT and requested |
| urn:zitadel:iam:user:resourceowner:id | When requested | When requested | When requested | When JWT and requested |
| urn:zitadel:iam:user:resourceowner:name | When requested | When requested | When requested | When JWT and requested |
| urn:zitadel:iam:user:resourceowner:primary\_domain | When requested | When requested | When requested | When JWT and requested |
[^1]: The nonce can also be used to distinguish between an id\_token and a logout\_token as latter must never include a nonce.
Standard Claims [#standard-claims]
| Claims | Example | Description |
| :------------------ | :------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| acr | TBA | TBA |
| act | `{"iss": "${CUSTOM_DOMAIN}","sub": "259241944654282754"}` | JSON object describing the actor from the `actor_token` after [token exchange](/guides/integrate/token-exchange#actor-token) |
| address | `Lerchenfeldstrasse 3, 9014 St. Gallen` | TBA |
| amr | `pwd mfa` | Authentication Method References as defined in [RFC8176](https://tools.ietf.org/html/rfc8176) `password` value is deprecated, please check `pwd` |
| aud | `69234237810729019` | The audience of the token, by default all client id's and the project id are included |
| auth\_time | `1311280969` | Unix time of the authentication |
| azp | `69234237810729234` | Client id of the client who requested the token |
| email | `road.runner@acme.ch` | Email Address of the subject |
| email\_verified | `true` | Boolean if the email was verified by ZITADEL |
| events | `{ "http://schemas.openid.net/event/backchannel-logout": {} }` | Security Events such as Back-Channel Logout |
| exp | `1311281970` | Time the token expires (as unix time) |
| family\_name | `Runner` | Last name of the subject |
| family\_name | `Runner` | Last name of the subject |
| gender | `other` | Gender of the subject |
| given\_name | `Road` | First name of the subject |
| given\_name | `Road` | First name of the subject |
| iat | `1311280970` | Time of the token was issued at (as unix time) |
| iss | `${CUSTOM_DOMAIN}` | Issuing domain of a token |
| jti | `69234237813329048` | Unique id of the token |
| locale | `en` | Language from the subject |
| name | `Road Runner` | The subjects full name |
| nbf | `1311280970` | Time the token must not be used before (as unix time) |
| nonce | `blQtVEJHNTF0WHhFQmhqZ0RqeHJsdzdkd2d...` | The nonce provided by the client |
| phone | `+41 79 XXX XX XX` | Phone number provided by the user |
| phone\_verified | `true` | Boolean if the phone was verified by ZITADEL |
| preferred\_username | `road.runner@acme.caos.ch` | ZITADEL's login name of the user. Consist of `username@primarydomain` |
| sid | `291693710356251044` | String identifier for a session. This represents a session of a user agent for a logged-in end-User. Different sid values are used to identify distinct sessions at an OP. |
| sub | `77776025198584418` | Subject ID of the user |
Custom Claims [#custom-claims]
Custom claims are being inserted into user tokens in addition to the standard claims.
Your app can use custom claims to handle more complex scenarios, such as restricting access based on these claims.
You can add custom claims using the [complement token flow](/apis/actions/complement-token) of the [actions feature](/guides/manage/console/actions-overview).
Multiple examples of Actions that result in custom claims can be found in our [Marketplace for ZITADEL Actions](https://github.com/zitadel/actions).
Static values as custom claim [#static-values-as-custom-claim]
```javascript reference
https://github.com/zitadel/actions/blob/de69b56f6d0463817953b59a52ffd6afc6a366fb/examples/add_claim.js#L9-L11
```
Metadata as custom claim [#metadata-as-custom-claim]
```javascript reference
https://github.com/zitadel/actions/blob/main/examples/add_metadata.js#L9-L15
```
Format roles claims [#format-roles-claims]
```javascript reference
https://github.com/zitadel/actions/blob/main/examples/custom_roles.js#L20-L33
```
Reserved Claims [#reserved-claims]
ZITADEL reserves some claims to assert certain data. Please check out the [reserved scopes](./scopes#reserved-scopes).
| Claims | Example | Description |
| :------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| urn:zitadel:iam:action:\{actionname}:log | `{"urn:zitadel:iam:action:appendCustomClaims:log": ["test log", "another test log"]}` | This claim is set during Actions as a log, e.g. if two custom claims with the same keys are set. |
| urn:zitadel:iam:org:domain:primary:\{domainname} | `{"urn:zitadel:iam:org:domain:primary": "acme.ch"}` | This claim represents the Organization Domain the user belongs to. |
| urn:zitadel:iam:org:project:roles | `{"urn:zitadel:iam:org:project:roles": [ {"user": {"id1": "acme.zitade.ch", "id2": "caos.ch"} } ] }` | When roles are asserted, ZITADEL does this by providing the `id` and `primaryDomain` below the role. This gives you the option to check in which organization a user has the role on the current project (where your application belongs to). |
| urn:zitadel:iam:org:project:\{projectid}:roles | `{"urn:zitadel:iam:org:project:id3:roles": [ {"user": {"id1": "acme.zitade.ch", "id2": "caos.ch"} } ] }` | When roles are asserted, ZITADEL does this by providing the `id` and `primaryDomain` below the role. This gives you the option to check in which organization a user has the role on a specific project. |
| urn:zitadel:iam:user:metadata | `{"urn:zitadel:iam:user:metadata": [ {"key": "VmFsdWU=" } ] }` | The metadata claim will include all metadata of a user. The values are base64 encoded. |
| urn:zitadel:iam:user:resourceowner:id | `{"urn:zitadel:iam:user:resourceowner:id": "orgid"}` | This claim represents the user's organization ID. |
| urn:zitadel:iam:user:resourceowner:name | `{"urn:zitadel:iam:user:resourceowner:name": "ACME"}` | This claim represents the user's organization's name. |
| urn:zitadel:iam:user:resourceowner:primary\_domain | `{"urn:zitadel:iam:user:resourceowner:primary_domain": "acme.ch"}` | This claim represents the user's Organization Domain. |
# OpenID Connect Endpoints in ZITADEL
OpenID Connect 1.0 Discovery [#open-id-connect-1-0-discovery]
The OpenID Connect Discovery Endpoint is located within the issuer domain.
This would give us `${CUSTOM_DOMAIN}/.well-known/openid-configuration`.
**Link to spec.** [OpenID Connect Discovery 1.0 incorporating errata set 1](https://openid.net/specs/openid-connect-discovery-1_0.html)
authorization_endpoint [#authorization-endpoint]
`${CUSTOM_DOMAIN}/oauth/v2/authorize`
The authorization\_endpoint is located with the login page, due to the need of accessing the same cookie domain
The authorization\_endpoint is the starting point for all initial user authentications. The user agent (browser) will be redirected to this endpoint to
authenticate the user in exchange for an authorization\_code (authorization code flow) or tokens (implicit flow).
Links to specs
Required request parameters [#required-request-parameters]
| Parameter | Description |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| client\_id | The id of your client as shown in Console. |
| redirect\_uri | Callback uri of the authorization request where the code or tokens will be sent to. Must match exactly one of the preregistered in Console. |
| response\_type | Determines whether a `code`, `id_token token` or just `id_token` will be returned. Most use cases will need `code`. See flow guide for more info. |
| scope | `openid` is required, see [Scopes](./scopes) for more possible values. Scopes are space delimited, e.g. `openid email profile` |
Following the [OIDC Core 1.0 specs](https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims) whenever an access\_token is issued,
the id\_token will not contain any claims of the scopes `profile`, `email`, `phone` and `address`.
Send the access\_token to the [userinfo\_endpoint](#userinfo-endpoint) or [introspection\_endpoint](#introspection-endpoint) the retrieve these claims
or set the `id_token_userinfo_assertion` Option ("User Info inside ID Token" in Management Console) to true.
Depending on your authorization method you will have to provide additional parameters or headers:
no additional parameters required
no additional parameters required
| Parameter | Description |
| ----------------------- | ----------------------------------------------------- |
| code\_challenge | The SHA-256 value of the generated `code_verifier` |
| code\_challenge\_method | Method used to generate the challenge, must be `S256` |
see PKCE guide for more information
no additional parameters required
Additional parameters [#additional-parameters]
| Parameter | Description |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| id\_token\_hint | Valid `id_token` (of an existing session) used to identity the subject. **SHOULD** be provided when using prompt `none`. |
| login\_hint | A valid logon name of a user. Will be used for username inputs or preselecting a user on `select_account`. Be sure to encode the hint correctly using url encoding (especially when using `+` or alike in the loginname) |
| max\_age | Seconds since the last active successful authentication of the user |
| nonce | Random string value to associate the client session with the ID Token and for replay attacks mitigation. **MUST** be provided when using **implicit flow**. |
| prompt | If the Auth Server prompts the user for (re)authentication. no prompt: the user will have to choose a session if more than one session exists `none`: user must be authenticated without interaction, an error is returned otherwise `login`: user must reauthenticate / provide a user name `select_account`: user is prompted to select one of the existing sessions or create a new one `create`: the registration form will be displayed to the user directly |
| state | Opaque value used to maintain state between the request and the callback. Used for Cross-Site Request Forgery (CSRF) mitigation as well, therefore highly **recommended**. |
| ui\_locales | Spaces delimited list of preferred locales for the login UI, e.g. `de-CH de en`. If none is provided or matches the possible locales provided by the login UI, the `accept-language` header of the browser will be taken into account. |
| response\_mode | The mechanism to be used for returning parameters to the application. See [response modes](#response-modes) for valid values. Invalid values are ignored. |
Response modes [#response-modes]
ZITADEL supports the following `response_mode` values. When no response mode is requested, the response mode is chosen based on the configured Response Type of the application.
As per [OpenID Connect Core 1.0, Section 3.1.2.1](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest):
> The use of this parameter is NOT RECOMMENDED when the Response Mode that would be requested is the default mode specified for the Response Type.
| Response Mode | Description |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| query | Encode the returned parameters in the URL query string. This is the default when the Response type is `code`, for example [Web applications](/guides/manage/console/applications-overview#web). |
| fragment | Encode the returned parameters in the URL fragment. This is the default when the Response Type is `id_token`, for example implicit [User Agent apps](/guides/manage/console/applications-overview#user-agent). This mode will not work for server-side applications, because fragments are never sent by the browser to the server. |
| form\_post[^1] | ZITADEL serves a small JavaScript to the browser which will send the returned parameters to the `redirect_uri` using HTTP POST. This mode only works for server-side applications and user agents which support / allow JavaScript. |
[^1]: Implements [OAuth 2.0 Form Post Response Mode](https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html)
Successful code response [#successful-code-response]
When your `response_type` was `code` and no error occurred, the following response will be returned:
| Property | Description |
| -------- | ----------------------------------------------------------------------------- |
| code | Opaque string which will be necessary to request tokens on the token endpoint |
| state | Unmodified `state` parameter from the request |
Successful implicit response [#successful-implicit-response]
When your `response_type` was either `id_token` or `id_token token` and no error occurred, the following response will be returned:
| Property | Description |
| ------------- | ------------------------------------------------------------------------------------- |
| access\_token | Only returned if `response_type` included `token` |
| expires\_in | Number of second until the expiration of the `access_token` |
| id\_token | An `id_token` of the authorized user |
| token\_type | Type of the `access_token`. Value is always `Bearer` |
| scope | Scopes of the `access_token`. These might differ from the provided `scope` parameter. |
| state | Unmodified `state` parameter from the request |
Error response [#error-response]
Regardless of the authorization flow chosen, if an error occurs the following response will be returned to the redirect\_uri.
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
| Property | Description |
| ------------------ | -------------------------------------------------------------------- |
| error | An OAuth / OIDC [error\_type](#authorize-errors) |
| error\_description | Description of the error type or additional information of the error |
| state | Unmodified `state` parameter from the request |
Possible errors [#possible-errors]
| error\_type | Possible reason |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| invalid\_request | The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. |
| invalid\_scope | The requested scope is invalid. Typically the required `openid` value is missing. |
| unauthorized\_client | The client is not authorized to request an access\_token using this method. Check in Management Console that the requested `response_type` is allowed in your application settings. |
| unsupported\_response\_type | The authorization server does not support the requested response\_type. |
| server\_error | The authorization server encountered an unexpected condition that prevented it from fulfilling the request. |
| interaction\_required | The authorization server requires end-user interaction of some form to proceed. This error MAY be returned when the prompt parameter value in the Authentication Request is none, but the Authentication Request cannot be completed without displaying a user interface for end-user interaction. |
| login\_required | The authorization server requires end-user authentication. This error MAY be returned when the prompt parameter value in the Authentication Request is none, but the Authentication Request cannot be completed without displaying a user interface for end-user authentication. |
token_endpoint [#token-endpoint]
`${CUSTOM_DOMAIN}/oauth/v2/token`
The token\_endpoint will as the name suggests return various tokens (access, id and refresh) depending on the used `grant_type`.
When using [`authorization_code`](#authorization-code-grant-code-exchange) flow call this endpoint after receiving the code from the authorization\_endpoint.
When using [`refresh_token`](#authorization-code-grant-code-exchange) or [`urn:ietf:params:oauth:grant-type:jwt-bearer` (JWT Profile)](#jwt-profile-grant) you will call this endpoint directly.
Authorization code grant (Code Exchange) [#authorization-code-grant-code-exchange]
As mention above, when using `authorization_code` grant, this endpoint will be your second request for authorizing a user with its user agent (browser).
Required request parameters [#required-request-parameters]
| Parameter | Description |
| ------------- | -------------------------------------------------------------------------------------------------------------- |
| code | Code that was issued from the authorization request. |
| grant\_type | Must be `authorization_code` |
| redirect\_uri | Callback uri where the code was be sent to. Must match exactly the redirect\_uri of the authorization request. |
Depending on your authorization method you will have to provide additional parameters or headers:
Send your `client_id` and `client_secret` as Basic Auth Header. Check [Client Secret Basic Auth Method](./authn-methods#client-secret-basic) on how to build it correctly.
Send your `client_id` and `client_secret` as parameters in the body:
| Parameter | Description |
| -------------- | --------------------------------- |
| client\_id | client\_id of the application |
| client\_secret | client\_secret of the application |
Send your `client_id` and `code_verifier` for us to recompute the `code_challenge` of the authorization request.
| Parameter | Description |
| -------------- | -------------------------------------------------------------- |
| client\_id | client\_id of the application |
| code\_verifier | code\_verifier previously used to generate the code\_challenge |
Send a client assertion as JWT for us to validate the signature against the registered public key.
| Parameter | Description |
| ----------------------- | -------------------------------------------------------------------------------------------------------------- |
| client\_assertion | JWT built and signed according to [Using JWTs for Client Authentication](./authn-methods#jwt-with-private-key) |
| client\_assertion\_type | Must be `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
Successful code response [#successful-code-response]
| Property | Description |
| -------------- | ------------------------------------------------------------------------------------- |
| access\_token | An `access_token` as JWT or opaque token |
| expires\_in | Number of second until the expiration of the `access_token` |
| id\_token | An `id_token` of the authorized user |
| scope | Scopes of the `access_token`. These might differ from the provided `scope` parameter. |
| refresh\_token | An opaque token. Only returned if `offline_access` scope was requested |
| token\_type | Type of the `access_token`. Value is always `Bearer` |
JWT profile grant [#jwt-profile-grant]
Required request parameters [#required-request-parameters]
| Parameter | Description |
| ----------- | -------------------------------------------------------------------------------------------------------------------------- |
| grant\_type | Must be `urn:ietf:params:oauth:grant-type:jwt-bearer` |
| assertion | JWT built and signed according to [Using JWTs for Authorization Grants](./grant-types#using-jw-ts-as-authorization-grants) |
| scope | [Scopes](./scopes) you would like to request from ZITADEL. Scopes are space delimited, e.g. `openid email profile` |
```bash
curl --request POST \
--url ${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 assertion=eyJhbGciOiJSUzI1Ni...
```
Successful JWT profile response [#successful-jwt-profile-response]
| Property | Description |
| ------------- | ------------------------------------------------------------------------------------- |
| access\_token | An `access_token` as JWT or opaque token |
| expires\_in | Number of second until the expiration of the `access_token` |
| id\_token | An `id_token` of the authorized service account |
| scope | Scopes of the `access_token`. These might differ from the provided `scope` parameter. |
| token\_type | Type of the `access_token`. Value is always `Bearer` |
Refresh token grant [#refresh-token-grant]
To request a new `access_token` without user interaction, you can use the `refresh_token` grant.
See [offline\_access Scope](./scopes#standard-scopes) for how to request a `refresh_token` in the authorization request.
Required request parameters [#required-request-parameters]
| Parameter | Description |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| grant\_type | Must be `refresh_token` |
| refresh\_token | The refresh\_token previously issued in the last authorization\_code or refresh\_token request. |
| scope | [Scopes](./scopes) you would like to request from ZITADEL for the new access\_token. Must be a subset of the scope originally requested by the corresponding auth request. When omitted, the scopes requested by the original auth request will be reused. Scopes are space delimited, e.g. `openid email profile` |
Depending on your authorization method you will have to provide additional parameters or headers:
Send your `client_id` and `client_secret` as Basic Auth Header. Check [Client Secret Basic Auth Method](./authn-methods#client-secret-basic) on how to build it correctly.
Send your `client_id` and `client_secret` as parameters in the body:
| Parameter | Description |
| -------------- | --------------------------------- |
| client\_id | client\_id of the application |
| client\_secret | client\_secret of the application |
Send your `client_id` as parameter in the body. No authentication is required.
Send a `client_assertion` as JWT for us to validate the signature against the registered public key.
| Parameter | Description |
| ----------------------- | -------------------------------------------------------------------------------------------------------------- |
| client\_assertion | JWT built and signed according to [Using JWTs for Client Authentication](./authn-methods#jwt-with-private-key) |
| client\_assertion\_type | Must be `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
Successful refresh token response [#successful-refresh-token-response]
| Property | Description |
| -------------- | ------------------------------------------------------------------------------------- |
| access\_token | An `access_token` as JWT or opaque token |
| expires\_in | Number of second until the expiration of the `access_token` |
| id\_token | An `id_token` of the authorized user |
| scope | Scopes of the `access_token`. These might differ from the provided `scope` parameter. |
| refresh\_token | An new opaque refresh\_token. |
| token\_type | Type of the `access_token`. Value is always `Bearer` |
Client credentials grant [#client-credentials-grant]
Required request parameters [#required-request-parameters]
| Parameter | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------ |
| grant\_type | Must be `client_credentials` |
| scope | [Scopes](./scopes) you would like to request from ZITADEL. Scopes are space delimited, e.g. `openid profile` |
Additionally, you need to authenticate your client by either sending `client_id` and `client_secret` as Basic Auth Header.
Check [Client Secret Basic Auth Method](./authn-methods#client-secret-basic) on how to build it correctly.
```bash
curl --request POST \
--url ${CUSTOM_DOMAIN}/oauth/v2/token \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic ${BASIC_AUTH}' \
--data grant_type=client_credentials \
--data scope=openid profile
```
Or you can also send your `client_id` and `client_secret` as parameters in the body:
| Parameter | Description |
| -------------- | --------------------------------- |
| client\_id | client\_id of the application |
| client\_secret | client\_secret of the application |
```bash
curl --request POST \
--url ${CUSTOM_DOMAIN}/oauth/v2/token \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=client_credentials \
--data client_id=${CLIENT_ID} \
--data client_secret=${CLIENT_SECRET} \
--data scope=openid profile
```
Successful client credentials response [#successful-client-credentials-response]
| Property | Description |
| ------------- | ------------------------------------------------------------------------------------- |
| access\_token | An `access_token` as JWT or opaque token |
| expires\_in | Number of second until the expiration of the `access_token` |
| scope | Scopes of the `access_token`. These might differ from the provided `scope` parameter. |
| token\_type | Type of the `access_token`. Value is always `Bearer` |
Token Exchange grant [#token-exchange-grant]
The Token Exchange grant implements [RFC 8693, OAuth 2.0 Token Exchange](https://www.rfc-editor.org/rfc/rfc8693) and can be used to exchange tokens to a different scope, audience or subject. Changing the subject of an authenticated token is called impersonation or delegation. ZITADEL also provides a [token exchange guide](/guides/integrate/token-exchange) with more details on using the Token Exchange Grant.
Request parameters [#request-parameters]
Depending on your authorization method you will have to provide additional parameters or headers:
Send your `client_id` and `client_secret` as Basic Auth Header. Check [Client Secret Basic Auth Method](./authn-methods#client-secret-basic) on how to build it correctly.
Send your `client_id` and `client_secret` as parameters in the body:
| Parameter | Description |
| -------------- | --------------------------------- |
| client\_id | client\_id of the application |
| client\_secret | client\_secret of the application |
Send your `client_id` as parameter in the body. No authentication is required.
Send a `client_assertion` as JWT for us to validate the signature against the registered public key.
| Parameter | Description |
| ----------------------- | -------------------------------------------------------------------------------------------------------------- |
| client\_assertion | JWT built and signed according to [Using JWTs for Client Authentication](./authn-methods#jwt-with-private-key) |
| client\_assertion\_type | Must be `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
Successful token exchange response [#successful-token-exchange-response]
Token types [#token-types]
Error response [#error-response]
| error\_type | Possible reason |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| invalid\_request | The request is missing a required parameter, includes an unsupported parameter value (other than grant type), repeats a parameter, includes multiple credentials, utilizes more than one mechanism for authenticating the client, or is otherwise malformed. |
| invalid\_scope | The requested scope is invalid, unknown, malformed, or exceeds the scope granted by the resource owner. |
| unauthorized\_client | The authenticated client is not authorized to use this authorization grant type. |
| unsupported\_grant\_type | The authorization grant type is not supported by the authorization server. |
| server\_error | The authorization server encountered an unexpected condition that prevented it from fulfilling the request. |
| invalid\_grant | The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. |
| invalid\_client | Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method). |
introspection_endpoint [#introspection-endpoint]
`${CUSTOM_DOMAIN}/oauth/v2/introspect`
This endpoint enables clients to validate an `acccess_token`, either opaque or JWT. Unlike client side JWT validation,
this endpoint will check if the token is not revoked (by client or logout).
| Parameter | Description |
| --------- | --------------- |
| token | An access token |
Depending on your authorization method you will have to provide additional parameters or headers:
Send your `client_id` and `client_secret` as Basic Auth Header. Check [Client Secret Basic Auth Method](./authn-methods#client-secret-basic) on how to build it correctly.
```bash
curl --request POST \
--url ${CUSTOM_DOMAIN}/oauth/v2/introspect \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic {your_basic_auth_header}' \
--data token=VjVxyCZmRmWYqd3_F5db9Pb9mHR5fqzhn...
```
Send a `client_assertion` as JWT for us to validate the signature against the registered public key.
| Parameter | Description |
| ----------------------- | ------------------------------------------------------------------------------------------------------------- |
| client\_assertion | JWT built and signed according to [Using JWTs for Client Authentication](./authn-methods#client-secret-basic) |
| client\_assertion\_type | must be `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
```bash
curl --request POST \
--url ${CUSTOM_DOMAIN}/oauth/v2/introspect \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
--data client_assertion=eyJhbGciOiJSUzI1Ni... \
--data token=VjVxyCZmRmWYqd3_F5db9Pb9mHR5fqzhn...
```
Successful introspection response [#successful-introspection-response]
Upon successful authorization of the client a response with the boolean `active` is returned, indicating if the provided token
is active and the requesting client is part of the token audience.
If `active` is **true**, further information will be provided:
| Property | Description |
| ----------- | --------------------------------------------------------------------- |
| aud | The audience of the token |
| client\_id | The client\_id of the application the token was issued to |
| exp | Time the token expires (as unix time) |
| iat | Time of the token was issued at (as unix time) |
| iss | Issuer of the token |
| jti | Unique id of the token |
| nbf | Time the token must not be used before (as unix time) |
| scope | Space delimited list of scopes granted to the token |
| token\_type | Type of the inspected token. Value is always `Bearer` |
| username | ZITADEL's login name of the user. Consist of `username@primarydomain` |
Additionally and depending on the granted scopes, information about the authorized user is provided.
Check the [Claims](./claims) page if a specific claims might be returned and for detailed description.
Error response [#error-response]
If the authorization fails, an HTTP 401 with `invalid_client` will be returned.
userinfo_endpoint [#userinfo-endpoint]
`${CUSTOM_DOMAIN}/oidc/v1/userinfo`
This endpoint will return information about the authorized user.
Send the `access_token` of the **user** (not the client) as Bearer Token in the `authorization` header:
```bash
curl --request GET \
--url ${CUSTOM_DOMAIN}/oidc/v1/userinfo
--header 'Authorization: Bearer dsfdsjk29fm2as...'
```
Successful userinfo response [#successful-userinfo-response]
If the `access_token` is valid, the information about the user depending on the granted scopes is returned.
Check the [Claims](./claims) page if a specific claims might be returned and for detailed description.
Error response [#error-response]
If the token is invalid or expired, an HTTP 401 will be returned.
revocation_endpoint [#revocation-endpoint]
`${CUSTOM_DOMAIN}/oauth/v2/revoke`
This endpoint enables clients to revoke an `access_token` or `refresh_token` they have been granted.
If you revoke an `access_token` only the specific token will be revoked. When revoking a `refresh_token`,
the corresponding `access_token` will be revoked as well.
| Parameter | Description |
| --------- | -------------------------------- |
| token | An access token or refresh token |
Depending on your authorization method you will have to provide additional parameters or headers:
Send your `client_id` and `client_secret` as Basic Auth Header. Check [Client Secret Basic Auth Method](./authn-methods#client-secret-basic) on how to construct a request correctly.
Send your `client_id` and `client_secret` as parameters in the body:
| Parameter | Description |
| -------------- | --------------------------------- |
| client\_id | client\_id of the application |
| client\_secret | client\_secret of the application |
Send your `client_id` as parameters in the body:
| Parameter | Description |
| ---------- | ----------------------------- |
| client\_id | client\_id of the application |
Send a `client_assertion` as JWT for ZITADEL to verify the signature against the registered public key.
| Parameter | Description |
| ----------------------- | --------------------------------------------------------------------------------------------------------------- |
| client\_assertion | JWT created and signed according to [Using JWTs for Client Authentication](./authn-methods#client-secret-basic) |
| client\_assertion\_type | must be `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
```bash
curl --request POST \
--url ${CUSTOM_DOMAIN}/oauth/v2/revoke \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
--data client_assertion=eyJhbGciOiJSUzI1Ni... \
--data token=VjVxyCZmRmWYqd3_F5db9Pb9mHR5fqzhn...
```
end_session_endpoint [#end-session-endpoint]
`${CUSTOM_DOMAIN}/oidc/v1/end_session`
The endpoint has to be opened in the user agent (browser) to terminate the user sessions.
No parameters are needed apart from the user agent cookie, but you can provide the following to customize the behavior:
| Parameter | Description |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| id\_token\_hint | the id\_token that was previously issued to the client |
| client\_id | client\_id of the application |
| post\_logout\_redirect\_uri | Callback uri of the logout where the user (agent) will be redirected to. Must match exactly one of the preregistered in Console. |
| state | Opaque value used to maintain state between the request and the callback |
| logout\_hint | A valid login name of a user. Will be used to select the user to logout. Only supported when using the login UI V2. |
| ui\_locales | Spaces delimited list of preferred locales for the login UI, e.g. `de-CH de en`. If none is provided or matches the possible locales provided by the login UI, the `accept-language` header of the browser will be taken into account. |
The `post_logout_redirect_uri` will be checked against the previously registered uris of the client provided by the `azp` claim of the `id_token_hint` or the `client_id` parameter.
If both parameters are provided, they must be equal.
If neither an `id_token_hint` nor a `client_id` parameter is provided, the `post_logout_redirect_uri` will be ignored.
jwks_uri [#jwks-uri]
`${CUSTOM_DOMAIN}/oauth/v2/keys`
The endpoint returns a JSON Web Key Set (JWKS) containing the public keys that can be used to locally validate JWTs you received from ZITADEL.
The alternative would be to validate tokens with the [introspection endpoint](#introspection-endpoint).
Key rotation [#key-rotation]
Keys are automatically rotated on a regular basis or on demand, meaning keys can change in irregular intervals.
ZITADEL ensures that a proper `kid` is set with each key.
Be aware that these keys can be rotated without any prior notice.
Caching [#caching]
You can optimize performance of your clients by caching the response from the keys endpoint.
We recommend to regularly update the cached response, since the [keys can be rotated without prior notice](#key-rotation).
You could also combine caching with a risk-based on-demand refresh when a critical operation is executed.
Without caching you will call this endpoint on each request.
This might result in being rate limited for a large number of requests that come from the same backend.
OAuth 2.0 metadata [#o-auth-2-0-metadata]
**ZITADEL** does not yet provide a OAuth 2.0 Metadata endpoint but instead provides a [OpenID Connect Discovery Endpoint](https://openid.net/specs/openid-connect-discovery-1_0.html).
# Grant Types in ZITADEL
For a list of supported or unsupported `Grant Types` please have a look at the table below.
| Grant Type | Supported |
| :---------------------------------------------------- | :-------- |
| Authorization Code | yes |
| Authorization Code with PKCE | yes |
| Client Credentials | yes |
| Device Authorization | yes |
| Implicit | yes |
| JSON Web Token (JWT) Profile | yes |
| Refresh Token | yes |
| Resource Owner Password Credentials | no |
| Security Assertion Markup Language (SAML) 2.0 Profile | no |
| Token Exchange | yes |
Authorization Code [#authorization-code]
**Link to spec.** [The OAuth 2.0 Authorization Framework Section 1.3.1](https://tools.ietf.org/html/rfc6749#section-1.3.1)
Proof Key for Code Exchange [#proof-key-for-code-exchange]
**Link to spec.** [Proof Key for Code Exchange by OAuth Public Clients](https://tools.ietf.org/html/rfc7636)
Implicit [#implicit]
**Link to spec.** [The OAuth 2.0 Authorization Framework Section 1.3.2](https://tools.ietf.org/html/rfc6749#section-1.3.2)
Client Credentials [#client-credentials]
**Link to spec.** [The OAuth 2.0 Authorization Framework Section 1.3.4](https://tools.ietf.org/html/rfc6749#section-1.3.4)
Refresh Token [#refresh-token]
**Link to spec.** [The OAuth 2.0 Authorization Framework Section 1.5](https://tools.ietf.org/html/rfc6749#section-1.5)
JSON Web Token (JWT) Profile [#json-web-token-jwt-profile]
**Link to spec.** [JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants](https://tools.ietf.org/html/rfc7523)
Using JWTs as Authorization Grants [#using-jw-ts-as-authorization-grants]
Our service account works with the JWT profile to authenticate them against ZITADEL.
1. Create or use an existing service account
2. Create a new key and download it
3. Generate a JWT with the structure below and sign it with the downloaded key
4. Send the JWT Base64 encoded to ZITADEL's token endpoint
5. Use the received access token
***
Key JSON
| Key | Example | Description |
| :----- | :------------------------------------------------------------------ | :------------------------------------------------------------------- |
| type | `"serviceaccount"` | The type of account, right now only serviceaccount is valid |
| keyId | `"81693565968772648"` | This is unique ID of the key |
| key | `"-----BEGIN RSA PRIVATE KEY-----...-----END RSA PRIVATE KEY-----"` | The private key generated by ZITADEL, this can not be regenerated! |
| userId | `78366401571647008` | The service accounts ID, this is the same as the subject from tokens |
```json
{
"type": "serviceaccount",
"keyId": "81693565968772648",
"key": "-----BEGIN RSA PRIVATE KEY-----...-----END RSA PRIVATE KEY-----",
"userId": "78366401571647008"
}
```
***
JWT
| Claim | Example | Description |
| :---- | :--------------------------- | :------------------------------------------------------------------------------------------------------------ |
| aud | `"https://${CUSTOM_DOMAIN}"` | String or Array of intended audiences MUST include ZITADEL's issuing domain |
| exp | `1605183582` | Unix timestamp of the expiry |
| iat | `1605179982` | Unix timestamp of the creation singing time of the JWT, MUST NOT be older than 1h |
| iss | `"77479219772321307"` | String which represents the requesting party (owner of the key), normally the `userId` from the json key file |
| sub | `"77479219772321307"` | The subject ID of the service account, normally the `userId` from the json key file |
```json
{
"iss": "77479219772321307",
"sub": "77479219772321307",
"aud": "https://${CUSTOM_DOMAIN}",
"exp": 1605183582,
"iat": 1605179982
}
```
> To identify your key, it is necessary that you provide a JWT with a `kid` header claim representing your keyId from the Key JSON:
>
> ```json
> {
> "alg": "RS256",
> "kid": "81693565968772648"
> }
> ```
***
See [JWT Profile Grant on Token Endpoint](./endpoints#token-endpoint) for usage.
Using JWTs for Client Authentication [#using-jw-ts-for-client-authentication]
See how to build a [JWT for client authentication](./authn-methods#jwt-with-private-key) from the downloaded key.
Find out how to use it on the [token endpoint](./endpoints#token-endpoint) or the [introspection endpoint](./endpoints#introspection-endpoint).
Token Exchange [#token-exchange]
**Link to spec.** [OAuth 2.0 Token Exchange](https://tools.ietf.org/html/rfc8693)
Device Authorization [#device-authorization]
**Link to spec.** [OAuth 2.0 Device Authorization Grant](https://tools.ietf.org/html/rfc8628)
Security Assertion Markup Language (SAML) 2.0 Profile [#security-assertion-markup-language-saml-2-0-profile]
**Link to spec.** [Security Assertion Markup Language (SAML) 2.0 Profile for OAuth 2.0 Client Authentication and Authorization Grants](https://tools.ietf.org/html/rfc7522)
Not Supported Grant Types [#not-supported-grant-types]
Resource Owner Password Credentials [#resource-owner-password-credentials]
> Due to growing security concerns we do not support this grant type. With OAuth 2.1 it looks like this grant will be removed.
**Link to spec.** [The OAuth 2.0 Authorization Framework Section 1.3.3](https://tools.ietf.org/html/rfc6749#section-1.3.3)
# Scopes in ZITADEL
ZITADEL supports the usage of scopes as way of requesting information from the instance and also instruct ZITADEL to do certain operations.
Standard Scopes [#standard-scopes]
| Scopes | Description |
| :-------------- | ------------------------------------------------------------------------------- |
| openid | When using openid connect this is a mandatory scope |
| profile | Optional scope to request the profile of the subject |
| email | Optional scope to request the email of the subject |
| address | Optional scope to request the address of the subject |
| phone | Optional scope to request the phone of the subject |
| offline\_access | Optional scope to request a refresh\_token (only possible when using code flow) |
Reserved Scopes [#reserved-scopes]
In addition to the standard compliant scopes, we use the following scopes.
| Scopes | Example | Description |
| :------------------------------------------------ | :----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `urn:zitadel:iam:org:project:role:{rolekey}` | `urn:zitadel:iam:org:project:role:user` | By using this scope a client can request the claim `urn:zitadel:iam:org:project:roles` to be asserted when possible. As an alternative approach you can enable all roles to be asserted from the [project](/guides/manage/console/roles#role-assignments) a client belongs to. |
| `urn:zitadel:iam:org:projects:roles` | `urn:zitadel:iam:org:projects:roles` | By using this scope a client can request the claim `urn:zitadel:iam:org:project:{projectid}:roles` to be asserted for each requested project. All projects of the token audience, requested by the `urn:zitadel:iam:org:project:id:{projectid}:aud` scopes will be used. |
| `urn:zitadel:iam:org:id:{id}` | `urn:zitadel:iam:org:id:178204173316174381` | When requesting this scope **ZITADEL** will enforce that the user is a member of the selected organization. If the organization does not exist a failure is displayed. It will assert the `urn:zitadel:iam:user:resourceowner` claims. |
| `urn:zitadel:iam:org:domain:primary:{domainname}` | `urn:zitadel:iam:org:domain:primary:acme.ch` | When requesting this scope **ZITADEL** will enforce that the user is a member of the selected organization and the username is suffixed by the provided domain. If the organization does not exist a failure is displayed |
| `urn:zitadel:iam:org:roles:id:{orgID}` | `urn:zitadel:iam:org:roles:id:178204173316174381` | This scope can be used one or more times to limit the granted organization IDs in the returned roles. Unknown organization IDs are ignored. When this scope is not used, all granted organizations are returned inside the roles. |
| `urn:zitadel:iam:org:project:id:{projectid}:aud` | `urn:zitadel:iam:org:project:id:69234237810729019:aud` | By adding this scope, the requested project id will be added to the audience of the access token |
| `urn:zitadel:iam:org:project:id:zitadel:aud` | `urn:zitadel:iam:org:project:id:zitadel:aud` | By adding this scope, the ZITADEL project id will be added to the audience of the access token |
| `urn:zitadel:iam:user:metadata` | `urn:zitadel:iam:user:metadata` | By adding this scope, the metadata of the user will be included in the token. The values are base64 encoded. |
| `urn:zitadel:iam:user:resourceowner` | `urn:zitadel:iam:user:resourceowner` | By adding this scope: id, name and primary\_domain of the user's organization will be included in the token. |
| `urn:zitadel:iam:org:idp:id:{idp_id}` | `urn:zitadel:iam:org:idp:id:76625965177954913` | By adding this scope the user will directly be redirected to the identity provider to authenticate. Make sure you also send the Organization Domain scope if a custom login policy is configured. Otherwise the system will not be able to identify the identity provider. |
# SAML Endpoints in ZITADEL
SAML 2.0 metadata [#saml-2-0-metadata]
The SAML Metadata is located within the issuer domain. This would give us `${CUSTOM_DOMAIN}/saml/v2/metadata`.
This metadata contains all the information defined in the spec.
**Link to
spec.** [Metadata for the OASIS Security Assertion Markup Language (SAML) V2.0 – Errata Composite](https://www.oasis-open.org/committees/download.php/35391/sstc-saml-metadata-errata-2.0-wd-04-diff.pdf)
Certificate endpoint [#certificate-endpoint]
`${CUSTOM_DOMAIN}/saml/v2/certificate`
The certificate endpoint provides the certificate which is used to sign the responses for download, for easier use with
different service providers which want the certificate separately instead of inside the metadata.
SSO endpoint [#sso-endpoint]
`${CUSTOM_DOMAIN}/saml/v2/SSO`
The SSO endpoint is the starting point for all initial user authentications. The user agent (browser) will be redirected
to this endpoint to authenticate the user.
Supported on this endpoint or currently `urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect`
or `urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST` bindings.
**Link to
spec.** [Bindings for the OASIS Security Assertion Markup Language (SAML) V2.0 – Errata Composite](https://www.oasis-open.org/committees/download.php/35387/sstc-saml-bindings-errata-2.0-wd-05-diff.pdf)
Required request parameters [#required-request-parameters]
| Parameter | Description |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| RelayState | (Optional) ID to associate the exchange with the original request. |
| SAMLRequest | The request made to the SAML IDP. (base64 encoded) |
| SigAlg | Algorithm used to sign the request, only if binding is 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' as signature has to be provided es separate parameter. (base64 encoded) |
| Signature | Signature of the request as parameter with 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' binding. (base64 encoded) |
Successful response [#successful-response]
Depending on the content of the request the response comes back in the requested binding, but the content is the same.
| Parameter | Description |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| RelayState | ID to associate the exchange with the original request. |
| SAMLResponse | The response form the SAML IDP. (base64 encoded) |
| SigAlg | Algorithm used to sign the response, only if binding is 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' as signature has to be provided es separate parameter. (base64 encoded) |
| Signature | Signature of the response as parameter with 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' binding. (base64 encoded) |
Error response [#error-response]
Regardless of the error, the used http error code will be '200', which represents a successful request. Whereas the
response will contain a StatusCode include a message which provides more information if an error occurred.
**Link to
spec** [Assertions and Protocols for the OASIS Security Assertion Markup Language (SAML) V2.0 – Errata Composite](https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf)
Custom attributes [#custom-attributes]
Custom attributes are being inserted into SAML response if not already present.
Your app can use custom claims to handle more complex scenarios, such as restricting access based on these claims.
You can add custom attributes using the [complement SAMLresponse](/apis/actions/customize-samlresponse) of the [actions feature](/guides/manage/console/actions-overview).
Examples of Actions that result in custom attributes can be found in our [Marketplace for ZITADEL Actions](https://github.com/zitadel/actions).
# How ZITADEL Processes and Stores Secrets
In this chapter you can find information of how ZITADEL processes and stores secrets and credentials in a secure fashion.
We use the terms secret and credentials interchangeable to keep this guide lean.
Secrets Principles [#secrets-principles]
ZITADEL uses the following principles when handling Secrets across their lifecycle:
* Automate rotation
* Limit lifetime
* Show only once
* Prefer public / private key concepts (FIDO2, U2F, JWT Profile, ...)
* Irreversible hash secrets / passwords
* Encrypt secrets storage
* When sent across unsecure channels (eMail, SMS, ...)
* Forced changed through receiver
* Verify that the secret can only be used once
Secrets Storage [#secrets-storage]
By default ZITADEL stores secrets from its users, applications as well as its generated secrets like signing keys in the database.
To protect the secrets against extraction from database as well as database dumps they are encrypted with AES256.
The key used to encrypt and decrypt the secrets in the ZITADEL database is called `masterkey` and needs to be exactly 32 bytes long.
The only secrets stored outside of the Secrets Storage are the masterkey, the TLS Keys, the initial Admin User (including the password)
Secrets stored in the Secrets Storage [#secrets-stored-in-the-secrets-storage]
Public Keys [#public-keys]
ZITADEL does handle many different public keys. These include:
* FIDO2
* U2F
* JWT Profile
* Signing Keys
Due to the inherent nature of a public key being public we safeguard them against malicious key changes with our unique [eventstore concept](../eventstore/overview).
Hashed Secrets [#hashed-secrets]
ZITADEL does handle many different passwords and secrets. These include:
* User Authentication
* Password
* Client / Machine Authentication
* Client Secrets
ZITADEL hashes all Passwords and Client Secrets in an non reversible way to further reduce the risk of a Secrets Storage breach.
Passwords and secrets are always hashed with a random salt and stored as an encoded string that contains the Algorithm, its Parameters, Salt and Hash.
The storage encoding used by ZITADEL is Modular Crypt Format and a full reference can be found in our [Passwap library](https://github.com/zitadel/passwap#encoding).
The following hash algorithms are supported:
* argon2i / id[^1]
* bcrypt (Default)
* md5: implementation of md5Crypt with salt and password shuffling [^2]
* md5plain: md5 digest of a password without salt [^2]
* md5salted: md5 digest of a salted password [^2]
* phpass: md5 digest with PHPass algorithm (used in WordPress) [^2]
* drupal7: Custom hashing format used in Drupal 7 [^2]
* sha2: implementation of crypt(3) SHA-256 & SHA-512
* scrypt
* pbkdf2
[^1]: argon2 algorithms are currently disabled on ZITADEL Cloud due to its steep memory requirements.
[^2]: md5 and drupal7 are insecure and can only be used to import and verify users, not hash new passwords.
ZITADEL updates stored hashes when the configured algorithm or its parameters are updated,
the first time verification succeeds.
This allows to increase cost along with growing computing power.
ZITADEL allows to import user passwords from systems that use any of the above hashing algorithms.
Note however that by default, only `bcrypt` is enabled.
Further `Verifiers` must be enabled in the [settings](/self-hosting/manage/configure) by the system administrator.
Encrypted Secrets [#encrypted-secrets]
Some secrets cannot be hashed because they need to be used in their raw form. These include:
* Federation
* Client Secrets of Identity Providers (IdPs)
* Multi-factor Authentication
* TOTP Seed Values
* Validation Secrets
* Verifying contact information like eMail, Phonenumbers
* Verifying proof of ownership over domain names (DNS)
* Resting accounts of users (password, MFA reset, ...)
* Private Keys
* Token Signing (JWT, ...)
* Token Encryption (Opaque Bearer Tokens)
* Useragent Cookies (Session Cookies) Encryption
* CSRF Cookie Encryption
* Mail Provider
* SMTP Passwords
* SMS Provider
* Twilio API Keys
By default ZITADEL uses `RSA256` for signing purposes and `AES256` for encryption
Secrets stored outside the Secrets Storage [#secrets-stored-outside-the-secrets-storage]
Masterkey [#masterkey]
Since the Masterkey is used as means of protecting the Secrets Storage it cannot be stored in the storage.
You find [here the many ways how ZITADEL can consume the Masterkey](/self-hosting/manage/configure).
TLS Material [#tls-material]
ZITADEL does support end to end TLS as such it can consume TLS Key Material.
Please check our [TLS Modes documentation](/self-hosting/manage/tls_modes) for more details.
Admin User [#admin-user]
The initial Admin User of ZITADEL can be configured through [ZITADELs config options](/self-hosting/manage/configure).
To prevent elevated breaches ZITADEL forces the Admin Users password to be changed during the first login.
# Zitadel's Software Architecture
Zitadel is built with two essential patterns. Event Sourcing (ES) and Command and Query Responsibility Segregation (CQRS).
Due to the nature of Event Sourcing Zitadel provides the unique capability to generate a strong audit trail of ALL the things that happen to its resources, without compromising on storage cost or audit trail length.
The combination of ES and CQRS makes Zitadel eventual consistent which, from our perspective, is a great benefit in many ways.
It allows us to build a Source of Records (SOR) which is the one single point of truth for all computed states.
The SOR needs to be transaction safe to make sure all operations are in order.
You can read more about this in our [ES documentation](../eventstore/overview).
Each Zitadel binary contains all components necessary to serve traffic
From serving the API, rendering GUI's, background processing of events and task.
This All in One (AiO) approach makes operating Zitadel simple.
The Architecture [#the-architecture]
Zitadel's software architecture is built around multiple components at different levels.
This chapter should give you an idea of the components as well as the different layers.
Service Layer [#service-layer]
The service layer includes all components who are potentially exposed to consumers of Zitadel.
HTTP Server [#http-server]
The http server is responsible for the following functions:
* serving the management GUI called Zitadel Management Console
* serving the static assets
* rendering server side html (login, password-reset, verification, ...)
API Server [#api-server]
The API layer consist of the multiple APIs provided by Zitadel. Each serves a dedicated purpose.
All APIs of Zitadel are always available as gRCP, gRPC-web and REST service.
The only exception is the [OpenID Connect & OAuth](/apis/openidoauth/endpoints) and [Asset API](/apis/introduction#assets) due their unique nature.
* [OpenID Connect & OAuth](/apis/openidoauth/endpoints) - allows to request authentication and authorization of Zitadel
* [SAML](/apis/saml/endpoints) - allows to request authentication and authorization of Zitadel through the SAML standard
* [Authentication API](/apis/introduction#authentication) - allow a user to do operation in its own context
* [Management API](/apis/introduction#management) - allows an admin or machine to manage the Zitadel resources on an organization level
* [Administration API](/apis/introduction#administration) - allows an admin or machine to manage the Zitadel resources on an instance level
* [System API](/apis/introduction#system) - allows to create and change new Zitadel instances
* [Asset API](/apis/introduction#assets) - is used to upload and download static assets
Core Layer [#core-layer]
Commands [#commands]
The Command Side has some unique requirements, these include:
* Transaction safety is a MUST
* Availability MUST be high
> When we classify this with the CAP theorem we would choose Consistent and Available but leave Partition Tolerance aside.
Command Handler [#command-handler]
The command handler receives all operations who alter a resource managed by Zitadel.
For example if a user changes their name. The API Layer will pass the instruction received through the API call to the command handler for further processing.
The command handler is then responsible for creating the necessary commands.
After creating the commands the command hand them down to the command validation.
Command Validation [#command-validation]
With the received commands the command validation will execute the business logic to verify if a certain action can take place.
For example if the user can really change their name is verified in the command validation.
If this succeeds the command validation will create the events that reflect the changes.
These events now are being handed down to the storage layer for storage.
Events [#events]
Zitadel handles events in two ways.
Events that should be processed in near real time are processed by an in memory pub sub system.
Some events can be handled asynchronously using the spooler.
Pub Sub [#pub-sub]
The pub sub system job is it to keep a query view up-to-date by feeding a constant stream of events to the projections.
Our pub sub system built into Zitadel works by placing events into an in memory queue for its subscribers.
There is no need for specific guarantees from the pub sub system. Since the SOR is the ES everything can be retried without loss of data.
In case of an error an event can be reapplied in two ways:
* The next event might trigger the projection to apply the whole difference
* The spooler takes care of background cleanups in a scheduled fashion
> The decision to incorporate an internal pub sub system with no need for specific guarantees is a deliberate choice.
> We believe that the toll of operating an additional external service like a MQ system negatively affects the ease of use of Zitadel as well as its availability guarantees.
> One of the authors of Zitadel did his thesis to test this approach against established MQ systems.
Spooler [#spooler]
The spoolers job is it to keep a query view up-to-date or at least look that it does not have a too big lag behind the Event Store.
Each query view has its own spooler which is responsible to look for the events who are relevant to generate the query view. It does this by triggering the relevant projection.
Spoolers are especially necessary where someone can query datasets instead of single ids.
> Each view can have exactly one spooler, but spoolers are dynamically leader elected, so even if a spooler crashes it will be replaced in a short amount of time.
Projections [#projections]
Projections are responsible for normalizing data for the query side or for analytical purpose.
They generally work by being invoked either through a scheduled spooler or the pub sub subscription.
When they receive events they will create their normalized object and then store this into the query view and its storage layer.
Queries [#queries]
The query side is responsible for answering read requests on data.
It has some unique requirements, which include:
* It needs to be easy to query
* Short response times are a MUST (80%of queries below 100ms on the api server)
* Availability MUST be high, even during high loads
* The query view MUST be able to be persisted for most request
> When we classify this with the CAP theorem we would choose **Available** and **Performance** but leave **Consistent** aside
Query Handler [#query-handler]
The query handler receives all read relevant operations. These can either be queries or simple `getById` calls.
When receiving a query it will proceed by passing this to the repository which will call the database and return the dataset.
If a request calls for a specific id the call will, most of the time, be revalidated against the Event Store.
This is achieved by triggering the projection to make sure that the last sequence of an id is loaded into the query view.
> The query side has the option to dynamically check the Event Store for newer events on a certain id to make sure for consistent responses without delay.
Query View [#query-view]
The query view is responsible to query the storage layer with the request from the command handler.
It is also responsible to execute authorization checks. To check if a request is valid and can be answered.
Storage Layer [#storage-layer]
As Zitadel itself is built completely stateless only the storage layer is needed to persist states.
The storage layer of Zitadel is responsible for multiple tasks. For example:
* Guarantee strong consistency for the command side
* Guarantee good query performance for the query side
* Backup and restore operation for disaster recovery purpose
Zitadel currently supports PostgreSQL.
Make sure to read our [Production Guide](/self-hosting/manage/production#prefer-postgre-sql) before you decide on using one of them.
Zitadel v2 supported CockroachDB and PostgreSQL. Zitadel v3 only supports PostgreSQL. Please refer to [the mirror guide](/self-hosting/manage/cli/mirror) to migrate to PostgreSQL.
# Zitadel's Deployment Architecture
High Availability [#high-availability]
Zitadel can be run as high available system with ease.
Since the storage layer takes the heavy lifting of making sure that data in synched across, server, data centers or regions.
Depending on your projects needs our general recommendation is to run Zitadel across multiple availability zones in the same region or across multiple regions.
Make sure to read our [Production Guide](/self-hosting/manage/production#prefer-postgre-sql) before you decide to use it.
Consult the [Postgres documentation](https://www.postgresql.org/docs/) for more details.
Scalability [#scalability]
Zitadel can be scaled in a linear fashion in multiple dimensions.
* Vertical on your compute infrastructure
* Horizontal in a region
* Horizontal in multiple regions
Our customers can reuse the same already known binary or container and scale it across multiple server, data center and regions.
To distribute traffic an already existing proxy infrastructure can be reused.
Simply steer traffic by path, hostname, IP address or any other metadata to the Zitadel of your choice.
> To improve your service quality we recommend steering traffic by path to different Zitadel deployments
> Feel free to [contact us](https://zitadel.com/contact/) for details
Example Deployment Architecture [#example-deployment-architecture]
Single Cluster / Region [#single-cluster-region]
A Zitadel Cluster is a highly available instance system with each component critical for serving traffic laid out at least three times.
Our storage layer (Postgres) is built for single region deployments.
Hence our reference design for Kubernetes is to have three application nodes and one storage node.
> If you are using a serverless offering like Google Cloud Run you can scale Zitadel from 0 to 1000 Pods without the need of deploying the node across multiple availability zones.
Multi Cluster / Region [#multi-cluster-region]
To scale Zitadel across regions it is recommend to create at least three clusters.
Each cluster is a fully independent ZITADEL setup.
To keep the data in sync across all clusters, we recommend using Postgres with read-only replicas as a storage layer.
Make sure to read our [Production Guide](/self-hosting/manage/production#prefer-postgre-sql) before you decide to use it.
Consult the [Postgres documentation](https://www.postgresql.org/docs/current/high-availability.html) for more details.
Zero Downtime Updates [#zero-downtime-updates]
Since an Identity system tends to be a critical piece of infrastructure, the "in place zero downtime update" is a well needed feature.
Zitadel is built in a way that upgrades can be executed without downtime by just updating to a more recent version.
The common update involves the following steps and do not need manual intervention of the operator:
* Keep the old version running
* Deploy the version in parallel to the old version
* The new version will start ...
* by updating databases schemas if needed
* participate in the leader election for background jobs
* As soon as the new version is ready to accept traffic it will signal this on the readiness endpoint `/debug/ready`
* At this point your network infrastructure can send traffic to the new version
Users who use [Kubernetes/Helm](/self-hosting/deploy/kubernetes) or serverless container services like Google Cloud Run can benefit from the fact the above process is automated.
As a good practice we recommend creating Database Backups prior to an update.
It is also recommend to read the release notes on GitHub before upgrading.
Since Zitadel utilizes Semantic Versioning Breaking Changes of any kind will always increase the major version (e.g Version 2 would become Version 3).
# ZITADEL Database Structure
This documentation gives you an insight into the structure of the ZITADEL database.
The goal is to give you a rough overview, so you know where which data is stored and which database schemas and tables are used.
Event [#event]
The single source of truth of ZITADEL are the events that are stored in the eventstore.
From these events all different kind of resources e.g. Users, Projects, Applications, etc. can be computed.
An event has the following data:
| Attribute | Description | Example |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
| id | unique identifier of the event, this is generated by the database | b6402a60-e4655-4cc0-904b-f5c6760c4406 |
| aggregate\_type | the type of the aggregate, an aggregate can be compared to a resource or an object. One aggregate contains multiple event types | user |
| aggregate\_id | The unique identifier of an aggregate, this is generated by ZITADEL and is a sonyflake id | 168096909691353697 |
| aggregate\_version | The aggregate version shows in which version of the aggregate the event was created. This is needed to be able to compute correct objects | v1 |
| event\_type | The type of the event | user.human.added |
| event\_sequence | The event sequence is a sequence number that is incremented by one for each event on the instance. For technical reasons, a number can be omitted in some cases. This is needed so that the sequence of the events can be ensured. | 1234 |
| previous\_aggregate\_sequence | This number is the sequence of the event last created on this specific aggregate. E.g. Last user with specific aggregate\_id | 1233 |
| previous\_aggregate\_type\_sequence | This number is the sequence of the event last created on this aggregate. E.g Last User | 1230 |
| creation\_date | timestamp when the event was created | 2022-07-05 13:57:56.358774+00 |
| editor\_user | The editor user contains mostly an unique identifier of a user. And tells who did the request that led to this event. Sometimes this can also be a name of a system within ZITADEL. | 165460784409638965, NOTIFICATION, LOGIN |
| editor\_service | The service defines which API was called when the event got created. If the event was created from the system itself this is empty. | Admin-API |
| resource\_owner | The resource owner is either the organization or instance ID the event belongs to. This is an id generated by ZITADEL as sonyflake id | 168051083313153168 |
| instance\_id | ZITADEL is capable of containing multiple ZITADEL instances withing the system. This id is the unique identifier of the Instance and is generated by ZITADEL as sonyflake id. | 165460784409737865 |
Schemas [#schemas]
| Schema | Description | Examples |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| System | The system contains everything that is needed outside the ZITADEL instances. | assets, encryption\_key |
| Eventstore | Eventstore is the base of ZITADEL and is the single source of truth. All the events stored in the eventstore can be used to generate different projections. | events, instance sequences, system wide unique constraints |
| Projections | The projections contain all the computed objects which are used for reading requests. | users, projects, etc |
| Auth | This contains projections which are used for the auth api. All projections in this schema should be moved to Projections soon | users, auth\_request, etc. |
| Adminapi | This contains projections which are used for the admin api. All projections in this schema should be moved to Projections soon | styling |
| Notification | This contains projections which are used for sending notification. All projections in this schema should be moved to Projections soon | styling |
Projections [#projections]
The projections in ZITADEL contain all the computed objects, that are used for the reading requests.
It is possible that the projections are slightly behind the actual event and not all objects are up-to-date.
Pub-Sub [#pub-sub]
To keep the projections as up-to-date as possible, an internal pub-sub system is used.
As soon as an event is written to the event store, it is sent to the projections that have subscribed to this aggregate.
Spooler [#spooler]
It is sometimes possible for technical reasons that not all events were sent to the projections.
For this reason, a spooler runs in parallel, which checks every n minutes whether there are new events that have not yet been processed.
Current Sequence [#current-sequence]
To ensure that no events get missed when creating the Projections, ZITADEL stores the current sequence, that was processed.
You can find the current sequence in the following tables:
* projections.current\_sequences
* notification.current\_sequences
* auth.current\_Sequences
* adminapi.current\_sequences
The current sequence is stored for each ZITADEL instance and table.
| Attribute | Description | Examples |
| ----------------- | ----------------------------------------------------------- | ----------------------------- |
| projection\_name | The name of the projection for which the sequence is valid. | projection.users |
| aggregate\_type | The aggregate type where the sequence was from | user |
| current\_sequence | The sequence that was last processed | 1234 |
| instance\_id | The instance to which the event belongs | 165460784409737834 |
| timestamp | Timestamp when the table was updated | 2022-07-05 13:57:59.454798+00 |
Failed Events [#failed-events]
Sometimes an event cannot be processed correctly for some reason and an error occurs.
The event is then tried to be processed n times.
When a defined number of attempts have failed, the event is stored in the Failed Events Table and the next event is processed.
This must be done so that the projection is not blocked and no further events are processed.
You can find the failed\_events in the following tables:
* projections.failed\_events
* notification.failed\_events
* auth.failed\_events
* adminapi.failed\_events
| Attribute | Description | Examples |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| projection\_name | The name of the projection for which the failed event should have been processed. | projection.users |
| failed\_sequence | The sequence of the event that failed | 1234 |
| failure\_count | The number of times the event was attempted to be processed. If the number is lower than the max. attempts the event could be processed, but not on the first attempt | 5 |
| error | The error message that occurred when the event could not be processed | User not found |
| instance\_id | The instance to which the event belongs | 165460784409737834 |
# ZITADEL Event Store
ZITADEL is built on the [Event Sourcing pattern](../architecture/software), where changes are stored as events in an Event Store.
What is an Event Store? [#what-is-an-event-store]
Traditionally, data is stored in relations as a state
* A request needs to know the relations to select valid data
* If a relation changes, the requests need to change as well
* That is valid for actual, as well as for historical data
An Event Store on the other hand stores events, meaning every change that happens to any piece of data relates to an event.
The data is stored as events in an append-only log.
* Think of it as a ledger that gets new entries over time, accumulative
* To request data, all you have to do is to sum the events as the summary reflects the actual state
* To investigate past changes to your system, you just select the events from your time range of interest
* That makes audit/analytics very powerful, due to the historical data available to build queries
Benefits [#benefits]
* Audit: You have a built-in audit trail that tracks all changes over an unlimited period of time.
* Travel back in time: With our way of storing data we can show you all of your resources at a given point in time.
* Future Projections: It is easy to compute projections with new business logic by replaying all events since installation.
Definitions [#definitions]
Event Sourcing has some specific terms that are often used in our documentation. To understand how ZITADEL works it is important to understand this key definitions.
Events [#events]
An event is something that happens in the system and gets written to the database. This is the single source of truth.
Events are immutable and the current state of your system is derived from the events.
Possible Events:
* user.added
* user.changed
* product.added
* user.password.checked
Aggregate [#aggregate]
An aggregate consist of multiple events. All events together from an aggregate will lead to the current state of the aggregate.
The aggregate can be compared with an object or a resources. An aggregates should be used as transaction boundary.
Projections [#projections]
Projections contain the computed objects, that will be used on the query side for all the requests.
Think of this as a normalized view of specific events of one or multiple aggregates.
# Account linking
ZITADEL supports linking user accounts from different external identity providers, such as social logins or enterprise IdPs, to a single ZITADEL user profile. This enables users to be recognized as the same user in your applications, regardless of which external account they use to log in.
Each user in ZITADEL has one account for streamlined access management and a unified audit trail. Multiple external identities can be linked to this account.
Advantages [#advantages]
* Users can log in with several identity providers without maintaining separate profiles
* Already registered users can link additional external profiles
* Provides backup authentication methods if an IdP is unavailable
* Enables unified auditing across all linked identities
How it works [#how-it-works]
Account linking is controlled by your organization and identity provider settings.
When external identity providers (such as social logins or enterprise SSO) are configured, a user account is created in ZITADEL, and the [external identity](/guides/manage/console/users-overview#federated-users) is linked to the ZITADEL account.
> **Important:** If a user account in ZITADEL is already linked to an external identity and the user enters their username during login, ZITADEL will automatically redirect to the linked external identity provider for authentication.
> There is **no choice** presented to the user between authenticating with local username/password and an external IDP once accounts are linked.
> Only if the external login fails (for example, due to an expired or invalid IDP secret), will ZITADEL fall back to local authentication.
Users are only shown options to pick local login or possible external IDPs **during registration**—that is, only when there is no existing account. For existing accounts that are linked to an external IDP, the system determines the authentication method automatically.
If only one external identity provider is configured and username/password login is disabled, all authentication requests are immediately redirected to the external provider.
In cases where a local account exists and a user registers or logs in through an external identity provider, ZITADEL can be instructed to link the new external identity to the local account based on matching criteria (such as email address or username).
Automatic account linking [#automatic-account-linking]
You can configure your identity provider to allow automatic account linking for users with the same email or username.
To enable this, set "Account linking allowed" in the [identity provider template settings](/guides/integrate/identity-providers/introduction#key-settings-on-the-templates).
Automatic account linking helps users associate multiple login options with their ZITADEL account, offering flexibility—however, at login, the authentication method will be determined by the link, **not by user choice**.
# ZITADEL Actions
By using ZITADEL actions, you can manipulate ZITADELs behavior on specific Events.
This is useful when you have special business requirements that ZITADEL doesn't support out-of-the-box.
We're working on Actions continuously. In the [roadmap](https://zitadel.com/roadmap), you see how we are planning to expand and improve it. Please tell us about your needs and help us prioritize further fixes and features.
Why actions? [#why-actions]
ZITADEL can't anticipate and solve every possible business rule and integration requirements from all ZITADEL users. Here are some examples:
* A business requires domain specific data validation before a user can be created or authenticated.
* A business needs to automate tasks. Roles should be assigned to users based on their ADFS 2016+ groups.
* A business needs to store metadata on a user that is used for integrating applications.
* A business needs to restrict the users who are allowed to register to a certain organization by their email domains.
With actions, ZITADEL provides a way to solve such problems.
How it works [#how-it-works]
Using the actions feature, *ORG\_OWNERs* create a flow for each supported flow type.
Each flow type provides its own events.
You can hook into these events by assigning them an action.
An action is composed of
* a name,
* a custom JavaScript code snippet,
* an execution timeout in seconds,
* a switch that defines if its corresponding flow should fail if the action fails.
Within the JavaScript code, you can read and manipulate the state.
Further reading [#further-reading]
* [Assign users a role after they register using an external identity provider](/guides/manage/customize/behavior)
* [Actions reference](/guides/manage/console/actions-overview)
* [Actions Marketplace: Find example actions to use in ZITADEL](https://github.com/zitadel/actions)
* [Example use cases](/apis/actions/code-examples)
# ZITADEL Actions v2
By using ZITADEL Actions v2, you can modify ZITADEL's behavior on specific API calls, events, or functions. This is useful when you have special business requirements that ZITADEL does not support out-of-the-box.
We're working on Actions continuously. In the [roadmap](https://zitadel.com/roadmap), you can see how we plan to expand and improve this feature. Please tell us about your needs to help us prioritize further improvements and features.
To use Actions v2, activate the "Actions" [feature flag](/reference/api/feature/zitadel.feature.v2.FeatureService.SetInstanceFeatures) in order to manage the related resources.
Actions v2 will always be executed if available, even if the feature flag is switched off. To remove any Actions v2, the related Execution must be removed.
Why Actions? [#why-actions]
ZITADEL can't anticipate or solve every possible business rule and integration requirement for all users. Here are some examples:
* A business requires domain-specific data validation before a user can be created or authenticated.
* A business needs to automate tasks, such as assigning roles to users based on their ADFS 2016+ groups.
* A business needs to store metadata on a user, which is used for integrating applications.
* A business needs to restrict which users are allowed to register to a certain organization by their email domains.
With Actions, ZITADEL provides a way to address such scenarios.
How it works [#how-it-works]
There are three necessary components:
* **Endpoint:** An external endpoint with the desired logic. It can be anything, as long as it can receive an HTTP POST request.
* **Target:** A resource in ZITADEL containing all the necessary information about how to trigger an endpoint.
* **Execution:** A resource in ZITADEL specifying when to trigger which targets.
The process is that, at certain points, ZITADEL executes a defined Execution, which then calls the defined Target(s). This allows everyone to implement custom behavior for as many processes as needed.
Possible conditions for the Execution:
* **Request:** Reacts to or manipulates requests to ZITADEL, for example, by adding information to newly created users.
* **Response:** Reacts to or manipulates responses from ZITADEL, for example, provisioning newly created users to other systems.
* **Function:** Reacts to various functions in ZITADEL, replacing [Actions](/concepts/features/actions).
* **Event:** Reacts to different events created in ZITADEL, for example, to inform someone if a user gets locked.
Currently, defined Actions v2 will be executed in addition to [Actions](/concepts/features/actions).
Migration [#migration]
* [Migrate Actions v1 to Actions v2](/guides/integrate/actions/migrate-from-v1)
Further reading [#further-reading]
* [Actions v2 reference](/guides/integrate/actions/usage)
* [Actions v2 example execution for request](/guides/integrate/actions/testing-request)
* [Actions v2 example execution for request manipulation](/guides/integrate/actions/testing-request-manipulation)
* [Actions v2 example execution for request signature check](/guides/integrate/actions/testing-request-signature)
* [Actions v2 example execution for response](/guides/integrate/actions/testing-response)
* [Actions v2 example execution for response manipulation](/guides/integrate/actions/testing-response-manipulation)
* [Actions v2 example execution for function](/guides/integrate/actions/testing-function)
* [Actions v2 example execution for function manipulation](/guides/integrate/actions/testing-function-manipulation)
* [Actions v2 example execution for event](/guides/integrate/actions/testing-event)
Example use cases: [#example-use-cases]
The following repository contains various examples demonstrating how to use Actions v2 in different scenarios. It also includes a deployment script for testing the examples with Cloudflare Workers:
* [Actions v2 examples](https://github.com/zitadel/actions/tree/main/actions-v2-cloudflare-workers)
# ZITADEL's In-built Audit Trail
ZITADEL provides you with a built-in audit trail to track all changes and events over an unlimited period of time.
Most other solutions replace a historical record and track changes in a separate log when information is updated.
ZITADEL only ever appends data in an [Eventstore](/concepts/eventstore/overview), keeping all historical record.
The audit trail itself is identical to the state, since ZITADEL calculates the state from all the past changes.
This form of audit log has several benefits over storing classic audit logs.
You can view past data in-context of the whole system at a single point in time.
Reviewing a past state of the application can be important when tracing an incident that happened months back. Moreover, the eventstore provides a truly complete and clean audit log.
There will be three major areas for future development on the audit data
* [Metrics](https://github.com/zitadel/zitadel/issues/4458) and [standard reports](https://github.com/zitadel/zitadel/discussions/2162#discussioncomment-1153259)
* [Feedback loop](https://github.com/zitadel/zitadel/issues/5102) and threat detection
* Forensics and replay of events
Accessing the Audit Log [#accessing-the-audit-log]
Last changes of an object [#last-changes-of-an-object]
You can check the last changes of most objects in the [Console](/guides/manage/console/console-overview).
In the following screenshot you can see an example of last changes on a [user](/guides/manage/console/users-overview).
The same view is available on several other objects such as organization or project.
Event View [#event-view]
Administrators can see all events across an instance and filter them directly in [Console](/guides/manage/console/console-overview).
Go to your default settings and then click on the Tab **Events** to open the Event Viewer or browse to $CUSTOM\_DOMAIN/ui/console/events
Event API [#event-api]
Since everything that is available in Management Console can also be called with our APIs, you can access all events and audit data through our APIs:
* [Event API Guide](/guides/integrate/zitadel-apis/event-api)
* [API Documentation](/reference/api/admin)
Access to the API is possible with a [Service Account](/guides/integrate/service-accounts/authenticate-service-accounts), allowing you to integrate the events with your own business logic.
Using logs in external systems [#using-logs-in-external-systems]
You can use the events from the audit log in external systems such as a SOC/SIEM solution.
Follow our guide on how to [integrate ZITADEL with external systems for streaming events and audit logs](/guides/integrate/external-audit-log).
# Custom Domain
A ZITADEL Custom Domain refers to the ability for organizations to personalize the authentication experience by using their own domain name rather than the default ZITADEL domain.
This feature allows organizations to maintain their brand identity throughout the authentication process, providing a seamless and consistent user experience.
By configuring a Custom Domain within ZITADEL, organizations can replace the default authentication URLs with their own domain, such as "login.example.com" or "auth.companyname.com".
This not only enhances the overall user experience but also reinforces the organization's brand presence. Additionally, Custom Domains can contribute to trust and credibility, as users are more likely to recognize and trust URLs associated with the organization rather than generic domains. Overall, ZITADEL's Custom Domain feature empowers organizations to tailor the authentication process to align with their brand identity and user expectations.
Learn how to [configure a Custom Domain in ZITADEL Cloud](/guides/manage/cloud/instances#add-custom-domain) or how to configure [Custom Domain when self-hosting](/self-hosting/manage/custom-domain).
# External User Role Assignments
ZITADEL's external user role assignment is a feature that allows you to grant access to projects within your organization to users from other organizations.
This is useful in scenarios where you want to collaborate with external users without needing them to be part of your organization.
By using external user role assignments, you can streamline collaboration with external users while maintaining control over access to your projects within ZITADEL.
Where to store users [#where-to-store-users]
Consumer Identity Management (CIAM) / Business-to-Consumer (B2C) [#consumer-identity-management-ciam-business-to-consumer-b-2-c]
You might typically store all users in a single ZITADEL [organization](../structure/organizations) for managing customer accounts.
We recommend creating a second organization for your own team that also contains all the projects and applications that will be [granted](../structure/granted_projects) to the first organization with the B2C customer accounts.
Instead of duplicating user accounts for your team members in the B2C organization, you can create external user role assignments on the B2C organization.
Multitenancy / Business-to-Business (B2B) [#multitenancy-business-to-business-b-2-b]
ZITADEL allows you to create separate [organizations](../structure/organizations) for each of your business partners or tenants.
There might be cases where users from one organization need access to projects from another organization.
You can create a role assignment for an external user that allows the inviting organization to manage the roles for the external user.
Project Grants vs. Role Assignments [#project-grants-vs-role-assignments]
Project Grants are used to delegate access management of an entire project (or specific roles of the project) to another organization.
Role assignments provide a more granular approach, allowing specific users from external organizations to access your projects.
Alternative to multiple user accounts [#alternative-to-multiple-user-accounts]
A user account is always unique across a ZITADEL instance.
In some use cases, external user role assignments are a simple way to allow users access to multiple tenants.
References [#references]
* [API reference to create authorization (role assignment)](/reference/api/authorization/zitadel.authorization.v2.AuthorizationService.CreateAuthorization)
* [How to manage role assignments through ZITADEL's console](/guides/manage/console/roles#role-assignments)
* [More about multi-tenancy with ZITADEL](https://zitadel.com/blog/multi-tenancy-with-organizations)
# Identity Brokering
Link social logins and external identity providers with your identity management platform allowing users to log in with their preferred identity provider.
Establish a trusted connection between your central identity provider (IdP) and third party identity providers.
By using a central identity brokering service you don't need to develop and establish a trust relationship between each application and each identity provider individually.
What are federated identities? [#what-are-federated-identities]
Federated identity management is an arrangement built upon the trust between two or more domains. Users of these domains are allowed to access applications and services using the same identity.
This identity is known as federated identity and the pattern behind this is identity federation.
Compatibility across various IdPs is ensured by using industry standard protocols, such as:
* OpenID Connect (OIDC): A modern and versatile protocol for secure authentication.
* SAML2: A widely adopted protocol for secure single sign-on (SSO) in enterprise environments.
* LDAP: A lightweight protocol for accessing user data directories commonly used in corporate networks.
What is identity brokering? [#what-is-identity-brokering]
A service provider that specializes in brokering access control between multiple service providers (also referred to as relying parties) is called an identity broker.
Federated identity management is an arrangement that is made between two or more such identity brokers across organizations.
For example, if Google is configured as an identity provider in your organization, the user will get the option to use his Google Account on the Login Screen of ZITADEL.
Because Google is registered as a trusted identity provider, the user will be able to log in with the Google account after the user is linked with an existing ZITADEL account (if the user is already registered) or a new one with the claims provided by Google.
The schema is a very simplified version, but shows the essential steps for identity brokering
1. An unauthenticated user wants to use the alpha.com's application.
2. The application redirects the user to alpha.com's identity provider (IdP).
3. Based on the user's tenants settings the IdP presents the configured identity providers, or redirects the user directly to the primary external IdP. The user authenticates with their external identity provider (eg, Entra ID).
4. After the authentication, the user is redirected back to alpha.com's identity provider. If the user doesn't exist in the IdP the user will be created just-in-time and linked to the external identity provider for future reference.
5. As with a local authentication, the IdP issues a token to the user that can be used to access the application. The IdP redirects the user, which is now authenticated, eventually to the application.
Is single-sign-on (SSO) the same as identity brokering? [#is-single-sign-on-sso-the-same-as-identity-brokering]
Sometimes single-sign-on (SSO) and login with third party identity providers is used interchangeably.
Typically, SSO describes an authentication scheme that allows users to log in once at a central identity provider and access service providers (client applications) without having to login again.
Identity brokering describes an authentication scheme where users can log in with external identity providers that have an established trust with an identity provider which facilitates the authentication for the requested applications.
The connection between the two lies in how SSO can be implemented as part of an identity brokering solution.
In such cases, the identity broker uses SSO to enable seamless access across multiple systems, handling the complexities of different authentication protocols and standards behind the scenes.
This allows users to log in once and gain access to multiple systems that the broker facilitates.
Multitenancy and identity brokering [#multitenancy-and-identity-brokering]
In a multi-tenancy application, you want to be able to configure an external identity provider per tenant.
For example some organizations might use their EntraID, some other want to log in with their OKTA, or Google Workspace.
Using an identity provider with strong multitenancy capabilities such as ZITADEL, you can configure a different set of external identity providers per organization.
[Domain discovery](/guides/solution-scenarios/domain-discovery) ensures that users are redirected to their external identity provider based on their email-address or username.
[Administrators](../structure/administrators) can configure Organization Domains that are used for domain-based redirection to an external IdP.
Simplify identity brokering with ZITADEL templates [#simplify-identity-brokering-with-zitadel-templates]
ZITADEL works with SAML, OpenID Connect, and LDAP external identity providers.
For popular IdPs such as EntraID, Okta, Google, Facebook, and GitHub, ZITADEL [offers pre-configured templates](/guides/integrate/identity-providers/introduction).
These templates expedite the setup process, allowing organizations to quickly integrate these providers with minimal effort.
ZITADEL recognizes that specific needs may extend beyond pre-built templates.
To address this, ZITADEL provides generic templates that enable connection to virtually any IdP. This ensures maximum flexibility and future-proofs login infrastructure, accommodating future integrations with ease.
References [#references]
* [Detailed integration guide for many identity providers](/guides/integrate/identity-providers/introduction)
* [Setup identity providers with Management Console](/guides/manage/console/default-settings#identity-providers)
* [Configure identity providers with the ZITADEL API](/reference/api/management)
# Features
# Passkeys & Passwordless Authentication in ZITADEL
ZITADEL's passkeys feature enables passwordless authentication, offering a **smoother and more secure** login experience for your users. This document explains the essential details for developers.
What are Passkeys? [#what-are-passkeys]
Imagine signing in without passwords! Passkeys, replacing traditional passwords, leverage **public-key cryptography** similar to FIDO2 and WebAuthn. Users rely on their devices' **biometrics or PINs** for authentication, eliminating password burdens.
Benefits for Developers [#benefits-for-developers]
* **Enhanced Security:** Phishing-resistant passkeys minimize credential theft risks.
* **Streamlined User Experience:** Faster, easier logins free users from managing passwords.
* **Platform Agnostic:** Works across devices and platforms supporting passkeys.
* **Modern Standard:** Complies with the FIDO2 and WebAuthn standards.
Features [#features]
* **Seamless Registration:** Create unique passkeys for users on various devices. Optionally pair them with specific users and choose cross-platform or platform-specific options.
* **User Control:** Users manage their passkeys directly through ZITADEL's self-service portal, allowing registration, viewing, and deletion.
* **Intuitive Login:** Users initiate passkey login by selecting the passkey option and verifying themselves with the device's biometrics (fingerprint, face ID, etc.).
* **Intuitive Login:** Users initiate passkey login by selecting the passkey option and verifying themselves with the device's biometrics (fingerprint, face ID, etc.).
* **Robust Fallback:** Traditional password login remains available for users without passkeys.
Developer Resources [#developer-resources]
* **Documentation:** Passkeys Guide: [https://zitadel.com/docs/guides/integrate/login-ui/passkey](/guides/integrate/login-ui/passkey)
* [Create Passkey Registration Link API](/guides/manage/user/reg-create-user)
Notes [#notes]
* Passkey support is still evolving in browsers and platforms. Check compatibility for your target audience.
* ZITADEL actively develops its passkey features. Stay updated with documentation and releases.
* Passkeys are bound to your domain, thus we recommend configuring a [Custom Domain](/concepts/features/custom-domain) before setting up passkeys.
Don't hesitate to ask if you have further questions about integrating passkeys in your ZITADEL application!
# Self Service in ZITADEL
ZITADEL allows users to perform many tasks themselves.
For these tasks we either provide a user interface, or the tasks can be initiated or completed through ZITADEL's APIs.
It is important to understand that, depending on your use case, there will exist different user-types that want to perform different actions:
* `Users` are the end-users of your application. Like with any CIAM solution, users should be able to perform tasks like register/join, update their profile, manage authenticators, etc. There are certain actions that can be executed pre-login, yet others require the user to have a valid session.
* `Administrators` are users with a [special administrator role](../../guides/manage/console/administrators) within ZITADEL and can perform administrative actions such as organization settings or granting access rights to users.
All self-service interfaces are available in different [languages](/guides/manage/customize/texts#internationalization-i-18-n).
ZITADEL covers the typical "CIAM" self-service capabilities as well as delegated access management for multi-tenancy scenarios. Please refer to the section [Administrators](#administrators).
Registration [#registration]
You can pre-select a given organization by passing the scope `urn:zitadel:iam:org:domain:primary:{domainname}` in the authorization request.
This will force users to register only with the specified organization.
Furthermore, the branding and login settings (e.g., Social Login Providers) are directly shown to the user.
Local account [#local-account]
Allows anonymous users and authenticated users, who are not yet in the given organization, to create an account (register) themselves.
* Mandatory profile fields
* Set password
* Accept terms of service and privacy policy
* User receives an email with a one-time code for verification purpose
* User has to enter the one-time code to finish registration
* User can re-request a new one-time code
The user is prompted on the first login to set up MFA.
This step can be made mandatory if MFA is enforced in the login policy.
Existing Identity / SSO / Social Login [#existing-identity-sso-social-login]
Anonymous users and authenticated users, who are not yet in the given organization, to register with an external identity provider.
An external identity provider can be a Social Login Provider or a pre-configured identity provider.
* Information from the external identity provider is used to pre-fill the profile information
* User can update the profile information
* An account is created within ZITADEL and linked with the external identity provider
Account Linking [#account-linking]
When you log in with an external identity provider, and the user does not exist in ZITADEL, then an autoregister flow is triggered. The user is presented with two options:
* Create a new account: A new account will be created as stated above
* Autolink: The user is prompted to log in with an existing [local account](#local-account). If successful, the existing identity from the external identity provider will be linked with the local account. A user can now log in with either the local account or any of the linked external accounts.
Login [#login]
SSO / Social Logins [#sso-social-logins]
Given an external identity provider is configured on the instance or on the organization, then:
* the user will be shown a button for each identity provider as an alternative to log in with a [local account](#local-account)
* when clicking the button, the user will be redirected to the identity provider
* after successful login, the user will be redirected to the application
Machines [#machines]
Service acounts can't use an interactive login but require other means of authentication, such as privately signed JWT or personal access tokens.
Read more about [Service Accounts](/guides/integrate/service-accounts/authenticate-service-accounts) and recommended [OpenID Connect Flows](/guides/integrate/login/oidc/oauth-recommended-flows#different-client-profiles).
Logout [#logout]
Users can terminate the session for all their users (logout).
An application can also implement this by calling the [specific endpoint](/apis/openidoauth/endpoints#end-session-endpoint).
Profile [#profile]
These actions are available for authenticated users only.
ZITADEL provides a self-service UI for the user profile out-of-the box under the path *$CUSTOM\_DOMAIN/ui/console/users/me*.
You can also implement your own version in your application by using our APIs.
Change password [#change-password]
Users can change their passwords.
The current password must be entered first.
MFA / FIDO Passkeys [#mfa-fido-passkeys]
Users can set up and delete a second factor and FIDO Passkeys (Passwordless).
Available authenticators are:
* TOTP (Which are Authenticator Apps like Google/Microsoft Authenticator, Authy, etc.)
* OTP sent as Email
* OTP sent as SMS
* FIDO Universal Second Factor (U2F) (Security Keys, Device, etc.)
* FIDO2 WebAuthN (Passkeys)
Update Information [#update-information]
Users can change their profile information. This includes
* UserName
* First- and Last name
* Nickname
* Display Name
* Gender
* Language
* Email address
* Phone number
Email Verification [#email-verification]
Users can change their email address.
The user receives an OTP and can verify control over the given email address.
Phone Verification [#phone-verification]
Users can change their phone number.
The user receives an OTP and can verify control over the given phone number.
Identity providers [#identity-providers]
Users can create a connection between a [local user account](#local-account) and an [external identity](#existing-identity--sso--social-login).
The user can log in with any of the linked accounts.
[Linking of external accounts](#account-linking) is done during the login process.
Administrators [#administrators]
It is important to note that an `Administrator` is not simply an administrative user, but can be used to create much more advanced scenarios such as delegating administration of a whole organization to a user, acting then as administrator and permission manager of that user group.
Thus, we will explain service for two of the most common scenarios in ZITADEL:
* `Administrators in isolation`: Granting administrative permissions within a single organization context.
* `Administrators in delegation`: Granting administrative permissions to a user from a different organization where the organizations depend on each other
A list of [Administrator Roles](../../guides/manage/console/administrators#roles) is available with a description of permissions.
Administrators can be assigned to both human users and service accounts, e.g., for managing certain tasks programmatically.
Administrators in isolation [#administrators-in-isolation]
A user with Administrator roles `IAM_OWNER` or `ORG_OWNER` might want to assign other users from their organization elevated permissions to handle certain aspects of the IAM tasks.
This could be permission to assign authorizations within this isolated organization (`ORG_USER_MANAGER`) or handling setup of projects and applications (`PROJECT_OWNER`).
Administrators in delegation [#administrators-in-delegation]
In a setup like described in the [B2B Scenario](/guides/solution-scenarios/b2b), there exists an organization of the project owner and a customer organization.
The project is granted to the customer organization, such that the customer can access the project and assign authorization to their users.
Given such as set up the owner might want to give one administrative user of the customer organization the role `ORG_OWNER`.
Equipped with this Administrator Role, the user can perform actions like configuring their own SSO/Identity Provider, set security policy for their organization, customize branding, or assign project or Administrator roles to other users.
An `ORG_OWNER` can also not only delegate Administrator roles to other users [as described in the earlier section](#administrators-in-isolation) but also manage all aspects of their own organization as well as authorize users to use the granted project.
With ZITADEL there is no need to replicate all settings and projects across organizations.
Instead, you could set up the project in one organization, delegate it to different organizations, and then appoint users as Administrators of that organization to allow for self-service in a multi-tenancy scenario.
# Knowledge
# Opaque Tokens in Zitadel
In the context of application security, robust authentication mechanisms are essential for safeguarding sensitive data and ensuring user trust.
Opaque tokens, the default token type within the ZITADEL platform, play a crucial role in bolstering security measures.
This documentation elucidates the principles behind opaque tokens, their implementation within ZITADEL, and their advantages over alternative token types.
What are Opaque Tokens? [#what-are-opaque-tokens]
Opaque tokens are a type of access token utilized in authentication processes, particularly within OAuth 2.0 and OpenID Connect (OIDC) frameworks. Unlike self-contained tokens like [JSON Web Tokens (JWT)](https://datatracker.ietf.org/doc/html/rfc7519), opaque tokens do not divulge user information directly.
Instead, they serve as opaque references to session data stored securely on the authorization server.
Authentication Workflow with Opaque Tokens [#authentication-workflow-with-opaque-tokens]
1. **Token Generation**: When a user initiates an authentication process within an application integrated with ZITADEL, the authentication server generates a unique opaque token associated with the user's session.
2. **Token Presentation**: The generated opaque token is provided to the client, which subsequently presents it during requests to access protected resources within the application.
3. **Token Verification**: Upon receiving the opaque token, the application server interacts with the authorization server to validate its authenticity and retrieve detailed information about the user's session. This process ensures the integrity of the authentication flow and verifies the user's permissions to access requested resources.
Benefits of Opaque Tokens in ZITADEL [#benefits-of-opaque-tokens-in-zitadel]
1. **Reduced Token Exposure**: Opaque tokens mitigate the risk of token exposure since they do not contain sensitive user information directly. This reduces the likelihood of token-based attacks and enhances overall security posture.
2. **Enhanced Server-side Control**: With opaque tokens, validation occurs server-side, granting administrators greater control over authentication flows and access policies. This centralized approach facilitates comprehensive monitoring and enforcement of security measures, including server-side single-logout across all applications.
3. **Protection Against Token Tampering**: Opaque tokens prevent unauthorized manipulation of token contents, thereby ensuring the integrity and authenticity of authentication processes. This protection against token tampering further strengthens the security of applications integrated with ZITADEL.
Opaque Tokens vs. JWT Tokens [#opaque-tokens-vs-jwt-tokens]
When it comes to implementing authentication and authorization mechanisms within applications, developers often face the choice between different types of tokens, each with its own set of characteristics and advantages.
Two common types of tokens used in authentication protocols are opaque tokens and JSON Web Tokens (JWT).
Structure [#structure]
* **Opaque Tokens**: Opaque tokens are essentially references or pointers to information stored on the authorization server. They do not contain any meaningful user data within the token itself. Instead, they typically consist of a unique identifier (e.g., a session ID or database key) that allows the server to look up the associated session information.
* **JWT Tokens**: JSON Web Tokens, on the other hand, are self-contained tokens that contain user information in a JSON format. JWTs consist of three base64-encoded sections: header, payload, and signature. The payload contains claims or assertions about the user (e.g., user ID, roles, expiration time) that are digitally signed to ensure integrity.
Token verification [#token-verification]
* **Opaque Tokens**: Verifying opaque tokens requires interaction with the authorization server. When a client presents an opaque token to a resource server, the resource server sends the token to the authorization server for validation. The authorization server then checks the token's validity and returns the associated user information if the token is valid.
* **JWT Tokens**: JWT tokens can be verified locally by clients without needing to communicate with the authorization server. Clients can validate JWT signatures using public keys or shared secrets obtained during the token issuance process. This decentralized verification process can be faster and more scalable but requires securely distributing and managing keys.
Token security and size [#token-security-and-size]
* **Opaque Tokens**: Since opaque tokens do not contain user information, they are inherently more secure in terms of protecting sensitive data. However, the reliance on server-side validation means there is an overhead associated with each token verification request, which can impact performance in high-throughput scenarios.
* **JWT Tokens**: JWT tokens contain user information within the token itself, which can be convenient for clients as it eliminates the need for frequent interactions with the authorization server. However, this also means that JWT tokens can potentially expose sensitive information if not handled and secured properly. Additionally, JWT tokens tend to be larger compared to opaque tokens due to the encoded payload.
Use cases and trade-offs [#use-cases-and-trade-offs]
* **Opaque Tokens**: Opaque tokens are well-suited for scenarios where security and confidentiality are top priorities, such as handling highly sensitive user data or complying with strict privacy regulations. They are particularly advantageous in distributed systems where centralized control over authentication and access policies is desired.
* **JWT Tokens**: JWT tokens are often preferred in scenarios where performance and scalability are critical, such as microservices architectures or API-based applications. The ability to verify tokens locally can reduce latency and minimize dependencies on external services. However, developers must carefully consider the implications of including sensitive information in JWT payloads and implement appropriate security measures.
Conclusion [#conclusion]
In conclusion, opaque tokens represent a foundational component in fortifying application security within ZITADEL.
By leveraging opaque tokens, organizations can establish robust authentication mechanisms, mitigate security risks, and maintain stringent control over access policies.
As organizations navigate the complex landscape of application security, integrating technologies such as opaque tokens becomes imperative for safeguarding sensitive data and fostering user trust.
Notes:
* Read more about the differences in our [blog on JWT vs. Opaque tokens](https://zitadel.com/blog/jwt-vs-opaque-tokens)
* Learn how to use [token introspection](/guides/integrate/token-introspection) to validate access tokens
* Decode, verify and generate valid JWT tokens with [jwt.io](https://jwt.io/)
# Administrators
Notes:
* Read our [guide on Administrators](../../guides/manage/console/administrators) to learn more about the role concept and how to use Administrator roles in ZITADEL.
* [API reference](/apis/resources/mgmt/management-service-list-org-member-roles) for Administrators on the organization level
# ZITADEL's Granted Projects
Granted Project [#granted-project]
With ZITADEL you can grant selected roles within your project to an organization. The receiving organization can then create role assignments for their users on their own (self-service).
An example could be a service provider that offers a SaaS solution that has different permissions for employees working in Sales and Accounting. As soon as a new client purchases the service, the provider could grant the roles ‘sales’ and ‘accounting’ to that organization, allowing them to self-manage how they want to allocate the roles to their users.
To learn more about how to set up Project Grants, read this guide [here](../../guides/manage/console/projects-overview#granted-projects-b2b)
# Structure
# Instances
Instance Structure [#instance-structure]
An instance is the top node in ZITADEL's data hierarchy.
Within an instance all the default [settings](/concepts/structure/policies),
such as branding, login policy, password policy, etc. for the system can be configured.
One instance normally runs on one domain and represents one issuer (e.g. login.customer.com).
One instance can contain multiple [organizations](/guides/manage/console/organizations-overview),
which in turn can represent your own company (e.g. departments), your business customers or a consumer organization.
Read more about how to configure your instance in our [instance guide](/guides/manage/console/default-settings).
This overview shows the general structure of ZITADEL.
You will find more detailed explanations around the different concepts in the following sections.
Multiple Virtual Instances [#multiple-virtual-instances]
ZITADEL has the concept of virtual instances.
When installing ZITADEL from scratch, one instance is always automatically created for you.
Nevertheless, you can add more virtual instances via the [system API](/reference/api/system).
This is useful if you have business customers, which in turn have their business customers with self-service and Custom Domain demands.
By providing a virtual ZITADEL instances, your customers have all the customization options available in ZITADEL.
Scaling ZITADEL instances virtually enables you to easily distribute your limited compute resources to all your customers.
# ZITADEL Settings and Policies
Settings and policies control the behavior of all the different parts of the instance or an organization. For all parts we have a suitable default in the instance.
The default settings can be overridden for each organization, some policies are currently only available on the instance level. Learn more about our different policies [here](/guides/manage/console/default-settings).
API wise, settings are often called policies. You can read the proto and swagger definitions [here](../../apis/introduction).
# OAuth 2.0 Proxy
[OAuth2-proxy](https://github.com/oauth2-proxy/oauth2-proxy) is a project which allows services to delegate the authentication flow to a IDP, for example **ZITADEL**
Configure ZITADEL [#configure-zitadel]
Setup Application and get Keys [#setup-application-and-get-keys]
Before we can start building our application we have to do a few setup steps in ZITADEL Console.
You will need to provide some information about your app. We recommend creating a new app to start from scratch. Navigate to your project and add a new application at the top of the page.
Select Web Application and continue.
We recommend that you use [Authorization Code](/apis/openidoauth/grant-types#authorization-code) for the OAuth 2.0 Proxy.
> Make sure Authentication Method is set to `BASIC` and the Application Type is set to `Web`.
Redirect URLs [#redirect-ur-ls]
A redirect URL is a URL in your application where ZITADEL redirects the user after they have authenticated. Set your url to the domain the proxy will be deployed to or use the default one `http://127.0.0.1:4180/oauth2/callback`.
> If you are following along with the sample project you downloaded from our templates, you should set the Allowed Callback URL to `http://localhost:4200/auth/callback`. You will also have to set dev mode to `true` as this will enable unsecure http for the moment.
If you want to redirect the users back to a route on your application after they have logged out, add an optional redirect in the post redirectURI field.
Continue and Create the application.
Client ID and Secret [#client-id-and-secret]
After successful app creation a popup will appear showing you your clientID as well as a secret.
Copy your client ID and Secrets as it will be needed in the next step.
> Note: You will be able to regenerate the secret at a later time if you lose it.
OAuth 2.0 Proxy Setup [#o-auth-2-0-proxy-setup]
Authentication Example [#authentication-example]
```toml
provider = "oidc"
user_id_claim = "sub" #uses the subject as ID instead of the email
provider_display_name = "ZITADEL"
redirect_url = "http://127.0.0.1:4180/oauth2/callback"
oidc_issuer_url = "https://$CUSTOM_DOMAIN"
upstreams = [
"https://example.corp.com"
]
email_domains = [
"*"
]
client_id = "{ZITADEL_GENERATED_CLIENT_ID}"
client_secret = "{ZITADEL_GENERATED_CLIENT_SECRET}"
pass_access_token = true
cookie_secret = "{SUPPLY_SOME_SECRET_HERE}"
skip_provider_button = true
cookie_secure = false #localdev only false
http_address = "127.0.0.1:4180" #localdev only
```
> This was tested with version `oauth2-proxy v7.4.0 (built with go1.20.0)`
Check for groups [#check-for-groups]
If you want oauth2-proxy to check for roles in the tokens you have to add an [action](/guides/manage/console/actions-overview) in ZITADEL to [complement the token](/guides/manage/console/actions-overview) in ZITADEL to [complement the token](/apis/actions/complement-token) according to [this example](/guides/manage/console/actions-overview) in ZITADEL to [complement the token](/apis/actions/complement-token) according to [this example](https://github.com/zitadel/actions/blob/main/examples/custom_roles.js) and add the following settings to the config:
```toml
oidc_groups_claim = "{your_actions_group_key}"
allowed_groups = ["list", "of", "allowed", "roles"]
```
Completion [#completion]
You have successfully integrated ZITADEL in your proxy!
What next? [#what-next]
# Example Applications
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
# ZITADEL with Flutter
Flutter Web App [#flutter-web-app]
This guide demonstrates how you integrate **ZITADEL** into a Flutter app. It refers to our example on [GitHub](https://github.com/zitadel/zitadel_flutter)
At the end of the guide you have a mobile application for **Android**, **iOS** and **Web** with the ability to authenticate users via ZITADEL.
If you need any other information about Flutter, head over to the [documentation](https://flutter.dev/).
Setup Application [#setup-application]
Before we can start building our application, we have to do a few setup steps in ZITADEL Console.
You will need to provide some information about your app. We recommend creating a new app to start from scratch. Navigate to your Project, then add a new application at the top of the page.
Select **Native** application type and continue.
Redirect URIs [#redirect-ur-is]
With the Redirect URIs field, you tell ZITADEL where it is allowed to redirect users to after authentication.
As our application will also support web, we have to make sure to set redirects for http and https, as well as a **custom-scheme** for our native Android and IOS Setup.
For our local web development, add a redirectURI for `http://localhost:4444/auth.html` with your custom port. For Android and IOS, add your **custom scheme**. In our case it is `com.example.zitadelflutter`.
Your custom scheme has to be compliant with the OAuth 2.0
authentication for mobile devices ([RFC 8252 specification](https://tools.ietf.org/html/rfc8252)).
Otherwise your app might get rejected.
For development, you need to set dev mode to `true` to enable insecure HTTP and redirect to a `localhost` URI.
If you want to redirect the users back to a route on your application after they have logged out, add an optional redirect in the Post Logout URIs field.
Continue and create the application.
After creation, go to **token settings** and check the refresh token checkbox. This allows us to request a refresh\_token via `offline_access` scope.
Make sure to save the application.
Client ID [#client-id]
After successful app creation, a pop-up will appear, showing the app's client ID. Copy the client ID, as you will need it to configure your Flutter application.
Flutter Prerequisites [#flutter-prerequisites]
To move further in this quickstart, you'll need the following things prepared:
* Have Flutter (and Dart) installed ([how-to](https://flutter.dev/docs/get-started/install))
* Have an IDE set up for developing Flutter ([how-to](https://flutter.dev/docs/get-started/editor))
* Create a basic Flutter app ([how-to](https://flutter.dev/docs/get-started/codelab))
* Create a "Native" application in ZITADEL
After you created the starter Flutter app, the app will show a simple, templated Flutter app.
Install Dependencies [#install-dependencies]
To authenticate users with ZITADEL in a mobile application, some specific packages are needed.
The [RFC 8252 specification](https://tools.ietf.org/html/rfc8252) defines how
[OAUTH2.0 for mobile and native apps](https://oauth.net/2/native-apps/) works.
Basically, there are two major points in this specification:
1. It recommends to use [PKCE](https://oauth.net/2/pkce/)
2. It does not allow third party apps to use an embedded web view for the login process,
the app must open the login page within the default browser
First install [http](https://pub.dev/packages/http) a library for making HTTP calls,
then [`flutter_web_auth_2`](https://pub.dev/packages/flutter_web_auth_2) and a secure storage to store the auth / refresh tokens [flutter\_secure\_storage](https://pub.dev/packages/flutter_secure_storage).
To install run:
```bash
flutter pub add http
flutter pub add oidc
flutter pub add oidc_default_store
```
Setup for Android [#setup-for-android]
Navigate to your `AndroidManifest.xml` at `/android/app/src/main/AndroidManifest.xml` and add the following activity with your custom scheme.
```xml reference
https://github.com/zitadel/zitadel_flutter/blob/main/android/app/src/main/AndroidManifest.xml#L29-L38
```
Furthermore, for `secure_storage`, you need to set the minimum SDK version to 18
in `/android/app/src/build.gradle`.
Add Authentication [#add-authentication]
To reduce the commented default code, we will modify the `main.dart` file.
First, the `MyApp` class: it remains a stateless widget:
```dart reference
https://github.com/zitadel/zitadel_flutter/blob/main/lib/main.dart#L14-L28
```
Second, the `MyHomePage` class will remain a stateful widget with
its title, we don't change any code here.
```dart reference
https://github.com/zitadel/zitadel_flutter/blob/main/lib/main.dart#L30-L37
```
What we'll change now, is the `_MyHomePageState` class to enable
authentication via ZITADEL and remove the counter button of the starter application. We'll show the username of the authenticated user.
We define the needed elements for our state:
```dart
var _busy = false;
var _authenticated = false;
var _username = '';
final storage = const FlutterSecureStorage();
```
Then the builder method, which does show the login button if you're not
authenticated, a loading bar if the login process is going on and
your name if you are authenticated:
```dart reference
https://github.com/zitadel/zitadel_flutter/blob/main/lib/main.dart#L119-L159
```
And finally the `_authenticate` method which calls the authorization endpoint,
then fetches the user info and stores the tokens into the secure storage.
```dart reference
https://github.com/zitadel/zitadel_flutter/blob/main/lib/main.dart#L45-L117
```
Note that we have to use our http redirect URL for web applications or otherwise use our custom scheme for Android and iOS devices.
To setup other platforms, read the documentation of the [Flutter Web Auth](https://pub.dev/packages/flutter_web_auth_2).
To ensure our application catches the callback URL, you have to create a `auth.html` file in the `/web`
folder with the following content:
```html reference
https://github.com/zitadel/zitadel_flutter/blob/main/web/auth.html
```
Now, you can run your application for iOS and Android devices with
```bash
flutter run
```
or by directly selecting your device
```bash
flutter run -d iphone
```
For Web make sure you run the application on your fixed port such that it matches your redirect URI in your ZITADEL application. We used 4444 as port before so the command would look like this:
```bash
flutter run -d chrome --web-port=4444
```
Our Android and iOS Application opens ZITADEL's login within a custom tab, on Web a new tab is opened.
Result [#result]
If everything works out correctly, your applications should look like this:
# Go Web Application Login with ZITADEL
This integration guide demonstrates the recommended way to incorporate ZITADEL into your Go web application.
It explains how to enable user login in your application and how to fetch data from the user info endpoint.
> ℹ️ These examples and guides are based on our official [Go SDK](https://github.com/zitadel/zitadel-go).
>
> The SDK is a convenient wrapper around our low-level [OIDC library](https://github.com/zitadel/oidc). For most use cases, using the helpers provided in our [Go SDK](https://github.com/zitadel/zitadel-go) is the recommended approach for implementing authentication.
By the end of this guide, your application will have login functionality and will be able to access the current user's profile.
> This documentation references our [example](https://github.com/zitadel/zitadel-go) on GitHub.
> You can either create your own application or directly run the example by providing the necessary arguments.
Set up application [#set-up-application]
Before we begin developing our application, we need to perform a few setup steps in the ZITADEL Console.
You'll need to provide some information about your app. We recommend creating a new app to start from scratch. Navigate to your Project, then add a new application at the top of the page.
Select the **Web** application type and continue.
We recommend that you use [Proof Key for Code Exchange (PKCE)](/apis/openidoauth/grant-types#proof-key-for-code-exchange) for all applications.
Redirect URIs [#redirect-ur-is]
The Redirect URIs field tells ZITADEL where it's allowed to redirect users after authentication. For development, you can set dev mode to `true` to enable insecure HTTP and redirect to a `localhost` URI.
The Post-logout redirect send the users back to a route on your application after they have logged out.
> If you are following along with the [example](https://github.com/zitadel/zitadel-go), set the dev mode to `true`, the Redirect URIs to `http://localhost:8089/auth/callback` and Post-logout redirect URI to [http://localhost:8089/](http://localhost:8089/)>.
Continue and create the application.
Client ID [#client-id]
After successful creation of the app, a pop-up will appear displaying the app's client ID. Copy the client ID, as you will need it to configure your Go application.
Go setup [#go-setup]
Now that you have configured your web application on the ZITADEL side, you can proceed with the integration of your Go application.
Install ZITADEL Go SDK [#install-zitadel-go-sdk]
To connect with ZITADEL, you need to install an OAuth/OIDC client. Run the following command:
```bash
go get -u github.com/zitadel/zitadel-go/v3
```
Create the application server [#create-the-application-server]
Create a new go file with the content below. This will create an application with a home and profile page.
```go reference
https://github.com/zitadel/zitadel-go/blob/next/example/app/app.go
```
This will basically set up everything. So let's look at some parts of the code.
**Register authentication handler**:
For the authentication to work, the SDK needs some handlers in your application.
In this example we will register them on the `/auth/` prefix.
The SDK itself will then register three routes on that to be able to:
* start the authentication process and redirect to the Login UI (`/auth/login`)
* continue with the authentication process after the login UI (`/auth/callback`)
* terminate the session (`/auth/logout`)
```go
router.Handle("/auth/", z.Authentication)
```
***Authentication checks***
To ensure the user is authenticated before they are able to use your application, the middleware provides two options:
* You can either require the user to be authenticated. If they haven't already, they will be automatically redirected to the Login UI:
```go
mw.RequireAuthentication()(handler)
```
* You can just check the user's authentication status, but still continue serving the page:
```go
mw.CheckAuthentication()(handler)
```
***Authentication context***
If you used either of the authentication checks above, you can then access context information in your handler:
```go
mw.Context(req.Context())
```
Add pages to your application [#add-pages-to-your-application]
To be able to serve these pages create a `templates` directory in the same folder as you just created the go file.
Now create two HTML files in the new `templates` folder and copy the content of the examples:
**home.html**
The home page will display a short welcome message and allow the user to manually start the login process.
```go reference
https://github.com/zitadel/zitadel-go/blob/next/example/app/templates/home.html
```
**profile.html**
The profile page will display the Userinfo from the authentication context and allow the user to logout.
```go reference
https://github.com/zitadel/zitadel-go/blob/next/example/app/templates/profile.html
```
Start your application [#start-your-application]
You will need to provide some values for the program to run:
* `domain`: Your ZITADEL Custom Domain, e.g. my-domain.zitadel.cloud
* `key`: Random secret string. Used for symmetric encryption of state parameters, cookies and PKCE.
* `clientID`: The clientID provided by ZITADEL
* `redirectURI`: The redirectURI registered at ZITADEL
* `port`: The port on which the API will be accessible, default it 8089
```bash
go run main.go --domain --key --clientID --redirectURI
```
This could look like:
```bash
go run main.go --domain my-domain.zitadel.cloud --key XKv2Lqd7YAq13NUZVUWZEWZeruqyzViM --clientID 243861220627644836@example --redirectURI http://localhost:8089/auth/callback
```
If you then visit on [http://localhost:8089](http://localhost:8089) you should get the following screen:
By clicking on `Login` you will be redirected to your ZITADEL instance. After login with your existing user you will be presented the profile page:
Completion [#completion]
Congratulations! You have successfully integrated your Go application with ZITADEL!
If you get stuck, consider checking out our [example](https://github.com/zitadel/zitadel-go) application.
This application includes all the functionalities mentioned in this quickstart.
You can directly start it with your own settings. If you face issues, contact us or raise an issue on [GitHub](https://github.com/zitadel/zitadel-go/issues).
# Next.js Multi-tenant B2B Application with ZITADEL
Next.js Web App with B2B Scenario [#next-js-web-app-with-b-2-b-scenario]
This is our ZITADEL [Next.js](https://nextjs.org/) B2B template. It shows how to authenticate as a user with multiple organizations. The application shows your users' roles in the selected organizations, other projects your organization is allowed to use, and other users who have roles to use the application.
If you need more info on B2B use cases, consider reading our guide for the [B2B solution scenario](/guides/solution-scenarios/b2b).
> You can follow along with the template code in our [zitadel-nextjs-b2b](https://github.com/zitadel/zitadel-nextjs-b2b) repo.
What does it do? [#what-does-it-do]
Users with `view` role can view granted projects on their organization which were granted by your organization (owning this portal application).
Users with `admin` role can view granted projects and list users of the selected organization who are granted to use the portal application too.
Set up Vendor application and users in ZITADEL [#set-up-vendor-application-and-users-in-zitadel]
First, we need to create an organization that holds the Vendor's users, projects, and applications.
Vendor Organization [#vendor-organization]
Navigate to `https://$CUSTOM_DOMAIN.zitadel.cloud/ui/console/orgs` (replace $CUSTOM\_DOMAIN), and click on the button "New".
Toggle the setting "Use your personal account as organization owner".
Enter the name `Demo-Vendor`, and click "Create". Then click on that organization.
Portal Web Application [#portal-web-application]
To set up this sample you have to create a project and an application in the vendor organization (`Demo-Vendor`) first.
Open the Management Console (`https://$CUSTOM_DOMAIN.zitadel.cloud/ui/console/projects`) and create a new project. Let's call it `Portal`.
Then on the project detail page click on new application and enter a name for this app.
Let's call this one `portal-web`.
Select `Web`, continue, `CODE`, then enter `http://localhost:3000/api/auth/callback/zitadel` for the redirect, and `http://localhost:3000` for the post redirect. Then press on `create`.
Because the requests from your Next.js application to ZITADEL are made on the server side, you can safely select `CODE`. You will get a secret at the end of the stepper. With NextAuth your secret never gets exposed on the browser since it is kept in your Next.js server.
Copy the "Project ID" of the project `Portal` as you will need this in your environment configuration file later.
Click on the application `portal-web`.
On the application detail page click on the section under redirect settings and enable `Development Mode`. This will allow you application to work on `localhost:3000`.
To read the user data and roles from ID Token, go to the section Token Settings and make sure both checkboxes, `User roles inside ID Token` and `User Info inside ID Token` are enabled.
Make sure to save your changes.
Copy the "Application ID" of the application `portal-web` as you will need this in your environment configuration file later.
Roles [#roles]
To set up the necessary roles for your project, navigate to your `Portal` project, and add the following roles
| Key | Display Name | Group | Description |
| :----- | :------------ | :---- | -------------------------------------------------------------------------------- |
| admin | Administrator | | The administrator, allowed to read granted projects and to assign roles to users |
| reader | Reader | | A user who is allowed to read his organization's granted projects only |
Now in the `General` section of the Portal project, make sure to enable `Assert Roles on Authentication`.
This makes sure that roles, which are used by the application to enable UI components, are set in your OIDC ID Token.
Service Account [#service-account]
To make the application work, you need a service account which loads granted-projects and user-grants for you.
In the B2B-Demo organization, navigate to `Users` in navigation of Management Console, click on `Service Accounts`, and create a new user.
Let's set its username to `nextjs` and its name to `NextJS`. Then press `create`.
On the detail page of that user, navigate to "Personal Access Tokens" and add a new entry, set an optional expiration date.
Copy the generated Token as you will need this in your environment configuration file later.
Go back to the `Portal` project and add the Service Account as Manager (top right).
Make sure to select `Project Owner Viewer` as the management role.
To show granted projects, go to the `Demo-Vendor` organization and add the Service Account as `Org Project Permission Editor` Manager.
Setup [#setup]
Now clone this project and navigate to its root folder.
Create a file `.env.local` and copy and paste the following:
```text
NEXTAUTH_URL=http://localhost:3000
NEXT_PUBLIC_ZITADEL_ISSUER=https://$CUSTOM_DOMAIN.zitadel.cloud
ZITADEL_API=https://$CUSTOM_DOMAIN.zitadel.cloud
ORG_ID=${YOUR_ORG_ID}
PROJECT_ID=${PROJECT_ID}
ZITADEL_CLIENT_ID=${CLIENT_ID}
SERVICE_ACCOUNT_ACCESS_TOKEN=${SERVICE_ACCOUNT_SECRET}
NEXTAUTH_SECRET=randomsecret
```
Replace the values as follows
`NEXTAUTH_URL`: Base url of this demo app (B2B portal); runs per default on [http://localhost:3000](http://localhost:3000)
`NEXT_PUBLIC_ZITADEL_ISSUER`: The url to your zitadel instance. When using zitadel.cloud for this demo, you can find the domain of your ZITADEL instance in the customer portal. You can also find this information by going to your application `portal-web` and click 'URLs' in the navigation. The variable is prefixed with `NEXT_PUBLIC_` such that it can be accessed from the application.
`ZITADEL_API`: URL of the Management API. Typically, the same as `ZITADEL_ISSUER`.
`ORG_ID`: We will create an organization during later steps. You can find `${YOUR_ORG_ID}` by selecting the `Demo-Vendor` organization in Console. `${YOUR_ORG_ID}` is displayed on top of the organization detail page as "Organization ID".
`PROJECT_ID`: You can find `${PROJECT_ID}` by clicking on "Projects" in the navigation and select the Project `Portal`. `${PROJECT_ID}` is displayed on the top as "Project ID".
`ZITADEL_CLIENT_ID`: Having the project `Portal` selected, click on the Application `portal-web`. `${CLIENT_ID}` is displayed as a field in the OIDC settings, labeled "Client ID" and has the format `12345678@portal`.
`SERVICE_ACCOUNT_ACCESS_TOKEN`: Set up a service account, add a Personal Access Token, and copy the secret here (see below).
Install and Run [#install-and-run]
To run this sample locally, you need to install dependencies first.
Type and execute:
```bash
yarn install
```
then, to run the development server:
```bash
npm run dev
# or
yarn dev
```
and open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
Create a customer organization [#create-a-customer-organization]
Customer organization [#customer-organization]
Create a new organization in Console. The easiest way is to use the organization dropdown on the top left.
Let's call this new organization `Demo-Customer`.
Users [#users]
Now switch back to the organization `Demo-Customer` and [create a new user](/guides/manage/console/users-overview#create-user) in this organization.
Let's call the first user `Alice Admin`. Create a second user called `Eric Employee`.
Administrator Role [#administrator-role]
We want to enable Alice to assign roles to users in her organization in a self-service manner.
To make this happen, we need to assign Alice an [Administrator Role](/concepts/structure/administrators) within the Organization `Demo-Customer`.
Still in the organization `Demo-Customer`, navigate to Organization. Click on the plus on the top right and give `Alice Admin` the Administrator Role `Org Owner`.
Login with your user on the customer organization to validate the setup.
Create a project grant [#create-a-project-grant]
Project Grant [#project-grant]
Switch to the `Demo-Vendor` organization, select Projects in the navigation, and click on `Portal` and then `Project Grants`.
[Grant all roles of the Project](/guides/manage/console/projects-overview#granted-projects-b2b) to the organization `demo-customer.$CUSTOM_DOMAIN.zitadel.cloud`.
Role Assignment [#role-assignment]
As you have guessed, these two users need to be assigned roles.
On the `Demo-Customer` organization, navigate to Projects and select "Granted Projects" in the sub-navigation.
Select the project portal `Portal` and navigate to "Role Assignments".
Give `Alice Admin` the roles `reader` and `admin`.
`Eric Employee` will get only the role `reader`.
Login [#login]
You should be able to log in to the Demo Application with `Alice Admin` and see all granted projects.
You can log out and log in with `Eric Employee` and you should only have access to the granted projects, but not to the `Role Assignments` tab.
What next [#what-next]
You could create another project (eg, `Data Cube`) and grant that project to the customer organization. The granted project should appear after a reload automatically. This gives you an idea of how you could do Service Discovery with ZITADEL.
You could also build out the code (PRs welcome :wink:) for this application, for example:
* Create a mock `datacube-web` application and show how SSO between the portal and the application works with ZITADEL.
* Implement a feature in the `Role Assignments` tab to assign roles directly from the customer portal.
# Secure Go API Application with ZITADEL
This integration guide shows you how to integrate **ZITADEL** into your Go API. It demonstrates how to secure your API using
OAuth 2 Token Introspection.
> ℹ️ These examples and guides are based on our official [Go SDK](https://github.com/zitadel/zitadel-go).
>
> The SDK is a convenient wrapper around our low-level [OIDC library](https://github.com/zitadel/oidc). For most use cases, using the helpers provided in our [Go SDK](https://github.com/zitadel/zitadel-go) is the recommended approach for implementing authentication.
At the end of the guide you should have an API with a protected endpoint.
> This documentation references our HTTP example. There's also one for GRPC. Check them out on [GitHub](https://github.com/zitadel/zitadel-go/blob/next/example/api/http/main.go).
Prerequisites [#prerequisites]
This will handle the OAuth 2.0 introspection request including authentication using JWT with Private Key using our [Go SDK](https://github.com/zitadel/zitadel-go).
All that is required, is to create your API, create a private key and a personal access token for a service account.
Set up application and obtain keys [#set-up-application-and-obtain-keys]
Before we begin developing our API, we need to perform a few setup steps in the ZITADEL Console.
You'll need to provide some information about your app. We recommend creating a new app to start from scratch.
Starting from the homepage of your management console, click on Create Application
Select a project from the dropdown and select *Other* as framework, then continue.
Add your app name and select *API* as application type, then continue.
We recommend that you use JWT Profile for authenticating at the Introspection Endpoint. So select *JWT* as authentication method
You then need to create a new JSON key.
Select an expiration date that suits you.
And make sure to download it, as you won't be able to retrieve it again.
Now we need to create a *Personal Access Token* to authenticate the application requests.
On the user view, switch to *Service Accounts* and create a new one.
Give the service account a name and a username. Select `Bearer` as *Access Token Type*.
Create a service account and a personal access token (PAT) [#create-a-service-account-and-a-personal-access-token-pat]
Once done, from the left panel of the user management, click on Personal Access Token and create a new one.
Set an expiration date and then copy the PAT generated to somewhere safe. We will need it later.
Go Setup [#go-setup]
Add Go SDK to your project [#add-go-sdk-to-your-project]
You need to add the [SDK](https://github.com/zitadel/zitadel-go) into Go Modules by:
```bash
go get -u github.com/zitadel/zitadel-go/v3
```
Create example API [#create-example-api]
Create a new go file with the content below. This will create an API with three endpoints:
* `/api/healthz`: can be called by anyone and always returns `OK`
* `/api/tasks`: requires authorization and returns the available tasks
* `/api/add-task`: requires authorization with granted `admin` role and adds the task to the list
If authorization is required, the token must not be expired and the API has to be part of the audience (either client\_id or project\_id).
For tests, we will use a Personal Access Token.
```go reference
https://github.com/zitadel/zitadel-go/blob/next/example/api/http/main.go
```
You will need to provide some values for the program to run:
* `domain`: Your ZITADEL Custom Domain, e.g. [https://my-domain.zitadel.cloud](https://my-domain.zitadel.cloud)
* `key`: The path to the downloaded key.json
* `port`: The port on which the API will be accessible, default it 8089
Test API [#test-api]
After you have configured everything correctly, you can simply start the example by:
```bash
go run main.go --domain --key
```
This could look like:
```bash
go run main.go --domain my-domain.zitadel.cloud --key ./api.json
```
After you get a successful log:
```
2023/12/04 10:27:42 INFO server listening, press ctrl+c to stop addr=http://localhost:8089
```
Public endpoint [#public-endpoint]
Now you can call the API by browser or curl. Try the healthz endpoint first:
```bash
curl -i http://localhost:8089/api/healthz
```
it should return something like:
```
HTTP/1.1 200 OK
Content-Type: application/json
Date: Mon, 04 Dec 2023 09:29:38 GMT
Content-Length: 4
"OK"
```
Task list [#task-list]
and the task list endpoint:
```bash
curl -i http://localhost:8089/api/tasks
```
it will return:
```
HTTP/1.1 401 Unauthorized
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Mon, 04 Dec 2023 09:41:54 GMT
Content-Length: 44
unauthorized: authorization header is empty
```
We need to use the personal access token generated previously.
If you provide a valid Bearer Token:
```bash
curl -i -H "Authorization: Bearer ${token}" http://localhost:8089/api/tasks
```
it will return an empty list:
```
HTTP/1.1 200 OK
Content-Type: application/json
Date: Mon, 04 Dec 2023 09:49:06 GMT
Content-Length: 2
{}
```
Try to add a new task [#try-to-add-a-new-task]
Let's see what happens if you call the AddTask endpoint:
```bash
curl -i -H "Authorization: Bearer ${token}" http://localhost:8089/api/add-task
```
it will complain about the missing `admin` role:
```
HTTP/1.1 403 Forbidden
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Mon, 04 Dec 2023 09:52:00 GMT
Content-Length: 50
permission denied: missing required role: `admin`
```
Add administrator role [#add-administrator-role]
So let's create the role and grant it to the user. To do so, go to your project in ZITADEL Management Console
and create the role by selecting `Roles` in the navigation and then clicking on the `New Role` button.
Finally, create the role as shown below:
After you have created the role, let's grant it to the user who requested the tasks.
Click on `Role Assignments` in the navigation and create a new one by selecting the user and the `admin` role.
After successful creation, it should look like:
So you should now be able to add a new task:
```bash
curl -i -H "Authorization: Bearer ${token}" http://localhost:8089/api/add-task --data "task=My new task"
```
which will report back the successful addition:
```
HTTP/1.1 200 OK
Content-Type: application/json
Date: Mon, 04 Dec 2023 10:06:29 GMT
Content-Length: 26
"task `My new task` added"
```
Let's now retrieve the task list again:
```bash
curl -i -H "Authorization: Bearer ${token}" http://localhost:8089/api/tasks
```
As you can see your new task ist listed. And since you're an `admin` now, you will always get an additional `create a new task on /api/add-task`:
```
HTTP/1.1 200 OK
Content-Type: application/json
Date: Mon, 04 Dec 2023 10:08:38 GMT
Content-Length: 62
{"tasks":["My new task","create a new task on /api/add-task"]}
```
# Secure Java Spring Boot API Application with ZITADEL
This integration guide shows you how to integrate **ZITADEL** into your Java Spring Boot API. It demonstrates how to secure your API using
OAuth 2 Token Introspection.
At the end of the guide you should have an API with a protected endpoint.
This documentation references our [example](https://github.com/zitadel/zitadel-java) on GitHub.
You can either create your own application or directly run the example by providing the necessary arguments.
Set up application [#set-up-application]
Before we begin developing our API, we need to perform a few setup steps in the ZITADEL Console.
You'll need to provide some information about your app. We recommend creating a new app to start from scratch. Navigate to your Project, then add a new application at the top of the page.
Select the **API** application type and continue.
Select Basic Auth for authenticating at the Introspection Endpoint.
After successful creation of the app, a pop-up will appear displaying the app's client ID. Copy the client ID and secret, as you will need it to configure your Java application.
Spring Setup [#spring-setup]
Now that you have configured your web application on the ZITADEL side, you can proceed with the integration of your Spring application.
This guide will reference the [example repository](https://github.com/zitadel/zitadel-java) and explain the necessary steps taken in there.
If your starting from scratch, you can use the Spring Initializer with the [following setup](https://start.spring.io/#!type=maven-project\&language=java\&platformVersion=3.2.1\&packaging=jar\&jvmVersion=17\&dependencies=web,lombok,oauth2-resource-server) as a base.
Support class [#support-class]
To be able to take the most out of ZITADELs RBAC, we first need to create a CustomAuthorityOpaqueTokenIntrospector, that will
customize the introspection behavior and map the role claims (`urn:zitadel:iam:org:project:roles`)
into Spring Security `authiorities`, which can be used later on to determine the granted permissions.
So in your application, create a `support/zitadel` package and in there the `CustomAuthorityOpaqueTokenIntrospector.java`:
Application server settings [#application-server-settings]
As we have now our support class, we can now create and configure the application server itself.
In a new `config` package, create the `WebSecurityConfig.java`.
This class will take care of the authorization by require the calls on `/api/tasks` to be authorized. Any other endpoint will be public by default.
It will also use the just created CustomAuthorityOpaqueTokenIntrospector for the introspection call:
For the authorization (and the server in general) to work, the application needs some settings, so please provide the following to your `application.yml` (resources folder):
Note that the `introspection-uri`, `client-id` and `client-secret` are only placeholders. You can either change them in here using the values provided by ZITADEL
or pass them later on as arguments when starting the application.
Create example API [#create-example-api]
Create a `api` package with a `ExampleController.java` file with the content below. This will create an API with three endpoints / methods:
* `/api/healthz`: can be called by anyone and always returns `OK`
* `/api/tasks (GET)`: requires authorization and returns the available tasks
* `/api/tasks (POST)`: requires authorization with granted `admin` role and adds the task to the list
If authorization is required, the token must not be expired and the API has to be part of the audience (either client\_id or project\_id).
For tests we will use a Personal Access Token or the [Java Spring web example](../login/java-spring).
Test API [#test-api]
In case you've created your own application and depending on your development setup you might need to build the application first:
```bash
mvn clean package -DskipTests
```
You will need to provide the `introspection-uri` ($CUSTOM\_DOMAIN/oauth/v2/introspect), the `client-id` and `client-secret` previously created:
```bash
java \
-Dspring.security.oauth2.resourceserver.opaquetoken.introspection-uri= \
-Dspring.security.oauth2.resourceserver.opaquetoken.client-id= \
-Dspring.security.oauth2.resourceserver.opaquetoken.client-secret= \
-jar api/target/api-0.0.2-SNAPSHOT.jar
```
This could look like:
```bash
java \
-Dspring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://my-domain.zitadel.cloud/oauth/v2/introspect \
-Dspring.security.oauth2.resourceserver.opaquetoken.client-id=243861220627644836@example \
-Dspring.security.oauth2.resourceserver.opaquetoken.client-secret=WJKLF3kfPOi3optkg9vi3jmfjv8oj32nfiäohj!FSC09RWUSR \
-jar web/target/web-0.0.2-SNAPSHOT.jar
```
Public endpoint [#public-endpoint]
Now you can call the API by browser or curl. Try the healthz endpoint first:
```bash
curl -i http://localhost:18090/api/healthz
```
it should return something like:
```
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: text/plain;charset=UTF-8
Content-Length: 2
Date: Mon, 15 Jan 2024 09:07:21 GMT
OK
```
Task list [#task-list]
and the task list endpoint:
```bash
curl -i http://localhost:18090/api/tasks
```
it will return:
```
HTTP/1.1 401
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
WWW-Authenticate: Bearer
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Length: 0
Date: Mon, 15 Jan 2024 09:07:55 GMT
```
Get a valid access\_token for the API. You can either achieve this by getting an access token with the project\_id in the audience
(e.g. by using the [Spring Boot web example](../login/java-spring)) use a PAT of a service account.
If you provide a valid Bearer Token:
```bash
curl -i -H "Authorization: Bearer ${token}" http://localhost:18090/api/tasks
```
it will return an empty list:
```
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json
Transfer-Encoding: chunked
Date: Mon, 15 Jan 2024 09:15:10 GMT
[]
```
Try to add a new task [#try-to-add-a-new-task]
Let's see what happens if you call the tasks endpoint:
```bash
curl -i -X POST -H "Authorization: Bearer ${token}" -H "Content-Type: application/json" --data 'my new task' http://localhost:18090/api/tasks
```
it will complain with a permission denied (missing `admin` role):
```
HTTP/1.1 403
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
WWW-Authenticate: Bearer error="insufficient_scope", error_description="The request requires higher privileges than provided by the access token.", error_uri="https://tools.ietf.org/html/rfc6750#section-3.1"
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Length: 0
Date: Mon, 15 Jan 2024 09:24:39 GMT
```
Add administrator role [#add-administrator-role]
So let's create the role and grant it to the user. To do so, go to your project in ZITADEL Management Console
and create the role by selecting `Roles` in the navigation and then clicking on the `New Role` button.
Finally, create the role as shown below:
After you have created the role, let's grant it to the user who requested the tasks.
Click on `Role Assignments` in the navigation and create a new one by selecting the user and the `admin` role.
After successful creation, it should look like:
So you should now be able to add a new task:
```bash
curl -i -X POST -H "Authorization: Bearer ${token}" -H "Content-Type: application/json" --data 'my new task' http://localhost:18090/api/tasks
```
which will report back the successful addition:
```
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json
Content-Length: 10
Date: Mon, 15 Jan 2024 09:26:11 GMT
task added
```
Let's now retrieve the task list again:
```bash
curl -i -H "Authorization: Bearer ${token}" http://localhost:18090/api/tasks
```
As you can see your new task is listed:
```
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json
Transfer-Encoding: chunked
Date: Mon, 15 Jan 2024 09:26:48 GMT
["my new task"]
```
# Secure Nest.js API Application with ZITADEL
ZITADEL with Node.js (NestJS) [#zitadel-with-node-js-nest-js]
Community Contribution This example was created by a member of the ZITADEL community. It’s a great resource for seeing how others are building, but please note it is maintained by the community rather than the ZITADEL core team.
This documentation section guides you through the process of integrating ZITADEL into your Node.js backend using the NestJS framework. The provided example demonstrates authentication using an OIDC (OAuth2) token introspection strategy with a ZITADEL service account for machine-to-machine communication.
Overview [#overview]
The NestJS API includes a single secured route that prints "Hello World!" when authenticated. The API expects an authorization header with a valid JWT, serving as a bearer token to authenticate the user when calling the API. The API will validate the access token on the [introspect endpoint](/apis/openidoauth/endpoints#introspection-endpoint) and receive the user from ZITADEL.
The API application utilizes [JWT with Private Key](/apis/openidoauth/authn-methods#jwt-with-private-key) for authentication against ZITADEL and accessing the introspection endpoint. Make sure to create an API Application within Zitadel and download the JSON. In this instance, we use this service account, so make sure to provide the secrets in the example application via environmental variables.
Overview [#overview]
The NestJS API includes a private endpoint `GET http://localhost:${APP_PORT}/api/v1/app`, which returns "Hello World" when authenticated. The authentication is performed using a JWT obtained through the token introspection strategy.
Running the Example [#running-the-example]
Prerequisites [#prerequisites]
Make sure you have Node.js and npm installed on your machine.
ZITADEL Setup for the API [#zitadel-setup-for-the-api]
1. Create a ZITADEL instance and a project by following the steps [here](/guides/start/quickstart#3-create-your-zitadel-instance).
2. Set up an API application within your project:
* Create a new application of type "API" with authentication method "Private Key".
* Create a and save the Private Key JSON file.
Create and Run the API [#create-and-run-the-api]
Clone or download the [example repository](https://github.com/ehwplus/zitadel-nodejs-nestjs):
```bash
git clone https://github.com/ehwplus/zitadel-nodejs-nestjs && cd zitadel-nodejs-nestjs
```
and follow the instructions here: [https://github.com/ehwplus/zitadel-nodejs-nestjs/blob/main/README.md#installation](https://github.com/ehwplus/zitadel-nodejs-nestjs/blob/main/README.md#installation)
Test the API [#test-the-api]
Call the API without authorization headers:
```bash
curl --request GET \
--url http://localhost:${APP_PORT}/api/v1/app
```
You should get a response with Status Code 401 and an error message.
Now, add an authorization header with a valid JWT obtained through ZITADEL:
```bash
export JWT=your-valid-jwt
curl --request GET \
--url http://localhost:${APP_PORT}/api/v1/app \
--header "authorization: Bearer $JWT"
```
You should now receive a response with Status Code 200 and the message:
```json
"Hello World!"
```
Congratulations! You have successfully integrated ZITADEL authentication into your NestJS API using the Token Introspection strategy.
# Secure Pylon API Application with ZITADEL
This integration guide demonstrates the recommended way to incorporate ZITADEL into your [Pylon](https://pylon.cronit.io) service.
It explains how to check the token validity in the API and how to check for permissions.
By the end of this guide, your application will have three different endpoint which are public, private(valid token) and private-scoped(valid token with specific role).
ZITADEL setup [#zitadel-setup]
Before we can start building our application, we have to do a few setup steps in ZITADEL Management Console.
Create application [#create-application]
Create Service Account [#create-service-account]
Assign a role to the Service Account [#assign-a-role-to-the-service-account]
Prerequisites [#prerequisites]
At the end you should have the following for the API:
* Issuer, something like `https://example.zitadel.cloud` or `http://localhost:8080`
* `.json`-key-file for the API, from the application
* ID of the project
And the following from the Service Account:
* `.json`-key-file from the service account
Setup new Pylon service [#setup-new-pylon-service]
Pylon allows you to create a new service using the `npm create pylon` command. This command creates a new Pylon project with a basic project structure and settings.
During the setup process, you can choose your preferred runtime, such as Bun, Node.js, or Cloudflare Workers.
**This guide uses the Bun runtime.**
Creating a new project [#creating-a-new-project]
To create a new Pylon project, run the following command:
```bash
npm create pylon my-pylon@latest
```
This will create a new directory called `my-pylon` with a basic Pylon project structure.
Project structure [#project-structure]
Pylon projects are structured as follows:
```
my-pylon/
├── .pylon/
├── src/
│ ├── index.ts
├── package.json
├── tsconfig.json
```
* `.pylon/`: Contains the production build of your project.
* `src/`: Contains the source code of your project.
* `src/index.ts`: The entry point of your Pylon service.
* `package.json`: The npm package configuration file.
* `tsconfig.json`: The TypeScript configuration file.
Basic example [#basic-example]
Here's an example of a basic Pylon service:
```ts
export const graphql = {
Query: {
sum: (a: number, b: number) => a + b,
},
Mutation: {
divide: (a: number, b: number) => a / b,
},
};
export default app;
```
Secure the API [#secure-the-api]
Add ZITADEL info to the service [#add-zitadel-info-to-the-service]
1. Create a `.env` file in the root folder of your project and add the following settings:
```bash
AUTH_ISSUER='URL to the zitadel instance'
AUTH_PROJECT_ID='ID of the project'
```
It should look something like this:
```bash
AUTH_ISSUER='https://example.zitadel.cloud'
AUTH_PROJECT_ID='250719519163548112'
```
2. Copy the `.json`-key-file that you downloaded from the ZITADEL Management Console into the root folder of your project and rename it to `key.json`.
3. (Optional) For added convenience in production environments, you can include the content of the .json key file as `AUTH_KEY` in the .env file or as an environment variable.
Auth [#auth]
Pylon provides a auth module and a decorator to check the validity of the token and the permissions.
* `auth.initialize()`: Initializes the authentication middleware.
* `auth.require()` : Middleware to check if the token is valid.
* `auth.require({roles: ['role']})`: Middleware to check if the token is valid and has the specified roles.
* `requireAuth()`: Decorator to check if the token is valid.
* `requireAuth({roles: ['role']})`: Decorator to check if the token is valid and has the specified roles.
Build the Pylon service [#build-the-pylon-service]
Now we will create a new Pylon service with the following endpoints:
* `/api/public`: Public endpoint
* `/api/private`: Private endpoint
* `/api/private-scoped`: Private endpoint with specific role
* `/graphql`: GraphQL endpoint
* Query: `me`: Private endpoint that returns the current user and the messages if the role is `read:messages`
* Query: `info`: Public endpoint
Create the service [#create-the-service]
The following code demonstrates how to create a Pylon service with the required endpoints, it must be added to the `src/index.ts` file of your project:
```ts
import {
app,
auth,
requireAuth,
getContext,
ServiceError,
} from "@getcronit/pylon";
class User {
id: string;
name: string;
#messages: string[];
constructor(id: string, name: string, messages: string[]) {
this.id = id;
this.name = name;
this.#messages = messages;
}
@requireAuth({ roles: ["read:messages"] })
async messages() {
return this.#messages;
}
static users: User[] = [];
@requireAuth()
static async me() {
const ctx = getContext();
const id = ctx.get("auth")!.sub;
const user = User.users.find((user) => user.id === id);
if (!user) {
throw new ServiceError("User not found", {
statusCode: 404,
code: "USER_NOT_FOUND",
});
}
return user;
}
@requireAuth()
static async create() {
const ctx = getContext();
const auth = ctx.get("auth")!;
// Check if the user already exists
if (User.users.find((user) => user.id === auth.sub)) {
throw new ServiceError("User already exists", {
statusCode: 400,
code: "USER_ALREADY_EXISTS",
});
}
const user = new User(auth.sub, auth.username || "unknown", [
"Welcome to Pylon with ZITADEL!",
]);
User.users.push(user);
return user;
}
}
export const graphql = {
Query: {
me: User.me,
info: () => "Public Data",
},
Mutation: {
createUser: User.create,
},
};
// Initialize the authentication middleware
app.use("*", auth.initialize());
// Automatically try to create a user for each request for demonstration purposes
app.use(async (_, next) => {
try {
await User.create();
} catch {
// Ignore errors
// Fail silently if the user already exists
}
await next();
});
app.get("/img/api/info", (c) => {
return new Response("Public Data");
});
// The `auth.require()` middleware is optional here, as the `User.me` method already checks for it.
app.get("/img/api/me", auth.require(), async (c) => {
const user = await User.me();
return c.json(user);
});
// A role check for `read:messages` is not required here, as the `user.messages` method already checks for it.
app.get("/img/api/me/messages", auth.require(), async (c) => {
const user = await User.me();
// This will throw an error if the user does not have the `read:messages` role
return c.json(await user.messages());
});
export default app;
```
Call the API [#call-the-api]
To call the API you need an access token, which is then verified by ZITADEL.
Please follow [this guide here](/guides/integrate/token-introspection/private-key-jwt), ignoring the first step as we already have the `.json`-key-file from the serviceaccount.
You can also create a PAT for the service account and use it to test the API. For this, follow [this guide](/guides/integrate/service-accounts/personal-access-token#create-a-service-account-with-a-pat).
Optionally set the token as an environment variable:
```
export TOKEN='MtjHodGy4zxKylDOhg6kW90WeEQs2q...'
```
Now you have to start the Pylon service:
```bash
bun run dev
```
With the access token, you can then do the following calls:
1. GraphQL:
```
curl -H "Authorization: Bearer $TOKEN" -G http://localhost:3000/graphql --data-urlencode 'query={ info }'
curl -H "Authorization: Bearer $TOKEN" -G http://localhost:3000/graphql --data-urlencode 'query={ me { id name } }'
curl -H "Authorization: Bearer $TOKEN" -G http://localhost:3000/graphql --data-urlencode 'query={ me { id name messages } }'
```
You can also visit the GraphQL playground at `http://localhost:3000/graphql` and execute the queries there.
2. Routes:
```
curl -H "Authorization: Bearer $TOKEN" -X GET http://localhost:3000/api/info
curl -H "Authorization: Bearer $TOKEN" -X GET http://localhost:3000/api/me
curl -H "Authorization: Bearer $TOKEN" -X GET http://localhost:3000/api/me/messages
```
Completion [#completion]
Congratulations! You have successfully integrated your Pylon with ZITADEL!
If you get stuck, consider checking out their [documentation](https://pylon.cronit.io). If you face issues, contact Pylon or raise an issue on [GitHub](https://github.com/getcronit/pylon/issues).
# Secure Django (Python) API Application with ZITADEL
This integration guide demonstrates the recommended way to incorporate ZITADEL into your Django Python application.
It explains how to check the token validity in the API and how to check for permissions.
By the end of this guide, your application will have three different endpoints that are public, private (valid token) and private-scoped (valid token with a specific role).
This documentation references our [example](https://github.com/zitadel/example-python-django-oauth) on GitHub.
ZITADEL setup [#zitadel-setup]
Before we can start building our application, we have to do a few setup steps in the ZITADEL Management Console.
Create application [#create-application]
Create Service Account [#create-service-account]
Assign a role to the Service Account [#assign-a-role-to-the-service-account]
Prerequisites [#prerequisites]
At the end you should have the following for the API:
* Issuer, something like `https://example.zitadel.cloud` or `http://localhost:8080`
* Introspection URL, something like `https://example.zitadel.cloud/oauth/v2/introspect`
* Token URL, something like `https://example.zitadel.cloud/oauth/v2/token`
* `.json`-key-file for the API, from the application
* ID of the project
And the following from the Service Account:
* `.json`-key-file from the service account
Setup new Django application [#setup-new-django-application]
Setup Python [#setup-python]
Install dependencies [#install-dependencies]
For this example we need the following dependencies:
* `django`: to create an API with django
* `python-dotenv`: to use environment variables in the settings
* `authlib`: client-side OAuth functionality
* `requests`: HTTP requests for the introspection
For the dependencies we need a requirements.txt-file with the following content:
Then install all dependencies with:
```bash
python -m pip install -U requirements.txt
```
Then in your folder of choice, call the following command to create a Django base:
```bash
django-admin startproject myapi .
```
Define the Django API [#define-the-django-api]
Add to the settings.py to include ZITADEL info [#add-to-the-settings-py-to-include-zitadel-info]
There is info needed for the introspection calls, which we put into the settings.py:
and create a ".env"-file in the root folder with the settings as an example:
```bash
ZITADEL_INTROSPECTION_URL = 'URL to the introspection endpoint to verify the provided token'
ZITADEL_DOMAIN = 'Domain used as audience in the token verification'
API_PRIVATE_KEY_FILE_PATH = 'Path to the key.json created in ZITADEL'
```
I should look something like this:
```bash
ZITADEL_INTROSPECTION_URL = 'https://example.zitadel.cloud/oauth/v2/introspect'
ZITADEL_DOMAIN = 'https://example.zitadel.cloud'
API_PRIVATE_KEY_FILE_PATH = '/tmp/example/250719519163548112.json'
```
Validator definition [#validator-definition]
To validate the tokens, we need a validator which can be called in the event of API-calls.
validator.py:
Requests and URLs [#requests-and-ur-ls]
We define 3 different endpoints which differ in terms of requirements.
views.py:
To handle endpoints the urls have to be added to the urls.py:
DB [#db]
Create and run migrations:
```bash
python manage.py migrate
```
Run [#run]
You can use a local Django server to test the application.
```bash
python manage.py runserver
```
Call the API [#call-the-api]
To call the API you need an access token, which is then verified by ZITADEL.
Please follow [this guide here](/guides/integrate/token-introspection/private-key-jwt), ignoring the first step as we already have the `.json`-key-file from the serviceaccount.
Optionally set the token as an environment variable:
```
export TOKEN='MtjHodGy4zxKylDOhg6kW90WeEQs2q...'
```
With the access token, you can then do the following calls:
```
curl -H "Authorization: Bearer $TOKEN" -X GET http://localhost:8000/api/public
curl -H "Authorization: Bearer $TOKEN" -X GET http://localhost:8000/api/private
curl -H "Authorization: Bearer $TOKEN" -X GET http://localhost:8000/api/private-scoped
```
Completion [#completion]
Congratulations! You have successfully integrated your Django API with ZITADEL!
If you get stuck, consider checking out our [example](https://github.com/zitadel/example-python-django-oauth) application. This application includes all the functionalities mentioned in this quick-start. You can start by cloning the repository and defining the settings in the settings.py. If you face issues, contact us or raise an issue on [GitHub](https://github.com/zitadel/example-python-django-oauth/issues).
# Secure Flask (Python) API Application with ZITADEL
This example shows you how to secure a Python3 Flask API with both authentication and authorization using ZITADEL.
Overview [#overview]
The Python API will have public, private, and private-scoped routes and check if a user is authenticated and authorized to access the routes.
The private routes expect an authorization header with a valid access token in the request. The access token is used as a bearer token to authenticate the user when calling the API.
The API will validate the access token on the [introspect endpoint](/apis/openidoauth/endpoints#introspection-endpoint) and will receive the user's roles from ZITADEL.
The API application uses [Client Secret Basic](/apis/openidoauth/authn-methods#client-secret-basic) to authenticate against ZITADEL and access the introspection endpoint.
You can use any valid access\_token from a user or service account to send requests to the example API.
In this example we will use a service account with a [personal access token](/guides/integrate/service-accounts/personal-access-token) which can be used directly to access the example API.
Running the example [#running-the-example]
Python Prerequisites [#python-prerequisites]
In order to run the example you need to have `python3` and `pip3` installed.
ZITADEL settings for the API [#zitadel-settings-for-the-api]
You need to setup a couple of things in ZITADEL.
1. If you don't have an instance yet, please go ahead and create an instance as explained [here](/guides/start/quickstart#3-create-your-zitadel-instance). Also, create a new project by following the steps [here](/guides/start/quickstart#4-create-your-project-and-application).
2. You must create an API application in your project. Follow [this guide](/guides/manage/console/applications-overview) to create a new application of type "API" with authentication method "Basic". Save both the ClientID and ClientSecret after you create the application.
Create the API [#create-the-api]
1. Clone or download this [Python project](https://github.com/zitadel/example-api-python3-flask) to your workspace.
```
git clone https://github.com/zitadel/example-api-python3-flask
cd example-api-python3-flask
```
2. The [server.py](https://github.com/zitadel/example-api-python3-flask/blob/main/server.py) file contains a Flask-based API that provides authentication for routes using the OpenID Connect protocol as shown below.
```python
from flask import Flask, jsonify, Response
from authlib.integrations.flask_oauth2 import ResourceProtector
from validator import ZitadelIntrospectTokenValidator, ValidatorError
require_auth = ResourceProtector()
require_auth.register_token_validator(ZitadelIntrospectTokenValidator())
APP = Flask(__name__)
@APP.errorhandler(ValidatorError)
def handle_auth_error(ex: ValidatorError) -> Response:
response = jsonify(ex.error)
response.status_code = ex.status_code
return response
@APP.route("/img/api/public")
def public():
"""No access token required."""
response = (
"Public route - You don't need to be authenticated to see this."
)
return jsonify(message=response)
@APP.route("/img/api/private")
@require_auth(None)
def private():
"""A valid access token is required."""
response = (
"Private route - You need to be authenticated to see this."
)
return jsonify(message=response)
@APP.route("/img/api/private-scoped")
@require_auth(["read:messages"])
def private_scoped():
"""A valid access token and scope are required."""
response = (
"Private, scoped route - You need to be authenticated and have the role read:messages to see this."
)
return jsonify(message=response)
if __name__ == "__main__":
APP.run()
```
The API has three routes:
"/img/api/public" - No access token is required.
"/img/api/private" - A valid access token is required.
"/img/api/private-scoped" - A valid access token and a "read:messages" scope are required.
The [validator.py](https://github.com/zitadel/example-api-python3-flask/blob/main/validator.py) file implements the ZitadelIntrospectTokenValidator class, which is a custom class that inherits from the IntrospectTokenValidator class provided by the authlib library. The introspection process retrieves the token details from ZITADEL using ZITADEL's introspection endpoint.
```python
from os import environ as env
import os
import time
from typing import Dict
from authlib.oauth2.rfc7662 import IntrospectTokenValidator
import requests
from dotenv import load_dotenv, find_dotenv
from requests.auth import HTTPBasicAuth
load_dotenv()
ZITADEL_DOMAIN = os.getenv("ZITADEL_DOMAIN")
CLIENT_ID = os.getenv("CLIENT_ID")
CLIENT_SECRET = os.getenv("CLIENT_SECRET")
class ValidatorError(Exception):
def __init__(self, error: Dict[str, str], status_code: int):
super().__init__()
self.error = error
self.status_code = status_code
# Use Introspection in Resource Server
# https://docs.authlib.org/en/latest/specs/rfc7662.html#require-oauth-introspection
class ZitadelIntrospectTokenValidator(IntrospectTokenValidator):
def introspect_token(self, token_string):
url = f'{ZITADEL_DOMAIN}/oauth/v2/introspect'
data = {'token': token_string, 'token_type_hint': 'access_token', 'scope': 'openid'}
auth = HTTPBasicAuth(CLIENT_ID, CLIENT_SECRET)
resp = requests.post(url, data=data, auth=auth)
resp.raise_for_status()
return resp.json()
def match_token_scopes(self, token, or_scopes):
if or_scopes is None:
return True
roles = token["urn:zitadel:iam:org:project:roles"].keys()
for and_scopes in or_scopes:
scopes = and_scopes.split()
"""print(f"Check if all {scopes} are in {roles}")"""
if all(key in roles for key in scopes):
return True
return False
def validate_token(self, token, scopes, request):
print (f"Token: {token}\n")
now = int( time.time() )
if not token:
raise ValidatorError({
"code": "invalid_token_revoked",
"description": "Token was revoked." }, 401)
"""Expired"""
if token["exp"] < now:
raise ValidatorError({
"code": "invalid_token_expired",
"description": "Token has expired." }, 401)
"""Revoked"""
if not token["active"]:
raise InvalidTokenError()
"""Insufficient Scope"""
if not self.match_token_scopes(token, scopes):
raise ValidatorError({
"code": "insufficient_scope",
"description": f"Token has insufficient scope. Route requires: {scopes}" }, 401)
def __call__(self, *args, **kwargs):
res = self.introspect_token(*args, **kwargs)
return res
```
3. Create a new file named ".env" in the directory. Copy the settings in the [".env.example"](https://github.com/zitadel/example-api-python3-flask/blob/main/.env.example) file to the newly created .env file. Set the values with your Custom Domain/Issuer URL, Client ID, and Client Secret from the previous steps. Obtain your Issuer URL by following [these steps](/guides/start/quickstart#5-collect-your-integration-keys).
```python
ZITADEL_DOMAIN = "https://custom-domain-abcdef.zitadel.cloud"
CLIENT_ID = "197....@projectname"
CLIENT_SECRET = "NVAp70IqiGmJldbS...."
```
ZITADEL settings to create a service account [#zitadel-settings-to-create-a-service-account]
1. Create a service account and a Personal Access Token (PAT) for that user by following [this guide](/guides/integrate/service-accounts/personal-access-token#create-a-service-account-with-a-pat).
2. To assign roles, follow [this guide](/guides/manage/console/roles) to create a role `read:messages` on your project.
3. Next, add the role `read:messages` to the service account you created. Follow this [guide](/guides/manage/console/roles#role-assignments) for more information on creating a role assignment.
Run the API [#run-the-api]
1. Install required dependencies by running `pip3 install -r requirements.txt` on your terminal.
2. Run the API with the `python3 server.py` command.
3. Open another terminal and follow the next step to test the API.
Test the API [#test-the-api]
Public route [#public-route]
Invoke the public route by running the following command:
```
curl --request GET \
--url http://127.0.0.1:5000/api/public
```
You should get a response with Status Code 200 and the following message.
`{"message":"Public route - You don't need to be authenticated to see this."}`
Private route [#private-route]
Call the private route without authorization headers by running the following command:
```
curl --request GET \
--url http://127.0.0.1:5000/api/private
```
You should get a response with Status Code 401 and an error message.
Now let's add an authorization header to your request. Save the personal access token for your service account to a variable by running the following command. Replace the value with the PAT you obtained earlier.
`PAT=nr9vnUTkQkn4rxWk...`
Then call the private route with the PAT in the authorization header.
```
curl --request GET \
--url http://127.0.0.1:5000/api/private \
--header "authorization: Bearer $PAT"
```
Now you should get a response with Status Code 200 and the following message.
`{"message":"Private route - You need to be authenticated to see this."}`
Private route, protected [#private-route-protected]
Call the private route that requires the user to have a certain role
```
curl --request GET \
--url http://127.0.0.1:5000/api/private-scoped \
--header "authorization: Bearer $PAT"
```
You should get a response with Status Code 200 and the following message.
`{"message":"Private, scoped route - You need to be authenticated and have the role read:messages to see this."}`
You can remove the role from the service account in ZITADEL and try again. You should then get a Status Code 403, Forbidden error.
# Embed Authenticated MongoDB Charts Using ZITADEL
This integration guide shows how you can embed authenticated MongoDB Charts in your web application using ZITADEL as authentication provider.
Setup ZITADEL Application [#setup-zitadel-application]
Before you can embed an authenticated chart in your application, you have to do a few setup 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 Settings* section, change the *Auth Token Type* to JWT
Your application settings should now look similar to this:
Setup Custom JWT Provider for MongoDB Charts [#setup-custom-jwt-provider-for-mongo-db-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 settings should look similar to this:
Embedding your Chart [#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
```
```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: "",
getUserToken: () => {
return this.auth.getAccessToken()
},
});
const chart = sdk.createChart({
chartId: ""
});
await chart.render(document.getElementById("chart"));
}
}
```
# OIDC 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 applications about session termination at the OpenID Provider. This guide will explain how
back-channel logout is implemented inside ZITADEL and gives some usage examples.
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 [#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 application using the same SSO session.
This allows the application to also invalidate the user's session without the need for an active browser session.
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 [#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 by default.
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 [#client]
To enable the back-channel logout on an application, they simply need to register a `backchannel_logout_uri` as part of their
settings, e.g. [creating an OIDC application](/guides/manage/console/applications-overview).
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 [#back-channel-request]
When the session is terminated, ZITADEL will send back-channel logout requests asynchronously to every registered
application 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 [#logout-token]
The `logout_token` sent in the request is a JWT similar to an ID Token.
Note however, that a Logout Token must never contain a `nonce` claim, to make sure it cannot be used as an ID Token.
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](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 [#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 [#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 [#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 [#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.
# Streaming audit logs to external systems (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 [#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]
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 [#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 [#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
# Integrate
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
# Retrieve User Roles in ZITADEL
This guide explains all the possible ways of retrieving user roles across different organizations and projects using ZITADEL's APIs.
What are roles and role assignments in ZITADEL? [#what-are-roles-and-role-assignments-in-zitadel]
User role assignments 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 Console refers to the pairing of roles and users as role assignments, whereas the APIs refer to them as authorizations or user grants. This guide will use the term roles for application-specific roles (e.g., `admin`, `accountant`, `employee`, `hr`, etc.) and ZITADEL-specific manager 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 [#assign-roles-and-memberships]
Users or service accounts 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/administrators)
* [Add application roles via the Project Service API](/reference/api/project/zitadel.project.v2.ProjectService.AddProjectRole)
* [Add administrator roles to users via the Internal Permission Service API](/reference/api/internal_permission/zitadel.internal_permission.v2.InternalPermissionService.CreateAdministrator)
Retrieve roles [#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 [#generate-a-token]
You must first of all generate a token for the user. For 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 service account 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 users](/guides/integrate/login/oidc/login-users)
* [Generate tokens for service accounts](/guides/integrate/service-accounts/authenticate-service-accounts)
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 [#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 application (in one project) but access resources from another application (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 [#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.
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.
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 [#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 [#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 '
```
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": "david.wallace@dundermifflin.com",
"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`
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.
**Sample response:**
```bash
{
"email": "david.wallace@dundermifflin.com",
"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 [#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.
Example on github
```js reference
https://github.com/zitadel/actions/blob/main/examples/custom_roles.js
```
Retrieve roles using the auth API [#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 setup. You can use the logged-in user’s token or the service account’s token to retrieve the roles assigned to this user using the [APIs listed under user authorizations/grants in the auth API](/reference/api/authorization/zitadel.authorization.v2.AuthorizationService.ListAuthorizations).
**Scope used:** `openid urn:zitadel:iam:org:project:id:zitadel:aud`
**1. [List my project roles](/reference/api/auth/zitadel.auth.v1.AuthService.ListMyProjectPermissions)** [#1-list-my-project-roles]
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 '
```
**Sample response:**
```bash
{
"result": [
"cfo"
]
}
```
**2.[List my ZITADEL permissions](/reference/api/auth/zitadel.auth.v1.AuthService.ListMyZitadelPermissions)** [#2-list-my-zitadel-permissions]
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 '
```
**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 role assignments](/reference/api/auth/zitadel.auth.v1.AuthService.ListMyUserGrants)** [#3-list-my-role-assignments]
Returns a list of roles the authenticated user has been assigned. Role assignments (referred to as user grants in the APIs) 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 ' \
--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 [#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)** [#1-search-user-grants]
Returns a list of user roles that match the search queries. A user with administrator 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 ' \
--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": "david.wallace@dundermifflin.com",
"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)** [#2-user-grant-by-id]
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 '
```
Summary [#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.
# SCIM Provisioning from Okta
This guide provides step-by-step instructions to configure SCIM provisioning from Okta into ZITADEL.
Pre-requisites: [#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 [#step-1-set-up-scim-provisioning-in-zitadel]
SCIM provisioning in ZITADEL is accomplished by authenticating a Service Account with appropriate permissions.
1. **Create a Service Account**:
* Follow [this guide](https://zitadel.com/docs/guides/manage/console/users-overview) to create a Service Account within your ZITADEL Organization.
2. **Assign the Role**:
* Grant the Service Account 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 Account can be found [here](https://zitadel.com/docs/guides/integrate/service-accounts/authenticate-service-accounts).
Step 2: Set Up SCIM in Okta [#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**.
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**.
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 Settings** 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.
Step 3: Attribute Mapping (Recommended) [#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 [#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 [#helpful-reference-links]
* [Authenticate users with SAML](https://zitadel.com/docs/guides/integrate/login/saml)
* [ZITADEL: Creating Service Accounts](https://zitadel.com/docs/guides/manage/console/users-overview)
* [ZITADEL: Service Account Authentication](https://zitadel.com/docs/guides/integrate/service-accounts/authenticate-service-accounts)
* [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)
# OAuth 2.0 Token Exchange (RFC 8693): Impersonation & Delegation
The Token Exchange grant implements [RFC 8693, OAuth 2.0 Token Exchange](https://www.rfc-editor.org/rfc/rfc8693) and can be used to exchange tokens to a different scope, audience or subject. Changing the subject of an authenticated token is called impersonation or delegation. This guide will explain how token exchange is implemented inside ZITADEL and gives some usage examples.
In this guide we assume that the application performing the token exchange is already in possession of tokens. 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
The basics [#the-basics]
Token Exchange is a complex and broad subject. Before we get our hands dirty with the "how-to" part, lets first cover some basics.
Token types [#token-types]
Token Exchange offers a range of possibilities for providing and requesting different token types. The existence of the various `*_token_type` fields in the request and response data helps defining which tokens we are sending, which ones we wish to receive and finally which one(s) we did receive in the response.
Access Token type [#access-token-type]
```
urn:ietf:params:oauth:token-type:access_token
```
Access tokens can be supplied in the request, or requested to be in the response. When supplied as `subject_token` or `actor_token` this may be an opaque token or JWT.
The client does not need to care about the difference between the access token types in this case, it can pass the `access_token` value previously obtained from the token endpoint as-is.
When requesting an access token, token exchange will always return an opaque token. If a JWT is required, use the `urn:ietf:params:oauth:token-type:jwt` identifier for `requested_token_type`.
Refresh Token type [#refresh-token-type]
```
urn:ietf:params:oauth:token-type:refresh_token
```
At the moment we do not support sending refresh tokens as part of the Token Exchange grant. Instead, use the [`refresh_token` grant](/apis/openidoauth/endpoints#refresh-token-grant).
ID Token type [#id-token-type]
```
urn:ietf:params:oauth:token-type:id_token
```
ID Tokens can be supplied as `subject_token` and `actor_token`. We currently reject any expired ID Tokens, even as `subject_token`. This might change in future.
When requested as `requested_token_type`, the [response](#token-exchange-response) will carry the ID Token in the `access_token` field. The `token_type` will be set `N_A`, meaning that the returned `access_token` value cannot be used as Access Token. This is how the RFC specifies the behavior.
If you want both a "real" access token and ID token, request an access token or JWT token-type and set the `openid` scope. This will return both tokens similar to the other grand types.
JWT Token type [#jwt-token-type]
```
urn:ietf:params:oauth:token-type:jwt
```
The JWT token type caries a double meaning.
When used as a `subject_token_type`, ZITADEL will try to verify the `subject_token` in a similar way as a JWT Profile. The `sub` field of the JWT is used to set the subject of the requested token. Currently we only allow self-signed JWT as `subject_token` in combination with a valid `actor_token` for impersonation. A self-signed JWT is not enough to obtain other token types from the Token Exchange Grant. You will need to use the [JWT Profile grant](/apis/openidoauth/endpoints#jwt-profile-grant) instead.
When used as a `requested_token_type`, ZITADEL will return an access token as JWT.
User ID Token type [#user-id-token-type]
```
urn:zitadel:params:oauth:token-type:user_id
```
Technically not a token and an addition to the standard. It is provided for impersonation cases where there is no token available yet for the impersonated user.
This allows setting the plain zitadel user ID in the `subject_token`, along with a valid `actor_token` from the impersonator. The existence of the user is checked.
Sending only the user ID in the `subject_token` is not allowed and will result in an error.
Token exchange request [#token-exchange-request]
The details supplied in the request changes how Token Exchange operates. While the standard is very permissive, we need to clarify how ZITADEL implements it.
Subject token [#subject-token]
The `subject_token` and `subject_token_type` fields come in a pair. The [token type](#token-types) describes the subject token that is passed. This tells ZITADEL how we can verify the token.
The subject token is the most basic input for the token exchange. It describes for *who* we want to obtain a token. If only the `subject_token` with proper `subject_token_type` are supplied, a new access token is returned for the same user, with the same scope and the same audience.
We currently allow all token types, except refresh tokens, to be used as subject token. The JWT and User ID types depend on the presence of the [`actor_token`](#actor-token)
Actor token [#actor-token]
The actor parameters are optional and enable impersonation and delegation. At ZITADEL we don't make any distinction between the two concepts, so we call both cases impersonation from this point.The `actor_token` and `actor_token_type` come in a pair. If the actor token is provided, the actor token type must also be specified.
Currently only a valid access token or ID token are allowed as actor token. The user represented by the actor token must have the [impersonation permission](#impersonation-permissions) set, or else the request will be rejected and an error returned.
Requested token type [#requested-token-type]
The `requested_token_type` is an optional field that tells ZITADEL the type of token that is requested for the `access_token` response field. Note that the response can also contain ID and refresh tokens, based on [scope](#scope), even if the requested token type was an access token.
Currently ZITADEL supports requesting of:
* Opaque Access Token with the `urn:ietf:params:oauth:token-type:access_token` type;
* JWT Access Token with the `urn:ietf:params:oauth:token-type:jwt` type;
* ID Token with the `urn:ietf:params:oauth:token-type:id_token` type;
Scope [#scope]
[Scope](/apis/openidoauth/scopes) is an optional parameter that allows changing the scope of the supplied token, for the requested token. Scope can be entirely different from any of the supplied tokens. It can be used to extend or decrease the scope of the new token.
When scope is omitted in the request, it is taken from the `subject_token`. If the `subject_token` doesn't carry any scope (some types can't), it is taken from the `actor_token`. All allowed token types for the `actor_token` typically have a scope.
Audience [#audience]
Audience is an optional parameter that allows to decrease the audience of the requested token. When supplied it may never contain an audience which was not already present in either the `subject_token` or `actor_token` combined.
This is to prevent applications from one project or organization authorizing themselves access to applications of another project or organization and circumventing current ZITADEL authorization schemas.
When audience is omitted in the request, it is taken from the `subject_token`. If the `subject_token` doesn't carry any audience (some types can't), it is taken from the `actor_token`. All allowed token types for the `actor_token` typically have an audience.
Resource [#resource]
The resource parameter would allow mapping a URI to a target audience. This is further defined in [RFC 8707 Resource Indicators for OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc8707).
ZITADEL does not yet support Resource Indicators. Supplying this parameter will always result in a `invalid_target` error.
Token exchange response [#token-exchange-response]
The response schema looks very similar to the model of other token endpoint responses. The RFC attempts to reuse the same fields, however they might have different contents then they lead you to believe. This can lead to confusing situations, so be sure to read this section!
Access token [#access-token]
The `access_token` field contains the requested token, of the requested token type. **Even if the requested token is not an access token!** For example if the `requested_token_type` is an ID Token, the `access_token` field will actually contain an ID Token.
:exploding\_head:
Token Type [#token-type]
The `token_type` field gives us an idea of the token returned in the `access_token` field. It is not one of the `*_token_types` described above. It behaves almost like the other grand types. Normally this value is always `Bearer` but token exchange may also return `N_A` when a token cannot be used as a bearer token.
For example when the requested token type is an ID token, this value will be set to `N_A`, as an ID token cannot be send to an API as bearer token.
Issued token type. [#issued-token-type]
The `issued_token_type` contains one of the [token types](#token-types) described above. It should match the `requested_token_type` from the request.
Refresh token [#refresh-token]
The `refresh_token` may contain a new refresh token that can be used to refresh the `access_token` at a later moment. ZITADEL does not allow using refresh tokens in the Token Exchange grant. Refresh tokens can be used for the [`refresh_token` grant](/apis/openidoauth/endpoints#refresh-token-grant) instead, including ones obtained through Token Exchange.
A refresh token can be obtained by setting the `offline_access` [scope](#scope) in the request or applicable token.
ID Token [#id-token]
The `id_token` may contain an ID token. This is a non-standard field added by ZITADEL in order to match OpenID token responses. An ID Token may be obtained together with an access token or JWT token-type when the `openid` [scope](#scope) is set in the request or applicable token.
Expires in [#expires-in]
The `expires_in` returns the time in seconds the new `access_token` is valid. This value is given for all token types, even non-access tokens.
Scope [#scope]
The `scope` field contains the final scope of the obtained token. Scope might be different as the one requested, as ZITADEL validates the input. In the RFC the scope field is optional, but ZITADEL always send the value.
Now that we have the basics covered, we can get started with using the Token Exchange.
Simple Token Exchange examples [#simple-token-exchange-examples]
First we will cover "simple" Token Exchange which only involves exchanging the `subject_token` for a new token.
Preparation [#preparation]
These preparation steps are needed for all Token Exchange interaction, including impersonation.
Application [#application]
Next we need to select an application that is allowed to perform Token Exchange. As with the other grant types, we need to enable the `urn:ietf:params:oauth:grant-type:token-exchange` grant type.
ZITADEL allows any application to use Token Exchange, however we strongly recommend to only configure confidential clients (using either client credentials or JWT assertion) with the Token Exchange grant type. This is because there is some trust placed in the application when it comes to defining scope and that it obtained tokens in a legitimate way. For example, if the app possesses a token of an admin user with impersonation permissions it can obtain tokens for any other user in your instance. It is your responsibility to make sure the application can be trusted with this kind of powers. If you configure a public client with the Token Exchange grant, you risk a leaked token can be used by an attacker who knows the client ID of a granted public client.
Organization layout [#organization-layout]
For this example we have the following projects in our organization:
* **portal** contains the end user interfaces. In this case a web-app that initiated user login and performs operations on other APIs. The web-app has the token exchange grant type enabled;
* **aggregates** a project that contains APIs of low privilege which aggregate public data to return to the user;
* **settings** a project that contains APIs for settings and other privileged operations;
* **ZITADEL** the build-in project used for the zitadel management console and APIs;
Authenticated user tokens [#authenticated-user-tokens]
The *portal* web-app has been configured to include user info in the ID Token and completed a code-flow login for an user with the following scope:
```
openid profile email urn:zitadel:iam:org:project:id:259254020357488642:aud urn:zitadel:iam:org:project:id:259256588127174658:aud urn:zitadel:iam:org:project:id:zitadel:aud
```
The scope requested an access token and ID Token. The reserved scopes are used to add all of our defined projects to the audience of the token.
The resulting ID Token, user info and introspection responses will provide user profile information, user email and the token audience.
At the end of the code flow we have the following tokens:
Opaque Access token:
```
NaUAPHy5mLFQlwUCeUGYeDyhcQYuNhzTiYgwMor9BxP_bfMy2iDdLxJ87nntUc85vNyeHOY
```
ID token:
```
eyJhbGciOiJSUzI1NiIsImtpZCI6IjI1OTM3OTQwMTIwNzA1NDMzOCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJzdWIiOiIyNTkyNDIwMzkzNzg0NDQyOTAiLCJhdWQiOlsiMjU5MjU0NDA5MzIwNTI5OTIyQHBvcnRhbCIsIjI1OTI5Nzc3MzUwODE2NTYzNEBwb3J0YWwiLCIyNTkyNTQzMTcwNzkzMzA4MTgiLCIyNTkyNTQwMjAzNTc0ODg2NDIiLCIyNTkyNTY1ODgxMjcxNzQ2NTgiLCIyNTc3ODY5OTEyNDcyOTQ0NjgiXSwiZXhwIjoxNzExMTQxNjcwLCJpYXQiOjE3MTEwOTg0NzAsImF1dGhfdGltZSI6MTcxMTA5ODQ2OCwiYW1yIjpbInBhc3N3b3JkIiwicHdkIl0sImF6cCI6IjI1OTI1NDQwOTMyMDUyOTkyMkBwb3J0YWwiLCJjbGllbnRfaWQiOiIyNTkyNTQ0MDkzMjA1Mjk5MjJAcG9ydGFsIiwiYXRfaGFzaCI6InQxVDc4czhSVFZrdTJzeEJnMDNSQ1EiLCJjX2hhc2giOiJQdXBDMmNyak9aQXI2X08xdVRsR2R3IiwibmFtZSI6ImVuZCB1c2VyIiwiZ2l2ZW5fbmFtZSI6ImVuZCIsImZhbWlseV9uYW1lIjoidXNlciIsIm5pY2tuYW1lIjoiZW5kLXVzZXIiLCJnZW5kZXIiOiJmZW1hbGUiLCJsb2NhbGUiOiJlbiIsInVwZGF0ZWRfYXQiOjE3MTEwMTYyOTYsInByZWZlcnJlZF91c2VybmFtZSI6ImVuZC11c2VyIiwiZW1haWwiOiJ0aW0rZW5kLXVzZXJAeml0YWRlbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZX0.Dw8lfQwJTksCOr9dHLfWqpSf4gJwkcTdKMZGCkLueBMDdyqzL-qR_KcYCcp-NKDkY-o9e8SxJtIBkPlWzI2x0WutIg67SqzJbwS_Be88MkDKv-sRqKy_bVnyNTcYjuUReGzu4ycufjMu6aKtqYFEivdZsB2-2Pxnj5WSs_CY7jvBe_YQtfThSU88i1LPQDucQdSZZpOpOhEV4AI5C3XXbnv2nw0PMZ-Beq6svpCYqs_3Azeg0-UgxipuRgJfnqnqEqH0zlFNCndnkRuknUoda6-peuEI2KnRg9WkX7DoYrTToPde8Ay8NI48cWipm9dhxNxQbIr4ZDWQEazmsz9SpQ
```
The ID token is a JWT and contains the following claims:
```json
{
"iss": "http://localhost:9000",
"sub": "259242039378444290",
"aud": [
"259254409320529922@portal",
"259297773508165634@portal",
"259254317079330818",
"259254020357488642",
"259256588127174658",
"257786991247294468"
],
"exp": 1711141670,
"iat": 1711098470,
"auth_time": 1711098468,
"amr": ["password", "pwd"],
"azp": "259254409320529922@portal",
"client_id": "259254409320529922@portal",
"at_hash": "t1T78s8RTVku2sxBg03RCQ",
"c_hash": "PupC2crjOZAr6_O1uTlGdw",
"name": "end user",
"given_name": "end",
"family_name": "user",
"nickname": "end-user",
"gender": "female",
"locale": "en",
"updated_at": 1711016296,
"preferred_username": "end-user",
"email": "tim+end-user@zitadel.com",
"email_verified": true
}
```
The audience contains 2 client IDs from the current project (*portal*) and the IDs of the projects we described earlier, including the ZITADEL project ID.
Reduce audience and scope example [#reduce-audience-and-scope-example]
Now imagine that the portal web-app needs to call an aggregate API. The API is externally developed and configured to use ZITADEL's introspection endpoint to validate access tokens.
Besides that, we do not trust the API. If we were to forward the current access token in an `Authorization: Bearer` header, the untrusted API will get access to user information it might not need in order execute its business logic.
Another issue is that the API might start acting malicious and it would be able to call the **settings** and **ZITADEL** APIs with the same privilege as the passed token.
In this token exchange call we will reduce the scope and audience of the access token, so that we can forward the new token instead:
```bash
curl -L -X POST 'http://localhost:9000/oauth/v2/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: application/json' \
-u '259254409320529922@portal:eNdXJzB5RK5CXSpa4HqEfbdDqlM7drpskEHq1RBYMby0tM1MaCidyWsWlp5mglbN' \
-d 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
-d 'subject_token=NaUAPHy5mLFQlwUCeUGYeDyhcQYuNhzTiYgwMor9BxP_bfMy2iDdLxJ87nntUc85vNyeHOY' \
-d 'subject_token_type=urn:ietf:params:oauth:token-type:access_token' \
-d 'scope=openid' \
-d 'audience=259254020357488642' | jq
```
This gives the following response:
```json
{
"access_token": "CV3iikwgHfBqeGmzFebMIlbdoo3EHEz30LbOKWa-19FL0irJxcbITiLtOvUxouG0xuqECd0",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 43199,
"scope": "openid",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjI1OTM3OTQwMTIwNzA1NDMzOCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJzdWIiOiIyNTkyNDIwMzkzNzg0NDQyOTAiLCJhdWQiOlsiMjU5MjU0MDIwMzU3NDg4NjQyIiwiMjU5MjU0NDA5MzIwNTI5OTIyQHBvcnRhbCJdLCJleHAiOjE3MTExNDE4NDksImlhdCI6MTcxMTA5ODY0OSwiYXpwIjoiMjU5MjU0NDA5MzIwNTI5OTIyQHBvcnRhbCIsImNsaWVudF9pZCI6IjI1OTI1NDQwOTMyMDUyOTkyMkBwb3J0YWwiLCJhdF9oYXNoIjoiMGhqckJDcEhyLS1iYjg2ZlZtQmFjdyJ9.D7_upLZ3fEXRvdlX-EfK2x9FLgppDJZZ3QPvFHgw11rfRFgmMoZAgGmh3rNBbvBuDM8UYPw5FEcIlaEMMVaorKhTFbKQB-t0M0krZ81_uIrDa8J7svW5iPACg36Ge77PQz_aGUfbwoRcqSm26OG1Bw0Grmu3mxm7blnhqUHBFtZi5DLWmdK-EfKID6D4s7JR1JEH11nZyFT3LUY87wQ_9FQFWVcqtmvELmseVQsvENJkwifPRkzphgyABpiixMWZEh0HcoMVw7uYQBQS9-6yVyf0I4ScnTR7GtUUL650xw3yerxMTJVo3TfwDchVy7BzSXyWF9RSr46xgHY-48b1Tw"
}
```
As indicated by the `token_type` the new access token can be used as Bearer. Once the web-app will make a call to one of the aggregate APIs, that API can make an [introspection](/apis/openidoauth/endpoints#introspection-endpoint) call with the access token. Note we use the credentials of the API here:
```bash
curl -L -X POST 'http://localhost:9000/oauth/v2/introspect' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-u '259284000017809410@aggregates:ES1i1JWgGiHNW6bBljyynZyvQIlotEpVwzbgrTIYZzndOo2KxDkwap1WvdSdBjtk' \
-d token=CV3iikwgHfBqeGmzFebMIlbdoo3EHEz30LbOKWa-19FL0irJxcbITiLtOvUxouG0xuqECd0 | jq
```
The introspection response would look like:
```json
{
"active": true,
"scope": "openid",
"client_id": "259254409320529922@portal",
"token_type": "Bearer",
"exp": 1711141849,
"iat": 1711098649,
"nbf": 1711098649,
"sub": "259242039378444290",
"aud": ["259254020357488642"],
"iss": "http://localhost:9000",
"jti": "259380204902809602"
}
```
We can see that the audience and scope are reduced and we are not sharing any sensitive user information with the API. If the API tries to use the token on any API outside the aggregate project, it would be useless:
```bash
curl -L -X GET 'http://localhost:9000/auth/v1/users/me' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer CV3iikwgHfBqeGmzFebMIlbdoo3EHEz30LbOKWa-19FL0irJxcbITiLtOvUxouG0xuqECd0' | jq
```
```json
{
"code": 16,
"message": "Errors.Token.Invalid (AUTH-7fs1e)",
"details": [
{
"@type": "type.googleapis.com/zitadel.v1.ErrorDetail",
"id": "AUTH-7fs1e",
"message": "Errors.Token.Invalid"
}
]
}
```
Change token-type example [#change-token-type-example]
We can also use Token Exchange to change the type of token we are dealing with. For example, the first opaque token after user login can be exchanged for a JWT access token, while maintaining the same scope and audience:
```bash
curl -L -X POST 'http://localhost:9000/oauth/v2/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: application/json' \
-u '259254409320529922@portal:eNdXJzB5RK5CXSpa4HqEfbdDqlM7drpskEHq1RBYMby0tM1MaCidyWsWlp5mglbN' \
-d 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
-d 'subject_token=NaUAPHy5mLFQlwUCeUGYeDyhcQYuNhzTiYgwMor9BxP_bfMy2iDdLxJ87nntUc85vNyeHOY' \
-d 'subject_token_type=urn:ietf:params:oauth:token-type:access_token' \
-d 'requested_token_type=urn:ietf:params:oauth:token-type:jwt' | jq
```
Will give the following response:
```json
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjI1OTM3OTQwMTIwNzA1NDMzOCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJzdWIiOiIyNTkyNDIwMzkzNzg0NDQyOTAiLCJhdWQiOlsiMjU5MjU0NDA5MzIwNTI5OTIyQHBvcnRhbCIsIjI1OTI5Nzc3MzUwODE2NTYzNEBwb3J0YWwiLCIyNTkyNTQzMTcwNzkzMzA4MTgiLCIyNTkyNTQwMjAzNTc0ODg2NDIiLCIyNTkyNTY1ODgxMjcxNzQ2NTgiLCIyNTc3ODY5OTEyNDcyOTQ0NjgiXSwiZXhwIjoxNzExMTQyMjc0LCJpYXQiOjE3MTEwOTkwNzQsIm5iZiI6MTcxMTA5OTA3NCwianRpIjoiMjU5MzgwOTE2ODQzOTcwNTYyIn0.dsX-8bXTGaZL4d3FJ7Fmrhty4oIvSIOg5suZ16MIVXdogOZHWNpTvP3bXeyHL7zHX2prUjSxTg9EX_U9XcSnX4VeAzt4sG6_vH20pJLeXMivVbCDJBp9rv8rG2gVdEwVkfxhpK_2KHhtRzCpMj_xyjlM1eh7VbRBvEuH0m1Kqv96Gspc4w0jahl8hkDuV3v0PjTo7lB72emghVEwHyXhj6a53AKzPWzrZYOJnVSEKz0MgZeHcjT93D-nN3fYWulDw9VvTs6L65G3KnoRbB29plZtLrO5F-c0AJkVKi1W9dhd-_Yj-f8o5benxymAUxUAhWsROO2syWu89M9cdnjh9A",
"issued_token_type": "urn:ietf:params:oauth:token-type:jwt",
"token_type": "Bearer",
"expires_in": 43199,
"scope": "openid email profile urn:zitadel:iam:org:project:id:259254020357488642:aud urn:zitadel:iam:org:project:id:259256588127174658:aud urn:zitadel:iam:org:project:id:zitadel:aud",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjI1OTM3OTQwMTIwNzA1NDMzOCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJzdWIiOiIyNTkyNDIwMzkzNzg0NDQyOTAiLCJhdWQiOlsiMjU5MjU0NDA5MzIwNTI5OTIyQHBvcnRhbCIsIjI1OTI5Nzc3MzUwODE2NTYzNEBwb3J0YWwiLCIyNTkyNTQzMTcwNzkzMzA4MTgiLCIyNTkyNTQwMjAzNTc0ODg2NDIiLCIyNTkyNTY1ODgxMjcxNzQ2NTgiLCIyNTc3ODY5OTEyNDcyOTQ0NjgiXSwiZXhwIjoxNzExMTQyMjc0LCJpYXQiOjE3MTEwOTkwNzQsImF6cCI6IjI1OTI1NDQwOTMyMDUyOTkyMkBwb3J0YWwiLCJjbGllbnRfaWQiOiIyNTkyNTQ0MDkzMjA1Mjk5MjJAcG9ydGFsIiwiYXRfaGFzaCI6IjVVeUJ1el9rMVd3VTVPbUVNa21zSFEiLCJuYW1lIjoiZW5kIHVzZXIiLCJnaXZlbl9uYW1lIjoiZW5kIiwiZmFtaWx5X25hbWUiOiJ1c2VyIiwibmlja25hbWUiOiJlbmQtdXNlciIsImdlbmRlciI6ImZlbWFsZSIsImxvY2FsZSI6ImVuIiwidXBkYXRlZF9hdCI6MTcxMTAxNjI5NiwicHJlZmVycmVkX3VzZXJuYW1lIjoiZW5kLXVzZXIiLCJlbWFpbCI6InRpbStlbmQtdXNlckB6aXRhZGVsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlfQ.eXxM3hGM5_hn9Vieg-BGlt67KWNfeL3NjKkOiHyZKJNkWMYUmIO2bdk6eZC4_eEWgIMUv093UvTZ1t-xF01evrNaCQ68KROUCWVe6SW85XAaLFb2wtKCJwNAQYWYHl8IzCJdEs5JLlZ7BlU6qgTxdw5MN0npLJbjM4osI_R-9152QfDLjivJlM7F9DWOnA5DdnwBzrHHtOUU-JWvsR6BBXY9eaCZmTjNt2v9yNh6rR4FazlBOYQN-EcYc90Ybckm2Vyow0vRsAnj7moKDQlUdOSyBSwxnSs9sSMr_Nm7uPxcolJ5raIRonGD5FndYYaSc8vuKkkDzQ8yr1v2GVJMyQ"
}
```
You can now inspect the access token JWT and see the following claims:
```json
{
"iss": "http://localhost:9000",
"sub": "259242039378444290",
"aud": [
"259254409320529922@portal",
"259297773508165634@portal",
"259254317079330818",
"259254020357488642",
"259256588127174658",
"257786991247294468"
],
"exp": 1711142274,
"iat": 1711099074,
"nbf": 1711099074,
"jti": "259380916843970562"
}
```
Doing similar request you can:
* Exchange an ID token to opaque or JWT access token
* Exchange an opaque access token to a JWT access token
* Exchange a JWT access token to an opaque access token
* Exchange any access token to an ID token
Request an ID token example [#request-an-id-token-example]
In the following example we exchange the initial opaque access token to a new ID token. The usefulness of this is up to the imagination of the reader, but it demonstrates the weird behavior of requesting an ID token, as defined by the RFC.
You can also obtain an ID token in the `id_token` response field by requesting an access token and the `openid` scope.
```bash
curl -L -X POST 'http://localhost:9000/oauth/v2/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: application/json' \
-d 'client_id=259254409320529922@portal' \
-d 'client_secret=eNdXJzB5RK5CXSpa4HqEfbdDqlM7drpskEHq1RBYMby0tM1MaCidyWsWlp5mglbN' \
-d 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
-d 'subject_token=eZCZcbA-lpS1UnbyLvG2Mw2p6ix7CiES3HCDKBn6KMebhMu34hwu9p86N6EgOmkN6estous' \
-d 'subject_token_type=urn:ietf:params:oauth:token-type:access_token' \
-d 'requested_token_type=urn:ietf:params:oauth:token-type:id_token' | jq
```
This gives us a response with the ID token in the `access_token` field and the `token_type` set to `N_A`:
```json
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjI1OTIzOTc0MDQxMzMxMzAyNiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJzdWIiOiIyNTkyNDIwMzkzNzg0NDQyOTAiLCJhdWQiOlsiMjU5MjU0NDA5MzIwNTI5OTIyQHBvcnRhbCIsIjI1OTI1NDMxNzA3OTMzMDgxOCIsIjI1OTI1NDAyMDM1NzQ4ODY0MiIsIjI1OTI1NjU4ODEyNzE3NDY1OCIsIjI1Nzc4Njk5MTI0NzI5NDQ2OCJdLCJleHAiOjE3MTEwODk2MzcsImlhdCI6MTcxMTA0NjQzNywiYXpwIjoiMjU5MjU0NDA5MzIwNTI5OTIyQHBvcnRhbCIsImNsaWVudF9pZCI6IjI1OTI1NDQwOTMyMDUyOTkyMkBwb3J0YWwiLCJuYW1lIjoiZW5kIHVzZXIiLCJnaXZlbl9uYW1lIjoiZW5kIiwiZmFtaWx5X25hbWUiOiJ1c2VyIiwibmlja25hbWUiOiJlbmQtdXNlciIsImdlbmRlciI6ImZlbWFsZSIsImxvY2FsZSI6ImVuIiwidXBkYXRlZF9hdCI6MTcxMTAxNjI5NiwicHJlZmVycmVkX3VzZXJuYW1lIjoiZW5kLXVzZXIiLCJlbWFpbCI6InRpbStlbmQtdXNlckB6aXRhZGVsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlfQ.N2MfKznzdH-LaEV3qWPeqHW9dxlsgEoEm-ivU3uakVbtOe7AnpNTF56aPMlt3macNizixusm1vZWFHhHc-kBczMDqlzgFvEbwzSBi1ETmF0OIfazlbzGIJL0G1PCzD3883vR1oh80mwPUvoPqLkjHvQa3UaYIZ-Z08i8Oq-Cut8D3e2PhIfn9YCK9htq65GOJCHaWfWMPJrb65M5nTm6TyM4VfYe4iQgJ1D8Kuol_UQEpIeVnb7agu6mk9h1BdjhMGwBFPJjRbxSh9Mb7glFuRvgI1LWcbmr70HMMh0n0UVxPlIQUGJbrT0Wu97aJjFBdzEq5Rof4oJ2COAmvKvwVw",
"issued_token_type": "urn:ietf:params:oauth:token-type:id_token",
"token_type": "N_A",
"expires_in": 43199,
"scope": "openid profile email urn:zitadel:iam:org:project:id:259254020357488642:aud urn:zitadel:iam:org:project:id:259256588127174658:aud urn:zitadel:iam:org:project:id:zitadel:aud"
}
```
Impersonation examples [#impersonation-examples]
With impersonation we can let one user assume the role of another user.
Currently impersonated tokens cannot be used for the ZITADEL API. This is to prevent privilege escalation where a impersonator could become an instance owner, for example. We might enable the use of impersonated tokens in the future.
Preparation [#preparation]
We continue with the same application and project layout from the above examples. We will introduce a new user, the impersonator, which will assume the identity of the end user from the previous example.
Impersonation security settings [#impersonation-security-settings]
If you want to impersonate users by Token Exchange, the security settings of the instance must be configured to allow this. Go to "Default settings" and in the sidebar select "Security Settings". Enable the "Allow Impersonation" setting.
Impersonation permissions [#impersonation-permissions]
Next, we need to configure which users are allowed to impersonate other users. ZITADEL provides 4 [management roles](/guides/manage/console/administrators):
| Name | Role | Description |
| --------------------------- | ---------------------------- | ----------------------------------------------------------------- |
| Instance Admin Impersonator | IAM\_ADMIN\_IMPERSONATOR | Allow impersonation of admin and end users from all organizations |
| Instance Impersonator | IAM\_END\_USER\_IMPERSONATOR | Allow impersonation of end users from all organizations |
| Org Admin Impersonator | ORG\_ADMIN\_IMPERSONATOR | Allow impersonation of admin and end users from the organization |
| Org Impersonator | ORG\_END\_USER\_IMPERSONATOR | Allow impersonation of end users from the organization |
In this example we will assign the `ORG_END_USER_IMPERSONATOR` role to a user:
Authenticated impersonator tokens [#authenticated-impersonator-tokens]
At this point the *portal* web-app must have completed a code-flow login for a user with the `ORG_END_USER_IMPERSONATOR` ZITADEL role. The impersonator does not have a profile. In this case we only need the `openid` scope.
However, as we cannot extend the audience during token exchange, it is important that the project scopes are requested for the impersonator during login.
```
openid urn:zitadel:iam:org:project:id:259254020357488642:aud urn:zitadel:iam:org:project:id:259256588127174658:aud urn:zitadel:iam:org:project:id:zitadel:aud
```
At the end of the code flow we have the following tokens:
Opaque access token:
```
_oFT8JOKtqpS_5M5ml03P4TEQpCj8AT1XFq2jT_iKvgIB9lzjbrOl4MHJ3o3G-RSO_y0FR4
```
ID token:
```
eyJhbGciOiJSUzI1NiIsImtpZCI6IjI1OTM3OTQwMTIwNzA1NDMzOCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJzdWIiOiIyNTkyNDE5NDQ2NTQyODI3NTQiLCJhdWQiOlsiMjU5MjU0NDA5MzIwNTI5OTIyQHBvcnRhbCIsIjI1OTI5Nzc3MzUwODE2NTYzNEBwb3J0YWwiLCIyNTkyNTQzMTcwNzkzMzA4MTgiLCIyNTkyNTQwMjAzNTc0ODg2NDIiLCIyNTkyNTY1ODgxMjcxNzQ2NTgiLCIyNTc3ODY5OTEyNDcyOTQ0NjgiXSwiZXhwIjoxNzExMTQxMzcwLCJpYXQiOjE3MTEwOTgxNzAsImF1dGhfdGltZSI6MTcxMTA5ODE2OSwiYW1yIjpbInBhc3N3b3JkIiwicHdkIl0sImF6cCI6IjI1OTI1NDQwOTMyMDUyOTkyMkBwb3J0YWwiLCJjbGllbnRfaWQiOiIyNTkyNTQ0MDkzMjA1Mjk5MjJAcG9ydGFsIiwiYXRfaGFzaCI6InQ1X2dqR2k5TVNPYTNlNTBkUEdDVEEiLCJjX2hhc2giOiJnb3IzQ0tWN0ljVW8wNUpxTnd6aFp3In0.iN8LNj9VV-Kmb68frPesMM8L7PYWvwqcqlvvU4EsfNM_Q8_Upec8_8bXFk1EG7Ecg65JfrGdceQjYamldaMJyV2X9n-aZ9Db4CpyHUduJOIvWkeBQBxWDytiTFBiAaS-YhQ9L5UmDoz6b2HNrHGNlqGd_F0_rMdMZ0P4A8RQck-akNz8IntTpvQlbN6vWPC7_4Cy0xYqgWlqsCVWJkJ8v97XYLJlKPnu-tvoHQ48eZRXBgqUdrQAV8nAyp-1oglGQwJFGNzWBE-cRIkFJ5uMum7jRfuFPQGTSL8XNMQfAzRHCLOMLyFxttsL5ynMpcp2_w35DssmSY9r1J91tGdydg
```
The ID token has the following claims:
```json
{
"iss": "http://localhost:9000",
"sub": "259241944654282754",
"aud": [
"259254409320529922@portal",
"259297773508165634@portal",
"259254317079330818",
"259254020357488642",
"259256588127174658",
"257786991247294468"
],
"exp": 1711141370,
"iat": 1711098170,
"auth_time": 1711098169,
"amr": [
"password",
"pwd"
],
"azp": "259254409320529922@portal",
"client_id": "259254409320529922@portal",
"at_hash": "t5_gjGi9MSOa3e50dPGCTA",
"c_hash": "gor3CKV7IcUo05JqNwzhZw"
}
```
Delegation by token example [#delegation-by-token-example]
Let's assume that the web-app has the ability for an end-user to enable delegation. That option would make the end-user's token available to a user with impersonation permissions. The web-app will send a token exchange request with the `subject_token` of the end-user and the `actor_token` of the impersonator.
In this example we will also request a JWT access token, so we can inspect it later. Any other allowed type could be used.
```bash
curl -L -X POST 'http://localhost:9000/oauth/v2/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: application/json' \
-u '259254409320529922@portal:eNdXJzB5RK5CXSpa4HqEfbdDqlM7drpskEHq1RBYMby0tM1MaCidyWsWlp5mglbN' \
-d 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
-d 'subject_token=NaUAPHy5mLFQlwUCeUGYeDyhcQYuNhzTiYgwMor9BxP_bfMy2iDdLxJ87nntUc85vNyeHOY' \
-d 'subject_token_type=urn:ietf:params:oauth:token-type:access_token' \
-d 'actor_token=_oFT8JOKtqpS_5M5ml03P4TEQpCj8AT1XFq2jT_iKvgIB9lzjbrOl4MHJ3o3G-RSO_y0FR4' \
-d 'actor_token_type=urn:ietf:params:oauth:token-type:access_token' \
-d 'requested_token_type=urn:ietf:params:oauth:token-type:jwt' | jq
```
Will give the following response:
```json
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjI1OTM3OTQwMTIwNzA1NDMzOCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJzdWIiOiIyNTkyNDIwMzkzNzg0NDQyOTAiLCJhdWQiOlsiMjU5MjU0NDA5MzIwNTI5OTIyQHBvcnRhbCIsIjI1OTI5Nzc3MzUwODE2NTYzNEBwb3J0YWwiLCIyNTkyNTQzMTcwNzkzMzA4MTgiLCIyNTkyNTQwMjAzNTc0ODg2NDIiLCIyNTkyNTY1ODgxMjcxNzQ2NTgiLCIyNTc3ODY5OTEyNDcyOTQ0NjgiXSwiZXhwIjoxNzExMTQyODc1LCJpYXQiOjE3MTEwOTk2NzUsIm5iZiI6MTcxMTA5OTY3NSwianRpIjoiMjU5MzgxOTI2Mjk1NTAyODUwIiwiYWN0Ijp7ImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6OTAwMCIsInN1YiI6IjI1OTI0MTk0NDY1NDI4Mjc1NCJ9fQ.rz0M_r_rLN0OIf5UKOTi9Fz5-X3CFLMA4jBaZHDy1pdbBwfbnByL3LeB9UYtSjzMwaYmXJJJRlxAvO9I2bu2ReHYi97DzFo2gKX9p-rLoaEUYcAjg3HmJ0c9J1Ucvc05yXu2OXhNKDb7_qcX4IfaddpazPRvjNnpRk4NWFxKbTBLG4mpqxv5brM4iDPmzejUdoYKxSzlCH-ChZIf28vbE_ORf0HfxkptXAsZ3P9I9Fr-d_fenCmBFHAMP0u_tQ7z-IzgxDg9H54fWEm_LNrkFJf6PEPWLc1TFFOKMgU5nnGorSe0dLZGXOB_GJz6wTw6-ts8QKxJ_zajd4r3K4kKSg",
"issued_token_type": "urn:ietf:params:oauth:token-type:jwt",
"token_type": "Bearer",
"expires_in": 43199,
"scope": "openid email profile urn:zitadel:iam:org:project:id:259254020357488642:aud urn:zitadel:iam:org:project:id:259256588127174658:aud urn:zitadel:iam:org:project:id:zitadel:aud",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjI1OTM3OTQwMTIwNzA1NDMzOCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJzdWIiOiIyNTkyNDIwMzkzNzg0NDQyOTAiLCJhdWQiOlsiMjU5MjU0NDA5MzIwNTI5OTIyQHBvcnRhbCIsIjI1OTI5Nzc3MzUwODE2NTYzNEBwb3J0YWwiLCIyNTkyNTQzMTcwNzkzMzA4MTgiLCIyNTkyNTQwMjAzNTc0ODg2NDIiLCIyNTkyNTY1ODgxMjcxNzQ2NTgiLCIyNTc3ODY5OTEyNDcyOTQ0NjgiXSwiZXhwIjoxNzExMTQyODc1LCJpYXQiOjE3MTEwOTk2NzUsImF6cCI6IjI1OTI1NDQwOTMyMDUyOTkyMkBwb3J0YWwiLCJjbGllbnRfaWQiOiIyNTkyNTQ0MDkzMjA1Mjk5MjJAcG9ydGFsIiwiYWN0Ijp7ImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6OTAwMCIsInN1YiI6IjI1OTI0MTk0NDY1NDI4Mjc1NCJ9LCJhdF9oYXNoIjoiYnZPQVhzMUhkQmFZWTZqN1B6T3RqZyIsIm5hbWUiOiJlbmQgdXNlciIsImdpdmVuX25hbWUiOiJlbmQiLCJmYW1pbHlfbmFtZSI6InVzZXIiLCJuaWNrbmFtZSI6ImVuZC11c2VyIiwiZ2VuZGVyIjoiZmVtYWxlIiwibG9jYWxlIjoiZW4iLCJ1cGRhdGVkX2F0IjoxNzExMDE2Mjk2LCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJlbmQtdXNlciIsImVtYWlsIjoidGltK2VuZC11c2VyQHppdGFkZWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWV9.cza4Fgn73Jez29l9uzcCcG-QYGvsqjReAICGajWjFFIij7PohhSWYkJNpQuixXeyp_JD7qxLuG1yFUGcXS-IS8ui_yHpiWuXr7ik81OX00_iCwBr6Qn6Ae6Qc3LOLNieSo1jRY2vx6pTXn0ZPnXpL_AbtVU3bruyaxbBeQhhyVDZ0NOLOgB3r-0Vc43VDnziI4-7Ngl1lQpU6Jp-kRNmqar36S59Aj3upcUus77I8tCfS633T4E8PcIAlqPla8RYcpAan6Qpc3ge7ybqjdfmh_qLv672rY_rQvh3rbe3sHup0nK1XzZNr9Fl1_LeZtUiv5or7WB4c4cGpqc3SAuxow"
}
```
In the access token [claims](/apis/openidoauth/claims) we can see that the subject and audience are taken from the `subject_token`. The `act` claim contains the subject and issuer of the `actor_token`, so we can always determine the impersonator that obtained the token.
```json
{
"iss": "http://localhost:9000",
"sub": "259242039378444290",
"aud": [
"259254409320529922@portal",
"259297773508165634@portal",
"259254317079330818",
"259254020357488642",
"259256588127174658",
"257786991247294468"
],
"exp": 1711142875,
"iat": 1711099675,
"nbf": 1711099675,
"jti": "259381926295502850",
"act": {
"iss": "http://localhost:9000",
"sub": "259241944654282754"
}
}
```
Impersonation by user ID example [#impersonation-by-user-id-example]
The previous example required us to have an active token of the user we want to impersonate. There are situations where this requirement cannot be met. For example, the user does not have an active session and we still need to impersonate them.
ZITADEL allows passing a user ID as the `subject_token`, along with a valid `actor_token`. This is an addition to enable this specific use-case.
User ID as subject token is an experimental addition and is provided pending evaluation of our community. This method might be considered insecure and trust is fully placed into the app making the request. This might be removed in the future.
In the following example we are again requesting a JWT access token. Instead of a token, we use the user ID of the end-user in the `subject_token` field and adjust the `subject_token_type` accordingly. As the user ID does not carry any scope and the impersonator / actor does not have a profile, we need to add some scopes to the request in order to receive an ID token with profile and email information.
```bash
curl -L -X POST 'http://localhost:9000/oauth/v2/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: application/json' \
-u '259254409320529922@portal:eNdXJzB5RK5CXSpa4HqEfbdDqlM7drpskEHq1RBYMby0tM1MaCidyWsWlp5mglbN' \
-d 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
-d 'subject_token=259242039378444290' \
-d 'subject_token_type=urn:zitadel:params:oauth:token-type:user_id' \
-d 'actor_token=_oFT8JOKtqpS_5M5ml03P4TEQpCj8AT1XFq2jT_iKvgIB9lzjbrOl4MHJ3o3G-RSO_y0FR4' \
-d 'actor_token_type=urn:ietf:params:oauth:token-type:access_token' \
-d 'requested_token_type=urn:ietf:params:oauth:token-type:jwt' \
-d 'scope=openid profile email' | jq
```
This gives us the following response:
```json
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjI1OTM3OTQwMTIwNzA1NDMzOCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJzdWIiOiIyNTkyNDIwMzkzNzg0NDQyOTAiLCJhdWQiOlsiMjU5MjU0NDA5MzIwNTI5OTIyQHBvcnRhbCIsIjI1OTI5Nzc3MzUwODE2NTYzNEBwb3J0YWwiLCIyNTkyNTQzMTcwNzkzMzA4MTgiLCIyNTkyNTQwMjAzNTc0ODg2NDIiLCIyNTkyNTY1ODgxMjcxNzQ2NTgiLCIyNTc3ODY5OTEyNDcyOTQ0NjgiXSwiZXhwIjoxNzExMTQzNTEyLCJpYXQiOjE3MTExMDAzMTIsIm5iZiI6MTcxMTEwMDMxMiwianRpIjoiMjU5MzgyOTk0NDMzNzM2NzA2IiwiYWN0Ijp7ImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6OTAwMCIsInN1YiI6IjI1OTI0MTk0NDY1NDI4Mjc1NCJ9fQ.amF1wF090KItNNErpv_PaEw1t-zIQNh54IWPo_ECk7neNaWoTQjiUDQwuOBDpe8rqukP7gUnKlq9s3GOB0C5dGWyETMrezVeTQGkGEtGOhyvP21KWG8mAJ9MWP4VZ0XNXyzscioHdDC1ICPeRZPenfsGltcVKk0jzISW_wCprnJWXbVECBY_oEzZaVdopqv8kYYM2oXC-5Yi8tMBcm_R-9demCPoUUpKPHXRp524bv1jDfEti5WSziM-VbkFVWOB5VjSR1vFu7mXWmP9foRr11206EUkOrRUMewluRLUNm_aprhKADEo1nZ8WY76V3LLDH7wQ7L8v0UxqUtdw9v_kw",
"issued_token_type": "urn:ietf:params:oauth:token-type:jwt",
"token_type": "Bearer",
"expires_in": 43199,
"scope": "openid profile email",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjI1OTM3OTQwMTIwNzA1NDMzOCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJzdWIiOiIyNTkyNDIwMzkzNzg0NDQyOTAiLCJhdWQiOlsiMjU5MjU0NDA5MzIwNTI5OTIyQHBvcnRhbCIsIjI1OTI5Nzc3MzUwODE2NTYzNEBwb3J0YWwiLCIyNTkyNTQzMTcwNzkzMzA4MTgiLCIyNTkyNTQwMjAzNTc0ODg2NDIiLCIyNTkyNTY1ODgxMjcxNzQ2NTgiLCIyNTc3ODY5OTEyNDcyOTQ0NjgiXSwiZXhwIjoxNzExMTQzNTEyLCJpYXQiOjE3MTExMDAzMTIsImF6cCI6IjI1OTI1NDQwOTMyMDUyOTkyMkBwb3J0YWwiLCJjbGllbnRfaWQiOiIyNTkyNTQ0MDkzMjA1Mjk5MjJAcG9ydGFsIiwiYWN0Ijp7ImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6OTAwMCIsInN1YiI6IjI1OTI0MTk0NDY1NDI4Mjc1NCJ9LCJhdF9oYXNoIjoicXVYS1JENWY0YmxOb3YxS3Y2bnB5ZyIsIm5hbWUiOiJlbmQgdXNlciIsImdpdmVuX25hbWUiOiJlbmQiLCJmYW1pbHlfbmFtZSI6InVzZXIiLCJuaWNrbmFtZSI6ImVuZC11c2VyIiwiZ2VuZGVyIjoiZmVtYWxlIiwibG9jYWxlIjoiZW4iLCJ1cGRhdGVkX2F0IjoxNzExMDE2Mjk2LCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJlbmQtdXNlciIsImVtYWlsIjoidGltK2VuZC11c2VyQHppdGFkZWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWV9.kMRBX6te4bPh9PWQrKeQu7hWr13p_ehvIbOigrTs5ods3klM6PpCPTmDLuj65Ssd8SA5i_YTuNHDuoDzRlZAdvHx4X06eytF1yQQd0eME187cOaf3ffzK90ZWvuFk34N--teW41LjM0nq15wbUXMO8UWk4AStkl901nWBxAWhRLmR356ksQWNs8TAGLsSLCaG4py0pw807yUXCFy1EGwG7z-eAeA58mRmIYSxFmycU-uRqsCPzDuDSu4JD1G3sh1G3GKRF_DqwmEm4ClBx-_gNUJnH52o-xvTOX57QM40Ai6vub_Ncy5nxVFETU-PnpAXpslvNIsOz4CHwz7yDVPYg"
}
```
The new access token looks similar to the last example. However, the audience is now taken from the `actor_token`. As both audiences were the same you will not see the difference here.
```json
{
"iss": "http://localhost:9000",
"sub": "259242039378444290",
"aud": [
"259254409320529922@portal",
"259297773508165634@portal",
"259254317079330818",
"259254020357488642",
"259256588127174658",
"257786991247294468"
],
"exp": 1711143512,
"iat": 1711100312,
"nbf": 1711100312,
"jti": "259382994433736706",
"act": {
"iss": "http://localhost:9000",
"sub": "259241944654282754"
}
}
```
In the ID Token we see the profile and email information of the end-user:
```json
{
"iss": "http://localhost:9000",
"sub": "259242039378444290",
"aud": [
"259254409320529922@portal",
"259297773508165634@portal",
"259254317079330818",
"259254020357488642",
"259256588127174658",
"257786991247294468"
],
"exp": 1711143512,
"iat": 1711100312,
"azp": "259254409320529922@portal",
"client_id": "259254409320529922@portal",
"act": {
"iss": "http://localhost:9000",
"sub": "259241944654282754"
},
"at_hash": "quXKRD5f4blNov1Kv6npyg",
"name": "end user",
"given_name": "end",
"family_name": "user",
"nickname": "end-user",
"gender": "female",
"locale": "en",
"updated_at": 1711016296,
"preferred_username": "end-user",
"email": "tim+end-user@zitadel.com",
"email_verified": true
}
```
Refresh an impersonated token example [#refresh-an-impersonated-token-example]
If we use the previous example and append the `offline_access` scope, we will also receive a refresh token:
```bash
curl -L -X POST 'http://localhost:9000/oauth/v2/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: application/json' \
-u '259254409320529922@portal:eNdXJzB5RK5CXSpa4HqEfbdDqlM7drpskEHq1RBYMby0tM1MaCidyWsWlp5mglbN' \
-d 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
-d 'subject_token=259242039378444290' \
-d 'subject_token_type=urn:zitadel:params:oauth:token-type:user_id' \
-d 'actor_token=_oFT8JOKtqpS_5M5ml03P4TEQpCj8AT1XFq2jT_iKvgIB9lzjbrOl4MHJ3o3G-RSO_y0FR4' \
-d 'actor_token_type=urn:ietf:params:oauth:token-type:access_token' \
-d 'requested_token_type=urn:ietf:params:oauth:token-type:jwt' \
-d 'scope=openid profile email offline_access' | jq
```
Response with a refresh token:
```bash
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjI1OTM3OTQwMTIwNzA1NDMzOCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJzdWIiOiIyNTkyNDIwMzkzNzg0NDQyOTAiLCJhdWQiOlsiMjU5MjU0NDA5MzIwNTI5OTIyQHBvcnRhbCIsIjI1OTI5Nzc3MzUwODE2NTYzNEBwb3J0YWwiLCIyNTkyNTQzMTcwNzkzMzA4MTgiLCIyNTkyNTQwMjAzNTc0ODg2NDIiLCIyNTkyNTY1ODgxMjcxNzQ2NTgiLCIyNTc3ODY5OTEyNDcyOTQ0NjgiXSwiZXhwIjoxNzExMTU5Mzg1LCJpYXQiOjE3MTExMTYxODUsIm5iZiI6MTcxMTExNjE4NSwianRpIjoiMjU5NDA5NjI1NjYzNjY4MjI2IiwiYWN0Ijp7ImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6OTAwMCIsInN1YiI6IjI1OTI0MTk0NDY1NDI4Mjc1NCJ9fQ.QoPVZFOZUolPVOWwTYY1PZe7CKp2j8dqV8kt8a5Xz9ij1Y4TYZeKivDor68hfvlyulfT04gT8WNc3VLPtxJjNHQaydk9KrhzIN1liovh5Jy54KKvq4-jZpMPkBSy0Zkvv-lSuGEzM9wDurIOBUUy_JKmek3uySxH7bEQU4Jt6qQ_kQTT82rqFXAl3SWMQpaaVjvGMqEmzlmZacudSa1KETLyF2_UTCqoXXFWW-1mZtNGyy4EaMiU-k0h6MC1XBSyjr1aIVO2o4uWYmQYjIydmnKAoqJJEKkd-ZmSkCMEV9fFa8bKT816Agw1UNMDKMxF3tSW540oyAdGsLKSg39uIg",
"issued_token_type": "urn:ietf:params:oauth:token-type:jwt",
"token_type": "Bearer",
"expires_in": 43199,
"scope": "openid profile email offline_access",
"refresh_token": "Rh1SRrRBGkBAmyK7KxrMcHtZ0_ewzStK5-l6IDOQG5S6EmZ42gHkP9KdMP3u-cV2cgFzxcnaRHbae9ZjPq9tD0ZbPdvjgyER",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjI1OTM3OTQwMTIwNzA1NDMzOCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJzdWIiOiIyNTkyNDIwMzkzNzg0NDQyOTAiLCJhdWQiOlsiMjU5MjU0NDA5MzIwNTI5OTIyQHBvcnRhbCIsIjI1OTI5Nzc3MzUwODE2NTYzNEBwb3J0YWwiLCIyNTkyNTQzMTcwNzkzMzA4MTgiLCIyNTkyNTQwMjAzNTc0ODg2NDIiLCIyNTkyNTY1ODgxMjcxNzQ2NTgiLCIyNTc3ODY5OTEyNDcyOTQ0NjgiXSwiZXhwIjoxNzExMTU5Mzg1LCJpYXQiOjE3MTExMTYxODUsImF6cCI6IjI1OTI1NDQwOTMyMDUyOTkyMkBwb3J0YWwiLCJjbGllbnRfaWQiOiIyNTkyNTQ0MDkzMjA1Mjk5MjJAcG9ydGFsIiwiYWN0Ijp7ImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6OTAwMCIsInN1YiI6IjI1OTI0MTk0NDY1NDI4Mjc1NCJ9LCJhdF9oYXNoIjoiSmtRZ1JTZHlqVzJ5ZnZ5M3hUQUc4USIsIm5hbWUiOiJlbmQgdXNlciIsImdpdmVuX25hbWUiOiJlbmQiLCJmYW1pbHlfbmFtZSI6InVzZXIiLCJuaWNrbmFtZSI6ImVuZC11c2VyIiwiZ2VuZGVyIjoiZmVtYWxlIiwibG9jYWxlIjoiZW4iLCJ1cGRhdGVkX2F0IjoxNzExMDE2Mjk2LCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJlbmQtdXNlciIsImVtYWlsIjoidGltK2VuZC11c2VyQHppdGFkZWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWV9.SvSD5hgR-MkabVV41Zta0jgtHmhlhSvAbP1BQNbr7Pjzia-f-3zVRodKkPU6OkjVvI2D4Yqk2bBPO7ZUW9w76oDoScnlJoqJvZsBQDPxO8z7Gtgtj7rQAPQKC-JKU7Aeb-V072tZhOt0NG-S0yWeiObS4stMXHGrBYQbwyarboyqMO69qjYey2MkGVFmhEOVGZ9w7Np6HZPfBgs2qFUXoQ51FbBVVOxxuCF5KSUkD_QRgmjK03KFDlLI8adtvC3TUsWLJeTaiaYAmXU2VouGtEqDXfOmDzxeZI69gUxj4_io2v3tHLn3SuslMi1ulihplTircsDk3H4oAp2clqj4TA"
}
```
The refresh token can be used for the `refresh_token` grant:
```bash
curl -L -X POST 'http://localhost:9000/oauth/v2/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: application/json' \
-u '259254409320529922@portal:eNdXJzB5RK5CXSpa4HqEfbdDqlM7drpskEHq1RBYMby0tM1MaCidyWsWlp5mglbN' \
-d 'grant_type=refresh_token' \
-d 'refresh_token=Rh1SRrRBGkBAmyK7KxrMcHtZ0_ewzStK5-l6IDOQG5S6EmZ42gHkP9KdMP3u-cV2cgFzxcnaRHbae9ZjPq9tD0ZbPdvjgyER' | jq
```
The response now caries an opaque token again, because that is what is configured for the application:
```json
{
"access_token": "N4At8XdtlFySthaLzCSYX3GrEH_UmPgUzXjGF3WNLC_cl-Oy6s5G7ytZSV7zSClB3aSltYY",
"token_type": "Bearer",
"expires_in": 43199,
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjI1OTM3OTQwMTIwNzA1NDMzOCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJzdWIiOiIyNTkyNDIwMzkzNzg0NDQyOTAiLCJhdWQiOlsiMjU5MjU0NDA5MzIwNTI5OTIyQHBvcnRhbCIsIjI1OTI5Nzc3MzUwODE2NTYzNEBwb3J0YWwiLCIyNTkyNTQzMTcwNzkzMzA4MTgiLCIyNTkyNTQwMjAzNTc0ODg2NDIiLCIyNTkyNTY1ODgxMjcxNzQ2NTgiLCIyNTc3ODY5OTEyNDcyOTQ0NjgiXSwiZXhwIjoxNzExMTU5NDI3LCJpYXQiOjE3MTExMTYyMjcsImF6cCI6IjI1OTI1NDQwOTMyMDUyOTkyMkBwb3J0YWwiLCJjbGllbnRfaWQiOiIyNTkyNTQ0MDkzMjA1Mjk5MjJAcG9ydGFsIiwiYWN0Ijp7ImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6OTAwMCIsInN1YiI6IjI1OTI0MTk0NDY1NDI4Mjc1NCJ9LCJhdF9oYXNoIjoiUVZRRm1RejFUS3hiOTgxM3Y2RUlMQSIsIm5hbWUiOiJlbmQgdXNlciIsImdpdmVuX25hbWUiOiJlbmQiLCJmYW1pbHlfbmFtZSI6InVzZXIiLCJuaWNrbmFtZSI6ImVuZC11c2VyIiwiZ2VuZGVyIjoiZmVtYWxlIiwibG9jYWxlIjoiZW4iLCJ1cGRhdGVkX2F0IjoxNzExMDE2Mjk2LCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJlbmQtdXNlciIsImVtYWlsIjoidGltK2VuZC11c2VyQHppdGFkZWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWV9.M-lZwJ2UKpsGARLtGVV0IMQWWeHGw--Q75XcnSIOQat3FZRswUVPpo7Ir2xqvOoi4RCaPdq2Wy8Zl34-RnLOJ0ZtgPhdjx3qLFfJxfZtm_KTCfAaeTRprlwCEjLvZ2RdDsnSZasawRb1Bg_oajtckkEj4MfPyIEhq_RYgERbSZFMNFkQ99WIWnpP6bXVekkYCx2dGpJU3ZHQKUcjt0ejYteGo0-qVRrJCRR994fQddVkB7yYk8fDP7PwNcB6be9db1plpkWJGP3tiOSC6DvBoP8LhMeda4TFM7hgh9iiCqhB-FDbhXuhDFLcGhTrF0XYrowd8LNEtHdAS_T9RNN8xw"
}
```
If we inspect the ID token, we can see that the actor claim is preserved, even after token refresh:
```json
{
"iss": "http://localhost:9000",
"sub": "259242039378444290",
"aud": [
"259254409320529922@portal",
"259297773508165634@portal",
"259254317079330818",
"259254020357488642",
"259256588127174658",
"257786991247294468"
],
"exp": 1711159427,
"iat": 1711116227,
"azp": "259254409320529922@portal",
"client_id": "259254409320529922@portal",
"act": {
"iss": "http://localhost:9000",
"sub": "259241944654282754"
},
"at_hash": "QVQFmQz1TKxb9813v6EILA",
"name": "end user",
"given_name": "end",
"family_name": "user",
"nickname": "end-user",
"gender": "female",
"locale": "en",
"updated_at": 1711016296,
"preferred_username": "end-user",
"email": "tim+end-user@zitadel.com",
"email_verified": true
}
```
Impersonation by JWT profile example [#impersonation-by-jwt-profile-example]
If the web-app uses client assertion with JWT, it is also possible to create a self-singed JWT as subject token.
```bash
curl -L -X POST 'http://localhost:9000/oauth/v2/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: application/json' \
-d 'client_assertion=eyJhbGciOiJSUzI1NiIsImtpZCI6IjI1OTI5Nzc5ODk0MjM1OTU1NCJ9.eyJpc3MiOiIyNTkyOTc3NzM1MDgxNjU2MzRAcG9ydGFsIiwic3ViIjoiMjU5Mjk3NzczNTA4MTY1NjM0QHBvcnRhbCIsImF1ZCI6WyJodHRwOi8vbG9jYWxob3N0OjkwMDAiXSwiaWF0IjoxNzExMTAyNjU3LCJleHAiOjE3MTExMDYyNTd9.QVyS01stBxEeoMsA6FGXrEcbZebGMkj9PzuMO8-Gq-4dkk94O2SkD9LFGOU2QCgQgdUUxYyK363mfO9ihQs01CgYybwsqv8ijcpa_koAK5K2qx6Vrjtiipyr-GTB5egyoETMlxxc9JrvrI4xhtrczXUJNMJ3a4XwxNL7h8pwQCzoJmgAvZXX7JyuWzp8qToN5R9opv-mIpezziDZA4Cm9R8Uo1ASK-pdQ-Fx_DIQgvFXerEfPWAG0tRWV8Usq_bpMPedjWrFB--XeOu3aSFp7YYmo0WLJshIoWI9dJwWrfVI5oG3lHgvvuWpFmzFhi_zkOz4VXdqrPEjs9IUzGwcgQ' \
-d 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
-d 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
-d 'subject_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjI1OTI5Nzc5ODk0MjM1OTU1NCJ9.eyJpc3MiOiIyNTkyOTc3NzM1MDgxNjU2MzRAcG9ydGFsIiwic3ViIjoiMjU5MjQyMDM5Mzc4NDQ0MjkwIiwiYXVkIjpbImh0dHA6Ly9sb2NhbGhvc3Q6OTAwMCJdLCJpYXQiOjE3MTExMDI1NjAsImV4cCI6MTcxMTEwNjE2MH0.d5B-hXi36QfoiBlLxzmUev32RtbD_tSBymPiaph10a6bRvwcwp6mTP9SMFWtYt4wUiITOXRYTaFADqga8xIfa5ZmfR28kES8bqlOtXNlnfQFUH4_yYy8bw02d9v0jArVIkdYpQTVl_Zi9VyRKGcGXmkChNdQXKsF1FIigJeG78jpPTKs0sqRrTIbeDiwvAsWhiUSWPmZ1UsZThsNPrVynUgswLpMADz-f0mbNkc3MT9psDJbTF0tCI7yNTzbGPQymThd5CDVusEHkPA7abiQb4yvhbJvl4yFZxJyodkmNr0CotER-LgzcAYBeLFD07EWmf5Cwsbu3ZMIzcibJNtN5Q' \
-d 'subject_token_type=urn:ietf:params:oauth:token-type:jwt' \
-d 'actor_token=_oFT8JOKtqpS_5M5ml03P4TEQpCj8AT1XFq2jT_iKvgIB9lzjbrOl4MHJ3o3G-RSO_y0FR4' \
-d 'actor_token_type=urn:ietf:params:oauth:token-type:access_token' \
-d 'requested_token_type=urn:ietf:params:oauth:token-type:jwt' \
-d 'scope=openid profile email' | jq
```
The `client_assertion` has the following claims:
```json
{
"iss": "259297773508165634@portal",
"sub": "259297773508165634@portal",
"aud": [
"http://localhost:9000"
],
"iat": 1711102657,
"exp": 1711106257
}
```
And the `subject_token`:
```json
{
"iss": "259297773508165634@portal",
"sub": "259242039378444290",
"aud": [
"http://localhost:9000"
],
"iat": 1711102560,
"exp": 1711106160
}
```
In both cases the issuer is the web application, the audience must be the Custom Domain. For the assertion the subject is the application and for the subject token the subject is the impersonated user.
Response:
```json
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjI1OTM3OTQwMTIwNzA1NDMzOCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJzdWIiOiIyNTkyNDIwMzkzNzg0NDQyOTAiLCJhdWQiOlsiMjU5MjU0NDA5MzIwNTI5OTIyQHBvcnRhbCIsIjI1OTI5Nzc3MzUwODE2NTYzNEBwb3J0YWwiLCIyNTkyNTQzMTcwNzkzMzA4MTgiLCIyNTkyNTQwMjAzNTc0ODg2NDIiLCIyNTkyNTY1ODgxMjcxNzQ2NTgiLCIyNTc3ODY5OTEyNDcyOTQ0NjgiXSwiZXhwIjoxNzExMTQ1OTUxLCJpYXQiOjE3MTExMDI3NTEsIm5iZiI6MTcxMTEwMjc1MSwianRpIjoiMjU5Mzg3MDg2NDYzODI3OTcwIiwiYWN0Ijp7ImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6OTAwMCIsInN1YiI6IjI1OTI0MTk0NDY1NDI4Mjc1NCJ9fQ.sq5lGzxcQ0YePXcl-HjfqlQ8XaDcKhgVR2NJ-t5eMcfMasBKRhAzDhTPPojS32F7RClXgcRbiW-Jgemr4SsUAeZ3abmIGQnjzTu3alDFp9vtOcN1OvWttMl6tgvhW6JzsyRUnPRbC3n4_nRX9rXFi3eg5I3mNYo-a6yOw-pKdLxC2vNBYurFn_1uUbEGG0Z1UTzSHx8PVPpAeJ2nNWd8EN-HskpjSmSpklVazknu6NJHolNvmic0WmlZz_SAQ8M4uvea4aVOw3Uw4QRaPczsUuO0nB0g_bSi8lDH9GIP7CFNuD0BeDwJ-lKdH0QV-cPMuadAgG4G9W_t4IjvXcQYYQ",
"issued_token_type": "urn:ietf:params:oauth:token-type:jwt",
"token_type": "Bearer",
"expires_in": 43199,
"scope": "openid profile email",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjI1OTM3OTQwMTIwNzA1NDMzOCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJzdWIiOiIyNTkyNDIwMzkzNzg0NDQyOTAiLCJhdWQiOlsiMjU5MjU0NDA5MzIwNTI5OTIyQHBvcnRhbCIsIjI1OTI5Nzc3MzUwODE2NTYzNEBwb3J0YWwiLCIyNTkyNTQzMTcwNzkzMzA4MTgiLCIyNTkyNTQwMjAzNTc0ODg2NDIiLCIyNTkyNTY1ODgxMjcxNzQ2NTgiLCIyNTc3ODY5OTEyNDcyOTQ0NjgiXSwiZXhwIjoxNzExMTQ1OTUxLCJpYXQiOjE3MTExMDI3NTEsImF6cCI6IjI1OTI5Nzc3MzUwODE2NTYzNEBwb3J0YWwiLCJjbGllbnRfaWQiOiIyNTkyOTc3NzM1MDgxNjU2MzRAcG9ydGFsIiwiYWN0Ijp7ImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6OTAwMCIsInN1YiI6IjI1OTI0MTk0NDY1NDI4Mjc1NCJ9LCJhdF9oYXNoIjoiMENlN0pUMExHYUVJTmxwQVRIYzFRQSIsIm5hbWUiOiJlbmQgdXNlciIsImdpdmVuX25hbWUiOiJlbmQiLCJmYW1pbHlfbmFtZSI6InVzZXIiLCJuaWNrbmFtZSI6ImVuZC11c2VyIiwiZ2VuZGVyIjoiZmVtYWxlIiwibG9jYWxlIjoiZW4iLCJ1cGRhdGVkX2F0IjoxNzExMDE2Mjk2LCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJlbmQtdXNlciIsImVtYWlsIjoidGltK2VuZC11c2VyQHppdGFkZWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWV9.cMWoJBIPeakjtXWzsW3SGOAjMl27E0q8iePXtUHGueSUMhPibpOn7JiKd7VaZhgMDqN6c5TCU0EErdVm-6bc4SkxqrnYFjX4YIOygoTSbNzqkiOss6ZpcAGHt_RAd-i6NGcEm2_Fqp-EUO45V7jBEWgo3O4XLHsCVV1LQFpCHaSPK0ZtmjmNw-s-UKKF-kdSLLBpYKEUNmWGSMp3MqMgKLwl0SKFOiMY_HmBb-zSDRGN6s68b9Ays6Edxt-EnQ0pfR0TYFbnVSBQCqi5VXt3AcdnV1LRFQWi8ux6YTOiU10fZ3jbOiDjfS85bEKl9Nq5mhxVn9VsO4IiynjA9ZmlLQ"
}
```
And again the access token claims:
```json
{
"iss": "http://localhost:9000",
"sub": "259242039378444290",
"aud": [
"259254409320529922@portal",
"259297773508165634@portal",
"259254317079330818",
"259254020357488642",
"259256588127174658",
"257786991247294468"
],
"exp": 1711145951,
"iat": 1711102751,
"nbf": 1711102751,
"jti": "259387086463827970",
"act": {
"iss": "http://localhost:9000",
"sub": "259241944654282754"
}
}
```
Other usage examples [#other-usage-examples]
Above we gave some of the most straightforward use cases. Of course, you can combine these examples to:
* Impersonate and change the token type
* Impersonate and change scope
* Impersonate and reduce audience
* Impersonate, change the token type, scope and audience
Audit trail [#audit-trail]
In the user view of the management console, we can see whenever a new access token is created for a user.
The existing `Access Token created` event is also used in the case of a token exchange.
When there was an `actor_token` present during token exchange, we also log a `User impersonated` event.
In the [instance event list](/concepts/eventstore/overview) the `User impersonated` carries the actor in the payload:
```json
{
"actor": {
"issuer": "http://localhost:9000",
"user_id": "259241944654282754"
},
"applicationId": "259297773508165634@portal",
}
```
Finishing notes [#finishing-notes]
The current implementation of the Token Exchange grant was our first iteration on the subject.
We love to hear feedback from our users! This is a [GitHub discussion](https://github.com/zitadel/zitadel/discussions/7624) opened specifically for this purpose.
# ZITADEL Cloud Production Checklist
This checklist is specifically designed for teams deploying on **ZITADEL Cloud**.
Following these steps ensures your managed instance is resilient, branded and recovery-ready.
**Self-Hosting ZITADEL**
If you are managing your own infrastructure this list does not cover host-level concerns like database maintenance, high availability, or TLS termination.
Please refer to our [Self-Hosting Production Guide](/self-hosting/manage/productionchecklist) for infrastructure-specific requirements.
Infrastructure [#infrastructure]
Prioritize environment isolation to prevent development testing from impacting live users.
* [ ] **Multi-Instance Isolation**: Never test configurations in production. Use the [ZITADEL Customer Portal](https://zitadel.com/admin/dashboard) to spin up dedicated instances for dev, test, and prod.
* [ ] **Custom Domain Implementation**: Configure a custom domain (e.g., auth.yourdomain.com) to ensure brand consistency and avoid problems with domain bound authentication methods such as passkey.
* [ ] **Production SMTP Server**: Replace the default ZITADEL Email Provider with your own [SMTP Provider](/guides/manage/console/default-settings#smtp) (SendGrid, Postmark, Google, Microsoft, etc) for reliable delivery of MFA codes and welcome emails.
* [ ] **Verified Sender Identity**: Ensure the "From" address matches your custom domain and has valid SPF/DKIM/DMARC records to prevent emails from landing in spam.
Account Lockout & Access Lost Prevention [#account-lockout-access-lost-prevention]
Eliminate Single Points of Failure (SPOF) for administrative access.
* [ ] **Secondary Administrator**: Invite at least one other trusted team member as [Customer Portal Administrator](https://zitadel.com/admin/users) and as an IAM Owner into your specific ZITADEL instances
* **Why**: This prevents a single point of failure. If your primary account (e.g., via a lost Passkey or MFA device) is locked, the secondary admin can restore your access.
* [ ] **Diverse MFA Methods**: Ensure your administrators enroll multiple MFA methods (e.g., one on a YubiKey, one on an Authenticator app) to maximize recovery options.
* [ ] **Backup Service Account**: Create a dedicated Service Account with a PAT and with administrative roles (IAM\_OWNER).
* **Why**: If a misconfigured "Action" or a CSS error breaks the Management Console UI, you can still revert changes or manage the instance directly via the ZITADEL Management API using this PAT.
ZITADEL Setup and Configuration [#zitadel-setup-and-configuration]
Finalize the user experience and security policies before going live.
* [ ] **Branding**: Apply your logo, colors, fonts for your [Custom Branding](/guides/manage/customize/branding) if required
* [ ] **SMS Provider**: Set up a valid [SMS Service](/guides/manage/console/default-settings#sms) such as Twilio if needed
* [ ] **Terms of Service & Privacy Links**: Set your privacy policy, terms of service and a help Link if needed
* [ ] **Security Policy Audit**: Review your Login Policy and Password Complexity settings.
* [ ] **Token & Session Lifetimes**: Audit your [OIDC Token settings](/guides/manage/console/default-settings#oidc-token-lifetimes-and-expiration) to ensure they match your application's security needs.
# ZITADEL Terraform Provider
The [ZITADEL Terraform Provider](https://registry.terraform.io/providers/zitadel/zitadel/latest/docs) is a tool that allows you to manage ZITADEL resources through Terraform.
In other words, it lets you define and provision infrastructure for ZITADEL using Terraform configuration files.
This Terraform provider acts as a bridge, allowing you to manage various aspects of your ZITADEL instance directly through the [ZITADEL API](/apis/introduction), using Terraform's declarative configuration language.
It can be used to create, update, and delete ZITADEL resources, as well as to manage the relationships between those resources.
Before you start [#before-you-start]
Make sure you create the following resources in ZITADEL and have [Terraform installed](https://learn.hashicorp.com/tutorials/terraform/install-cli):
* [A ZITADEL Instance](../start/quickstart)
* [A service account](/guides/integrate/service-accounts/authenticate-service-accounts) with [enough authorization](/guides/manage/console/administrators) to manage the desired resources
Manage ZITADEL resources through terraform [#manage-zitadel-resources-through-terraform]
The full documentation and examples are available on the [Terraform registry](https://registry.terraform.io/providers/zitadel/zitadel/latest/docs).
To provide a small guide to where to start:
1. Create a folder where all the terraform files reside.
2. Configure the provider to use the right domain, port and token, with, for example, a `main.tf`file [as shown in the example](https://registry.terraform.io/providers/zitadel/zitadel/latest/docs).
3. Add a `zitadel_org` resource to the `main.tf` file, to create and manage a new organization in the instance, [as shown in the example](https://registry.terraform.io/providers/zitadel/zitadel/latest/docs/resources/org).
4. Add any resources to the organization in the `main.tf` file, [as an example a user](https://registry.terraform.io/providers/zitadel/zitadel/latest/docs/resources/human_user).
5. (Optional) Use Terraform in the directory with the command `terraform plan`, to see which resources would be created and how.
6. Apply the changes and start managing your resources with terraform with `terraform apply`.
7. (Optional) Delete your created resources with `terraform destroy` to clean-up.
References [#references]
* [Deploy ZITADEL in your infrastructure](/self-hosting/deploy/overview)
* [ZITADEL CLI](/self-hosting/manage/cli/overview)
* [Configuration Options in ZITADEL](/self-hosting/manage/configure)
# Migrate to ZITADEL
This section of guides shows you how to migrate from your current auth system to ZITADEL.
The individual guides in this section should give you an overview of things to consider before you start the migration.
When moving from a previous auth solution to ZITADEL, it is important to note that some decisions and features are unique to ZITADEL.
Without duplicating too much content, here are some important features and patterns to consider in terms of solution architecture.
You can read more about the basic structure and important concepts of ZITADEL in our [concepts section](/concepts).
Multi-tenancy architecture [#multi-tenancy-architecture]
Multi-tenancy in ZITADEL can be achieved through either [Instances](/concepts/structure/instance) or [Organizations](/concepts/structure/instance) or [Organizations](/guides/manage/console/organizations-overview).
Where instances represent isolated ZITADEL instances, Organizations provide a more permeable approach to multi-tenancy.
In most cases, when you want to achieve multi-tenancy, you use Organizations. Each organization can have their own set of Settings (eg, Security Policies, IDPs, Branding), Administrators, and Users.
Please also consult our guide on [Solution Scenarios](/guides/solution-scenarios) for B2C and B2B for more details.
Delegated access management [#delegated-access-management]
Some solutions that offer multi-tenancy require you to copy applications and settings to each tenant and manage changes individually.
ZITADEL works differently by using [Granted Projects](/concepts/structure/granted_projects).
Projects can be granted to [Organization](/guides/manage/console/organizations-overview#project-grants) or even to individual users.
You can think of it as a logical link to a Project, which can be used by the receiving Organization or User as if it was their own project, except privileges to modify the Project itself.
Delegated access management is a great way of keeping the administration overhead low and enabling [self-service](/concepts/features/selfservice#administrators-in-delegation) for Organizations to manage their own Settings and Authorizations.
Actions [#actions]
ZITADEL [Actions](/guides/manage/console/actions-overview) is the key feature to customize and create workflows and change the default behavior of the platform.
You define custom code that should be run on a specific Trigger.
A trigger could be the creation of a new user, getting profile information about a user, or a login attempt.
With the [HTTP module](/apis/actions/modules) you can even make calls to third party systems, for example, to receive additional user information from a backend system, or triggering a workflow in other systems (Webhook).
You can also create custom claims or manipulate tokens and information on the userinfo endpoint by using the [Complement Token Flow](/apis/actions/complement-token).
This might be required if an application expects roles/permissions in a certain format or additional attributes (eg, a backend user-id) as claims.
Metadata [#metadata]
You can store arbitrary key-value pairs of data on objects such as Users or Organizations.
Metadata could link a user to a specific backend user-id or represent an "organizational unit" for your business logic.
Metadata can be accessed directly with the correct [scopes](/apis/openidoauth/scopes#reserved-scopes) or transformed to custom claims (see above).
Migrating resources [#migrating-resources]
Migrating users [#migrating-users]
Migrating users with minimal impact on users can be a challenging task.
We provide some more information on migrating users and secrets in [this guide](./users).
Migrating clients / applications [#migrating-clients-applications]
After you have set up or imported your applications to ZITADEL, you need to update your client's settings, such as issuer, clientID or credentials.
It is not possible to create an application with a pre-defined clientID or import existing credentials.
Technical considerations [#technical-considerations]
Batch migration [#batch-migration]
**Batch migration** is the easiest way, if you can afford some minimal downtime to move all users and applications over to ZITADEL.
See the [User guide](./users) for batch migration of users.
Just-in-time migration [#just-in-time-migration]
In case all your applications depend on ZITADEL after the migration date, and ZITADEL is able to retrieve the required user information, including secrets, from the legacy system, then the recommended way is to let **ZITADEL orchestrate the user migration just-in-time**:
* Create a pre-authentication [Action](/guides/manage/console/actions-overview) to request user data from the legacy system and create a new user in ZITADEL.
* Optionally, create a post-authentication Action to flag successfully migrated users in your legacy system
For all other cases, we recommend that the **legacy system orchestrates the migration** of users to ZITADEL for more flexibility:
* Update your legacy system to create a user in ZITADEL on their next login, if not already flagged as migrated, by using our APIs (you can set the password and a verified email)
* Redirect migrated users with a login hint in the [auth request](https://zitadel.com/playgrounds/oidc) to ZITADEL to pre-select the user
In this case the migration can also be done as an import job or also allowing to create user session in both the legacy auth solution and ZITADEL in parallel with identity brokering:
* Set up ZITADEL to use your legacy system as an external identity provider (note: you can also use JWT-IDP, if you only have a token).
* Configure your app to use ZITADEL, which will redirect users automatically to the external identity provider to login.
* A session will be created both on the legacy system and ZITADEL
* If a user does not exist already in ZITADEL, you can auto-register new users and use an Action to pull additional information (eg, Secrets) from your legacy system. Note: ZITADEL links external identity information to users, meaning you can have users use both a password and external identity providers to login with the same user.
# Migrate Users
Migrating users from an existing system, while minimizing impact on said users, can be a challenging task.
Individual Users [#individual-users]
Creating individual users can be done with this endpoint: [ImportHumanUser](/reference/api/management/zitadel.management.v1.ManagementService.ImportHumanUser).
Please also consult our [guide](/guides/manage/user/reg-create-user) on how to create users.
```json
{
"userName": "test9@test9",
"profile": {
"firstName": "Road",
"lastName": "Runner",
"displayName": "Road Runner",
"preferredLanguage": "en"
},
"email": {
"email": "test@test.com",
"isEmailVerified": false
},
"hashedPassword": {
"value": "$2a$14$aPbwhMVJSVrRRW2NoM/5.esSJO6o/EIGzGxWiM5SAEZlGqCsr9DAK",
"algorithm": "bcrypt"
},
"passwordChangeRequired": false,
"otpCode": "testotp",
"requestPasswordlessRegistration": false,
"idps": [
{
"configId": "124425861423228496",
"externalUserId": "roadrunner@mailonline.com",
"displayName": "name"
}
]
}
```
Bulk import [#bulk-import]
For bulk import use the [import endpoint](/reference/api/admin/zitadel.admin.v1.AdminService.ImportData) on the admin API:
```json
{
"timeout": "10m",
"data_orgs": {
"orgs": [
{
"orgId": "104133391254874632",
"org": {
"name": "ACME"
},
"humanUsers": [
{
"userId": "104133391271651848",
"user": {
"userName": "test9@test9",
"profile": {
"firstName": "Road",
"lastName": "Runner",
"displayName": "Road Runner",
"preferredLanguage": "de"
},
"email": {
"email": "test@acme.tld",
"isEmailVerified": true
},
"hashedPassword": {
"value": "$2a$14$aPbwhMVJSVrRRW2NoM/5.esSJO6o/EIGzGxWiM5SAEZlGqCsr9DAK",
"algorithm": "bcrypt"
}
}
},
{
"userId": "120080115081209416",
"user": {
"userName": "testuser",
"profile": {
"firstName": "Test",
"lastName": "User",
"displayName": "Test User",
"preferredLanguage": "und"
},
"email": {
"email": "fabienne@caos.ch",
"isEmailVerified": true
},
"hashedPassword": {
"value": "$2a$14$785Fcdbpo9rn5L7E21nIAOJvGCPgWFrZhIAIfDonYXzWuZIKRAQkO",
"algorithm": "bcrypt"
}
}
},
{
"userId": "145195347319252359",
"user": {
"userName": "wile@test9",
"profile": {
"firstName": "Wile E.",
"lastName": "Coyote",
"displayName": "Wile E. Coyote",
"preferredLanguage": "en"
},
"email": {
"email": "wile.e@acme.tld"
}
}
}
]
}
]
}
}
```
We will improve the bulk import interface for users in the future.
You can show your interest or join the discussion on [this issue](https://github.com/zitadel/zitadel/issues/5524).
Migrate secrets [#migrate-secrets]
Besides user data you need to migrate secrets, such as password hashes, OTP seeds, and public keys for passkeys (FIDO2).
The snippets in the sections below are parts from the bulk import endpoint, to clarify how the different objects can be imported.
Passwords [#passwords]
ZITADEL stores passwords only as irreversible hashes, never in clear text.
Existing password hashes can be imported if they use a supported [hash algorithm](/concepts/architecture/secrets#hashed-secrets).
Import password hashes using the import API (snippet from [bulk-import](#bulk-import)):
```json
{
"userName": "test9@test9",
...,
"hashedPassword": {
"value": "$2a$14$aPbwhMVJSVrRRW2NoM/5.esSJO6o/EIGzGxWiM5SAEZlGqCsr9DAK",
"algorithm": "bcrypt"
},
"passwordChangeRequired": false,
...,
}
```
Upon initial login, ZITADEL validates the imported password using the appropriate verifier.
In ZITADEL, a password verifier checks the validity of a password hash created with an algorithm different from the currently configured one.
It acts as a translator, allowing ZITADEL to understand and validate hashes made with older algorithms like MD5 even when the system has transitioned to newer ones like Argon2.
This is crucial during migrations or when importing user data.
Essentially, a verifier ensures ZITADEL can work with passwords hashed using various algorithms, maintaining security while transitioning to stronger hashing methods.
Regardless of the `passwordChangeRequired` setting, the password is rehashed using the configured hasher algorithm and stored.
This ensures consistency and allows for automatic updates even when hasher settings are changed, such as increasing salt cost for bcrypt.
To configure the default hasher for new user passwords, set the `Algorithm` of the `PasswordHasher` in the [runtime settings file](/self-hosting/manage/configure/configure#runtime-configuration-file)
or by the environment variable `ZITADEL_SYSTEMDEFAULTS_PASSWORDHASHER_HASHER_ALGORITHM`, for example:
```
ZITADEL_SYSTEMDEFAULTS_PASSWORDHASHER_HASHER_ALGORITHM='pbkdf2'
```
Hasher settings updates will automatically rehash existing passwords when they are validated or changed.
In case the hashes can't be transferred directly, you always have the option to create a user in ZITADEL without password and prompt users to create a new password.
If your legacy system receives the passwords in clear text (eg, login form) you could also directly create users via ZITADEL API.
We will explain this pattern in more detail in this guide.
One-time passwords (OTP) [#one-time-passwords-otp]
You can pass the OTP secret when creating users:
*snippet from [bulk-import](#bulk-import) example:*
```json
{
"userName": "test9@test9",
...,
"otpCode": "testotp",
...,
}
```
Passkeys [#passkeys]
When creating new users, you can trigger a workflow that prompts the users to setup a passkey authenticator.
*snippet from [bulk-import](#bulk-import) example:*
```json
{
"userName": "test9@test9",
...,
"requestPasswordlessRegistration": false,
...,
}
```
For passkeys to work on the new system you need to make sure that the new auth server has the same domain as the legacy auth server.
Currently it is not possible to migrate passkeys directly from another system.
Users linked to an external IDP [#users-linked-to-an-external-idp]
A users `sub` is bound to the external [IDP's Client ID](/guides/manage/console/default-settings#identity-providers).
This means that the IDP Client ID configured in ZITADEL must be the same ID as in the legacy system.
Users should be imported with their `externalUserId`.
*snippet from [bulk-import](#bulk-import) example:*
```json
{
"userName": "test9@test9",
...,
"idps": [
{
"configId": "124425861423228496",
"externalUserId": "roadrunner@mailonline.com",
"displayName": "name"
}
...,
}
```
You can use an Action with [post-creation flow](/apis/actions/external-authentication#post-creation) to pull information such as roles from the old system and apply them to the user in ZITADEL.
Metadata [#metadata]
You can store arbitrary key-value information on a user (or Organization) in ZITADEL.
Use metadata to store additional attributes of the users, such as organizational unit, backend-id, etc.
Metadata must be added to users after the users were created. Currently metadata can't be added during user creation.
[API reference: User Metadata](/reference/api/user)
Request metadata from the userinfo endpoint by passing the required [reserved scope](/apis/openidoauth/scopes#reserved-scopes) in your auth request.
With the [complement token flow](/apis/actions/complement-token), you can also transform metadata (or roles) to custom claims.
Authorizations / Roles [#authorizations-roles]
You can assign roles from owned or granted projects to a user.
Authorizations must be added to users after the users were created. Currently metadata can't be added during user creation.
[API reference: User Authorization / Grants](/reference/api/authorization)
# The ZITADEL Quick Start Guide
In this guide, you’ll set up a Zitadel account and configure it to work with our pre-built example applications.
You can simply clone the repository, follow the console setup, and have a working authentication flow in minutes.
Get Started with ZITADEL Cloud [#get-started-with-zitadel-cloud]
Set up your ZITADEL account and organization to begin managing identities for your applications.
1\. Create your Account and Organization [#1-create-your-account-and-organization]
You first need access to the ZITADEL Cloud Customer Portal.
This is the administrative hub for managing your billing, teams, and instances.
1. **Sign Up**: Go to [zitadel.com](https://zitadel.com) and select Sign Up.
2. **Onboarding**: Follow the prompts to verify your email and set up your Portal Team.
* Tip: We recommend using Passkeys for a secure, passwordless login experience.
3. **Access**: Once authenticated, you will be redirected to the Customer Portal dashboard.
2\. Quick Onboarding [#2-quick-onboarding]
Complete the brief onboarding questions.
This data helps us prioritize the development of new features, SDKs, and integrations that matter most to our community.
3\. Create your ZITADEL instance [#3-create-your-zitadel-instance]
An [Instance](/concepts/structure/instance) is a fully isolated identity environment with its own users, policies, and data.
Most developers use separate instances to isolate Development, Test, and Production workflows.
Follow these steps to deploy your first instance:
1. **Start**: Click Create Instance on your dashboard.
2. **Identity**: Provide an Instance Name (e.g., dev-environment). This will be used to generate your default domain (e.g., dev-environment-xxxx.zitadel.cloud).
3. **Locality**: Select your Region.
* Note: Choosing a region close to your users minimizes latency and helps with data residency compliance.
4. **Admin Setup**: Create your Instance Administrator. This user has "root" permissions to manage all organizations, policies, and settings within this specific instance.
5. **Deploy**: Review your settings and click Create Instance.
4\. Create your Project and Application [#4-create-your-project-and-application]
In ZITADEL, [**Applications**](/guides/manage/console/applications-overview) are grouped into [**Projects**](/guides/manage/console/projects-overview).
This allows multiple applications (like a React frontend and a Go backend) to share the same roles and role assignments.
Launch the Management Console [#launch-the-management-console]
Click **Create your app**. This opens the Management Console for your instance in a new tab.
Log in using the Admin credentials you just created.
Step 1: Define your Project [#step-1-define-your-project]
* **Name**: Enter a name (e.g., Project1).
* **Framework**: Select your preferred framework
* **Continue**: Click the Continue button.
Step 2: Review Default Settings [#step-2-review-default-settings]
ZITADEL automatically configures the best security settings for your selected framework.
5\. Collect your Integration Keys [#5-collect-your-integration-keys]
Integrate ZITADEL into your App [#integrate-zitadel-into-your-app]
How the Authentication Flow Works [#how-the-authentication-flow-works]
ZITADEL handles the complexity of the OIDC handshake so your app stays secure without manual token management.
1. **Login**: App redirects the user to ZITADEL with a PKCE challenge.
2. **Auth**: User authenticates on the ZITADEL hosted login page.
3. **Exchange**: ZITADEL returns an Auth Code, which the app exchanges for an Access Token.
4. **Tokens**: The app shows the Access and ID Token
5. **Logout**: The app clears local tokens and terminates the ZITADEL session.
1\. Prerequisites [#1-prerequisites]
* [Node.js](https://nodejs.org/)
* [npm](https://www.npmjs.com/)
* [Node.js](https://nodejs.org/)
* [npm](https://www.npmjs.com/)
* [.NET SDK 8 or later](https://dotnet.microsoft.com/download)
* [Python](https://www.python.org/)
* [Poetry](https://python-poetry.org/)
* [Node.js](https://nodejs.org/)
* [npm](https://www.npmjs.com/)
* [Python](https://www.python.org/)
* [Poetry](https://python-poetry.org/)
* [Node.js](https://nodejs.org/)
* [npm](https://www.npmjs.com/)
* [Python](https://www.python.org/)
* [Poetry](https://python-poetry.org/)
* [Flutter](https://docs.flutter.dev/install)
* [Go](https://go.dev/)
* [Node.js](https://nodejs.org/)
* [npm](https://www.npmjs.com/)
* [PHP](https://www.php.net/)
* [Composer](https://getcomposer.org/)
* [Node.js](https://nodejs.org/)
* [npm](https://www.npmjs.com/)
* [Node.js](https://nodejs.org/)
* [npm](https://www.npmjs.com/)
* [Node.js](https://nodejs.org/)
* [npm](https://www.npmjs.com/)
* [Node.js](https://nodejs.org/)
* [npm](https://www.npmjs.com/)
* [Node.js](https://nodejs.org/)
* [npm](https://www.npmjs.com/)
* [Node.js](https://nodejs.org/)
* [npm](https://www.npmjs.com/)
* [Java Development Kit (JDK)](https://www.java.com/en/)
* [Maven](https://maven.apache.org/)
* [PHP](https://www.php.net/)
* [Composer](https://getcomposer.org/)
* [Node.js](https://nodejs.org/)
* [npm](https://www.npmjs.com/)
* [Node.js](https://nodejs.org/)
* [npm](https://www.npmjs.com/)
2\. Get the Example Project [#2-get-the-example-project]
```bash
git clone https://github.com/zitadel/example-auth-angular.git
cd example-auth-angular
```
```bash
git clone https://github.com/zitadel/example-auth-astro.git
cd example-auth-astro
```
```bash
git clone https://github.com/zitadel/example-auth-dotnet.git
cd example-auth-dotnet
```
```bash
git clone https://github.com/zitadel/example-auth-django.git
cd example-auth-django
```
```bash
git clone https://github.com/zitadel/example-auth-expressjs.git
cd example-auth-expressjs
```
```bash
git clone https://github.com/zitadel/example-auth-fastapi.git
cd example-auth-fastapi
```
```bash
git clone https://github.com/zitadel/example-auth-fastify.git
cd example-auth-fastify
```
```bash
git clone https://github.com/zitadel/example-auth-flask.git
cd example-auth-flask
```
```bash
git clone https://github.com/zitadel/zitadel_flutter.git
cd zitadel_flutter
```
```bash
git clone https://github.com/zitadel/zitadel-go.git
cd zitadel-go
```
```bash
git clone https://github.com/zitadel/zitadel-auth-hono.git
cd zitadel-auth-hono
```
```bash
git clone https://github.com/zitadel/example-auth-laravel.git
cd example-auth-laravel
```
```bash
git clone https://github.com/zitadel/example-auth-nestjs.git
cd example-auth-nestjs
```
```bash
git clone https://github.com/zitadel/example-auth-nextjs.git
cd example-auth-nextjs
```
```bash
git clone https://github.com/zitadel/example-auth-nuxtjs.git
cd example-auth-nuxtjs
```
```bash
git clone https://github.com/zitadel/example-auth-qwik.git
cd example-auth-qwik
```
```bash
git clone https://github.com/zitadel/example-auth-react.git
cd example-auth-react
```
```bash
git clone https://github.com/zitadel/example-auth-solidstart.git
cd example-auth-solidstart
```
```bash
git clone https://github.com/zitadel/example-auth-spring.git
cd example-auth-spring
```
```bash
git clone https://github.com/zitadel/example-auth-symfony.git
cd example-auth-symfony
```
```bash
git clone https://github.com/zitadel/example-auth-sveltekit.git
cd example-auth-sveltekit
```
```bash
git clone https://github.com/zitadel/example-auth-vuejs.git
cd example-auth-vuejs
```
3\. Configure your Credentials [#3-configure-your-credentials]
1. Copy+paste the .env.example to .env
2. Update the ZITADEL\_CLIENT\_ID and ZITADEL\_DOMAIN with the client id and issuer you collected in Step 5.
```bash
PORT=3000
NG_APP_ZITADEL_DOMAIN="https://your-zitadel-domain"
NG_APP_ZITADEL_CLIENT_ID="your-client-id"
NG_APP_ZITADEL_CALLBACK_URL="http://localhost:3000/auth/callback"
NG_APP_ZITADEL_POST_LOGOUT_URL="http://localhost:3000/auth/logout/callback"
NG_APP_ZITADEL_POST_LOGIN_URL="/profile"
```
1. Copy+paste the .env.example to .env
2. Update the ZITADEL\_CLIENT\_ID, ZITADEL\_CLIENT\_SECRET and ZITADEL\_DOMAIN with the client id and issuer you collected in Step 5.
```bash
NODE_ENV=development
PORT=3000
SESSION_SECRET="your-very-secret-and-strong-session-key"
SESSION_DURATION=3600
ZITADEL_DOMAIN="https://your-zitadel-domain"
ZITADEL_CLIENT_ID="your-zitadel-application-client-id"
ZITADEL_CLIENT_SECRET="your-zitadel-application-client-secret"
ZITADEL_CALLBACK_URL="http://localhost:3000/api/auth/callback/zitadel"
ZITADEL_POST_LOGIN_URL="/profile"
ZITADEL_POST_LOGOUT_URL="http://localhost:3000/api/auth/logout/callback"
NEXTAUTH_URL="http://localhost:3000"
```
1. Copy+paste the .env.example to .env
2. Update the ZITADEL\_CLIENT\_ID, ZITADEL\_CLIENT\_SECRET and ZITADEL\_DOMAIN with the client id and issuer you collected in Step 5.
```bash
PORT=3000
SESSION_DURATION=3600
ZITADEL_DOMAIN="https://your-zitadel-domain"
ZITADEL_CLIENT_ID="your-zitadel-application-client-id"
ZITADEL_CLIENT_SECRET="your-randomly-generated-client-secret"
ZITADEL_POST_LOGOUT_URL="http://localhost:3000/auth/logout/callback"
```
1. Copy+paste the .env.example to .env
2. Update the ZITADEL\_CLIENT\_ID and ZITADEL\_DOMAIN with the client id and issuer you collected in Step 5.
```bash
PY_ENV=development
PORT=3000
SESSION_SECRET="your-very-secret-and-strong-session-key"
SESSION_DURATION=3600
ZITADEL_DOMAIN="https://your-zitadel-domain"
ZITADEL_CLIENT_ID="your-zitadel-application-client-id"
ZITADEL_CLIENT_SECRET="your-randomly-generated-client-secret"
ZITADEL_CALLBACK_URL="http://localhost:3000/auth/callback"
ZITADEL_POST_LOGIN_URL="/profile"
ZITADEL_POST_LOGOUT_URL="http://localhost:3000/auth/logout/callback"
```
1. Copy+paste the .env.example to .env
2. Update the ZITADEL\_CLIENT\_ID, ZITADEL\_CLIENT\_SECRET and ZITADEL\_DOMAIN with the client id and issuer you collected in Step 5.
```bash
NODE_ENV=development
PORT=3000
SESSION_SECRET="your-very-secret-and-strong-session-key"
SESSION_DURATION=3600
ZITADEL_DOMAIN="https://your-zitadel-domain"
ZITADEL_CLIENT_ID="your-zitadel-application-client-id"
ZITADEL_CLIENT_SECRET="your-zitadel-application-client-secret"
ZITADEL_CALLBACK_URL="http://localhost:3000/auth/callback/zitadel"
ZITADEL_POST_LOGIN_URL="/profile"
ZITADEL_POST_LOGOUT_URL="http://localhost:3000/auth/logout/callback"
```
1. Copy+paste the .env.example to .env
2. Update the ZITADEL\_CLIENT\_ID, ZITADEL\_CLIENT\_SECRET and ZITADEL\_DOMAIN with the client id and issuer you collected in Step 5.
```bash
PY_ENV=development
PORT=3000
SESSION_SECRET="your-very-secret-and-strong-session-key"
SESSION_DURATION=3600
ZITADEL_DOMAIN="https://your-zitadel-domain"
ZITADEL_CLIENT_ID="your-zitadel-application-client-id"
ZITADEL_CLIENT_SECRET="your-zitadel-application-client-secret"
ZITADEL_CALLBACK_URL="http://localhost:3000/auth/callback"
ZITADEL_POST_LOGIN_URL="/profile"
ZITADEL_POST_LOGOUT_URL="http://localhost:3000/auth/logout/callback"
```
1. Copy+paste the .env.example to .env
2. Update the ZITADEL\_CLIENT\_ID, ZITADEL\_CLIENT\_SECRET and ZITADEL\_DOMAIN with the client id and issuer you collected in Step 5.
```bash
NODE_ENV=development
PORT=3000
SESSION_SECRET="your-very-secret-and-strong-session-key"
SESSION_DURATION=3600
ZITADEL_DOMAIN="https://your-zitadel-domain"
ZITADEL_CLIENT_ID="your-zitadel-application-client-id"
ZITADEL_CLIENT_SECRET="your-zitadel-application-client-secret"
ZITADEL_CALLBACK_URL="http://localhost:3000/auth/callback/zitadel"
ZITADEL_POST_LOGIN_URL="/profile"
ZITADEL_POST_LOGOUT_URL="http://localhost:3000/auth/logout/callback"
```
1. Copy+paste the .env.example to .env
2. Update the ZITADEL\_CLIENT\_ID, ZITADEL\_CLIENT\_SECRET and ZITADEL\_DOMAIN with the client id and issuer you collected in Step 5.
```bash
PY_ENV=development
PORT=3000
SESSION_SECRET="your-very-secret-and-strong-session-key"
SESSION_DURATION=3600
ZITADEL_DOMAIN="https://your-zitadel-domain"
ZITADEL_CLIENT_ID="your-zitadel-application-client-id"
ZITADEL_CLIENT_SECRET="your-zitadel-application-client-secret"
ZITADEL_CALLBACK_URL="http://localhost:3000/auth/callback"
ZITADEL_POST_LOGIN_URL="/profile"
ZITADEL_POST_LOGOUT_URL="http://localhost:3000/auth/logout/callback"
```
No action required for this step.
The issuer and client Id will be passed directly when running the app (next step)
No action required for this step.
The issuer and client Id will be passed directly when running the app (next step)
1. Copy+paste the .env.example to .env
2. Update the ZITADEL\_CLIENT\_ID, ZITADEL\_CLIENT\_SECRET and ZITADEL\_DOMAIN with the client id and issuer you collected in Step 5.
```bash
NODE_ENV=development
PORT=3000
SESSION_SECRET="your-very-secret-and-strong-session-key"
SESSION_DURATION=3600
ZITADEL_DOMAIN="https://your-zitadel-domain"
ZITADEL_CLIENT_ID="your-zitadel-application-client-id"
ZITADEL_CLIENT_SECRET="your-zitadel-application-client-secret"
ZITADEL_CALLBACK_URL="http://localhost:3000/auth/callback/zitadel"
ZITADEL_POST_LOGIN_URL="/profile"
ZITADEL_POST_LOGOUT_URL="http://localhost:3000/auth/logout/callback"
```
1. Copy+paste the .env.example to .env
2. Update the ZITADEL\_CLIENT\_ID, ZITADEL\_CLIENT\_SECRET and ZITADEL\_DOMAIN with the client id and issuer you collected in Step 5.
3. Generate app key, this will update the APP\_KEY variable in the .env file
```bash
php artisan key:generate
```
```bash
APP_KEY="your-app-key"
APP_ENV=local
APP_DEBUG=true
SERVER_URL="http://localhost:3000"
SERVER_PORT=3000
DB_CONNECTION=sqlite
ZITADEL_DOMAIN="https://your-zitadel-domain"
ZITADEL_CLIENT_ID="your-zitadel-application-client-id"
ZITADEL_CLIENT_SECRET="your-randomly-generated-client-secret"
ZITADEL_POST_LOGOUT_URL="http://localhost:3000/auth/logout/callback"
```
1. Copy+paste the .env.example to .env.local
2. Update the ZITADEL\_CLIENT\_ID, ZITADEL\_CLIENT\_SECRET and ZITADEL\_DOMAIN with the client id and issuer you collected in Step 5.
```bash
NODE_ENV=development
PORT=3000
SESSION_SECRET="your-very-secret-and-strong-session-key"
SESSION_SALT="your-very-secret-and-strong-session-salt"
SESSION_DURATION=3600
ZITADEL_DOMAIN="https://your-zitadel-domain"
ZITADEL_CLIENT_ID="your-zitadel-application-client-id"
ZITADEL_CLIENT_SECRET="your-zitadel-application-client-secret"
ZITADEL_CALLBACK_URL="http://localhost:3000/auth/callback/zitadel"
ZITADEL_POST_LOGIN_URL="/profile"
ZITADEL_POST_LOGOUT_URL="http://localhost:3000/auth/logout/callback"
```
1. Copy+paste the .env.example to .env
2. Update the ZITADEL\_CLIENT\_ID, ZITADEL\_CLIENT\_SECRET and ZITADEL\_DOMAIN with the client id and issuer you collected in Step 5.
```bash
NODE_ENV=development
PORT=3000
SESSION_SECRET="your-very-secret-and-strong-session-key"
SESSION_DURATION=3600
ZITADEL_DOMAIN="[https://your-instance.zitadel.cloud/](https://your-instance.zitadel.cloud/)"
ZITADEL_CLIENT_ID="zitadel-client-id"
ZITADEL_CLIENT_SECRET="zitadel-client-secret"
ZITADEL_CALLBACK_URL="http://localhost:3000/api/auth/callback/zitadel"
ZITADEL_POST_LOGIN_URL="/profile"
ZITADEL_POST_LOGOUT_URL="http://localhost:3000/api/auth/logout/callback"
NEXTAUTH_URL="http://localhost:3000"
```
1. Copy+paste the .env.example to .env
2. Update the ZITADEL\_CLIENT\_ID, ZITADEL\_CLIENT\_SECRET and ZITADEL\_DOMAIN with the client id and issuer you collected in Step 5.
```bash
NODE_ENV=development
PORT=3000
SESSION_SECRET="your-very-secret-and-strong-session-key"
SESSION_DURATION=3600
ZITADEL_DOMAIN="https://your-zitadel-domain"
ZITADEL_CLIENT_ID="your-zitadel-application-client-id"
ZITADEL_CLIENT_SECRET="your-zitadel-application-client-secret"
ZITADEL_CALLBACK_URL="http://localhost:3000/api/auth/callback/zitadel"
ZITADEL_POST_LOGIN_URL="/profile"
ZITADEL_POST_LOGOUT_URL="http://localhost:3000"
NEXTAUTH_URL="http://localhost:3000"
```
1. Copy+paste the .env.example to .env
2. Update the ZITADEL\_CLIENT\_ID, ZITADEL\_CLIENT\_SECRET and ZITADEL\_DOMAIN with the client id and issuer you collected in Step 5.
```bash
NODE_ENV=development
VITE_PORT=3000
VITE_SESSION_SECRET="your-very-secret-and-strong-session-key"
VITE_SESSION_DURATION=3600
VITE_ZITADEL_DOMAIN="https://your-zitadel-domain"
VITE_ZITADEL_CLIENT_ID="your-zitadel-application-client-id"
VITE_ZITADEL_CLIENT_SECRET="your-zitadel-application-client-secret"
VITE_ZITADEL_CALLBACK_URL="http://localhost:3000/auth/callback/zitadel"
VITE_ZITADEL_POST_LOGOUT_URL="http://localhost:3000/api/auth/logout/callback"
```
1. Copy+paste the .env.example to .env
2. Update the ZITADEL\_CLIENT\_ID and ZITADEL\_DOMAIN with the client id and issuer you collected in Step 5.
```bash
NODE_ENV=development
PORT=3000
VITE_ZITADEL_DOMAIN="https://your-zitadel-domain"
VITE_ZITADEL_CLIENT_ID="your-client-id"
VITE_ZITADEL_CALLBACK_URL="http://localhost:3000/auth/callback"
VITE_ZITADEL_POST_LOGIN_URL="/profile"
VITE_ZITADEL_POST_LOGOUT_URL="http://localhost:3000"
```
1. Copy+paste the .env.example to .env
2. Update the ZITADEL\_CLIENT\_ID, ZITADEL\_CLIENT\_SECRET and ZITADEL\_DOMAIN with the client id and issuer you collected in Step 5.
```bash
NODE_ENV=development
PORT=3000
SESSION_SECRET="your-very-secret-and-strong-session-key"
SESSION_DURATION=3600
ZITADEL_DOMAIN="https://your-zitadel-domain"
ZITADEL_CLIENT_ID="your-zitadel-application-client-id"
ZITADEL_CLIENT_SECRET="your-zitadel-application-client-secret"
ZITADEL_CALLBACK_URL="http://localhost:3000/api/auth/callback/zitadel"
ZITADEL_POST_LOGIN_URL="/profile"
ZITADEL_POST_LOGOUT_URL="http://localhost:3000/api/auth/logout/callback"
NEXTAUTH_URL="http://localhost:3000"
```
1. Copy+paste the .env.example to .env
2. Update the ZITADEL\_CLIENT\_ID, ZITADEL\_CLIENT\_SECRET and ZITADEL\_DOMAIN with the client id and issuer you collected in Step 5.
```bash
SPRING_PROFILES_ACTIVE=development
PORT=3000
SESSION_SECRET="your-very-secret-and-strong-session-key"
SESSION_DURATION=3600
ZITADEL_DOMAIN="https://your-zitadel-domain"
ZITADEL_CLIENT_ID="your-zitadel-application-client-id"
ZITADEL_CLIENT_SECRET="your-randomly-generated-client-secret"
ZITADEL_CALLBACK_URL="http://localhost:3000/auth/callback"
ZITADEL_POST_LOGOUT_URL="http://localhost:3000/auth/logout/callback"
```
1. Copy+paste the .env.example to .env
2. Update the ZITADEL\_CLIENT\_ID, ZITADEL\_CLIENT\_SECRET and ZITADEL\_DOMAIN with the client id and issuer you collected in Step 5.
```bash
APP_SECRET="your-app-secret-key"
SERVER_URL="http://localhost:3000"
SERVER_PORT=3000
ZITADEL_DOMAIN="https://your-zitadel-domain"
ZITADEL_CLIENT_ID="your-zitadel-application-client-id"
ZITADEL_CLIENT_SECRET="your-randomly-generated-client-secret"
ZITADEL_POST_LOGOUT_URL="http://localhost:3000/auth/logout/callback"
```
1. Copy+paste the .env.example to .env
2. Update the ZITADEL\_CLIENT\_ID, ZITADEL\_CLIENT\_SECRET and ZITADEL\_DOMAIN with the client id and issuer you collected in Step 5.
```bash
SESSION_SECRET="your-very-secret-and-strong-session-key"
SESSION_DURATION=3600
# Example: https://my-org-a1b2c3.zitadel.cloud
ZITADEL_DOMAIN="https://your-zitadel-domain"
ZITADEL_CLIENT_ID="your-zitadel-application-client-id"
ZITADEL_CLIENT_SECRET="your-zitadel-application-client-secret"
ZITADEL_CALLBACK_URL="http://localhost:3000/auth/callback/zitadel"
ZITADEL_POST_LOGIN_URL="/profile"
ZITADEL_POST_LOGOUT_URL="http://localhost:3000/auth/logout/callback"
NEXTAUTH_URL="http://localhost:3000"
```
1. Copy+paste the .env.example to .env
2. Update the ZITADEL\_CLIENT\_ID and ZITADEL\_DOMAIN with the client id and issuer you collected in Step 5.
```bash
NODE_ENV=development
PORT=3000
VITE_ZITADEL_DOMAIN="https://your-zitadel-domain"
VITE_ZITADEL_CLIENT_ID="your-client-id"
VITE_ZITADEL_CALLBACK_URL="http://localhost:3000/auth/callback"
VITE_ZITADEL_POST_LOGIN_URL="/profile"
VITE_ZITADEL_POST_LOGOUT_URL="http://localhost:3000"
```
4\. Build and Run [#4-build-and-run]
```bash
make start
```
Your app will be live at `http://localhost:3000`.
```bash
make start
```
Your app will be live at `http://localhost:3000`.
```bash
make start
```
Your app will be live at `http://localhost:3000`.
```bash
make start
```
Your app will be live at `http://localhost:3000`.
```bash
make start
```
Your app will be live at `http://localhost:3000`.
```bash
make start
```
Your app will be live at `http://localhost:3000`.
```bash
make start
```
Your app will be live at `http://localhost:3000`.
```bash
make start
```
Your app will be live at `http://localhost:3000`.
Make sure to replace the \[zitadel\_issuer] and \[zitadel\_client\_id] with your own value before running the command
```bash
flutter run -d chrome --web-port=4444 --dart-define zitadel_url=[zitadel_issuer] --dart-define zitadel_client_id=[zitadel_client_id]
```
Your app will be live at `http://localhost:4444`.
Make sure to replace the \[zitadel\_domain] (use the domain from the issuer without the https\://) and \[zitadel\_client\_id] with your own value before running the command
```bash
go run example/app/app.go --domain [zitadel_domain] --key XKv2Lqd7YAq13NUZVUWZEWZeruqyzViM --clientID [zitadel_client_id] --redirectURI http://localhost:8089/auth/callback
```
Your app will be live at `http://localhost:8089`.
```bash
make start
```
Your app will be live at `http://localhost:3000`.
```bash
make start
```
Your app will be live at `http://localhost:3000`.
```bash
make start
```
Your app will be live at `http://localhost:3000`.
```bash
make start
```
Your app will be live at `http://localhost:3000`.
```bash
make start
```
Your app will be live at `http://localhost:3000`.
```bash
make start
```
Your app will be live at `http://localhost:3000`.
```bash
make start
```
Your app will be live at `http://localhost:3000`.
```bash
make start
```
Your app will be live at `http://localhost:3000`.
```bash
make start
```
Your app will be live at `http://localhost:3000`.
```bash
make start
```
Your app will be live at `http://localhost:3000`.
```bash
make start
```
Your app will be live at `http://localhost:3000`.
```bash
make start
```
Your app will be live at `http://localhost:3000`.
Success! 🚀 [#success-🚀]
You’ve successfully integrated ZITADEL into an application.
Login policy settings misconfigurations that occur during the testing phase can easily lead to a [lockout](/legal/policies/account-lockout-policy). To ensure you don't lose access to your instance:
1. **Generate a backup PAT:** Create a [Service Account Personal Access Token](/guides/integrate/service-accounts/personal-access-token) with the [`IAM_OWNER`](/guides/manage/console/administrators) role to revert any login UI misconfigurations using the API.
2. **Add a second Instance Administrator:** Always designate at least one [**second instance administrator**](/guides/manage/console/administrators).
What’s next? [#whats-next]
* [**Example Applications**](/examples/introduction): Find more comprehensive guides and examples for the different frameworks
* [**SSO**](/guides/integrate/services): Learn how to add SSO to your services
* [**Customize the UI**](/guides/manage/customize/branding): Make the login page your own with Branding.
* [**Explore the API**](/apis/introduction): Check out the ZITADEL API Reference for advanced integrations.
Need help? Join our [Discord community](https://zitadel.com/chat) or explore the full Documentation. Happy coding!
# B2B Multi-Tenant Authentication with ZITADEL
Business to Business [#business-to-business]
B2B describes the situation where an organization interacts with other organizations.
This **multi-tenancy architecture** usually adds some form of complexity to an Identity and Access Management System.
In ZITADEL an [organization](/guides/manage/console/organizations-overview) can represent a business partner or partner who typically has its own branding and has different access settings like an additional federated login for its users.
B2B can be a simple scenario where an organization only shares one of its projects with another organization or has a more complex case where an organization is offering a portal application to all its partners with included (self)administration.
Sample scenario [#sample-scenario]
Octagon is a fictitious company used throughout this guide to explain the details and key concepts of such a B2B scenario.
Octagon tries to solve multiple tasks in the banking field. Its portfolio includes several applications for their employees, customers, and partners. Some of which are web-based, some of which are used by service accounts only.
Portal Application [#portal-application]
Octagon has a **Portal application** where its employees can access their account and list all applications they are allowed to use.
Employees work for a department within Octagon or for Octagon itself.
Some users have enhanced features because they supervise certain teams. Those can onboard new employees and manage their roles and features.
Target groups of the application can be split into:
* **Employees:** users who are using the application as a starting point for their work.
* **Supervisors:** users who are mainly using the application to manage users and their access of their department.
* **Administrators:** users who can grant additional organizations or departments and elect supervisors.
Planning considerations [#planning-considerations]
To define the need of the **Portal Application**, some planning considerations about organizations have to be made:
* **Login and Access:** Does a user have a preset organization to login? Does the application show the default login page, or does each organization use its own branding?
* **Organizations:** Does a user have access to multiple organizations? Is a user required to use a different federated login for those organizations?
* **Roles** Does the application need users to have specific roles assigned within their organizations? Which roles are needed to enable certain features of the application?
Login [#login]
You can decide whether an organization is preselected for the login or if the user is redirected to the default login screen. Using OpenID Connect, you can send the user to a specific organization by defining the organization in a [reserved scope](/apis/openidoauth/scopes#reserved-scopes) (Organization Domain).
Settings to the branding or the login options of the organization can be made from the organization section in [Console](/guides/manage/console/console-overview).
The behavior of the login branding can be set in your project detail page. You can choose the branding of the selected organization, the user's organization, or the project's organization.
Organizations [#organizations]
Generally a user belongs to and is managed by one organization, however, a user can be authorized to access projects in other organizations via role assignments. A user should be able to use the same identity to switch between organizations.
If this feature is not desired, a separate user for each organization should be created.
Adding a user from a different organization to the audience of a project can be as easy as assigning a new role to a user. A role assignment combines a user from any organization with a project and 0-N roles.
Our sample scenario includes the following users:
* **Dimitri:** a team leader who is employed by the Pentagon, an Octagon department. Dimitri uses his Microsoft Account in combination with a One Time Password to access the portal. Pentagon therefore has set up Microsoft as their Identity Provider. Pentagon also requires its users to secure their accounts with additional factors.
* **Michael:** a trainee of the Pentagon who uses the portal to access his workspace apps. Michael uses his Google Account in combination with biometrics (e.g., his fingerprint on his Laptop).
* **Bill:** is employed at Octagon as an Administrator of the Portal Application. Bill also uses a Microsoft Account in combination with a Security Key to secure his account.
After having determined the constellation of the organizations and its users, all the necessary data (Portal project with roles and app, users, login requirements, identity providers, branding) should be set up in [Console](https://$\{CUSTOM_DOMAIN}.zitadel.cloud/ui/console/org).
A B2B [sample application](https://github.com/zitadel/zitadel-nextjs-b2b) for NextJS can be found [here](../../examples/login/nextjs-b2b).
To allow another organization to use a project, a project grant has to be created. Upon creation, roles for a grant can be limited to a subset of the total project roles.
In our scenario, the Octagon creates a project grant for the Pentagon. Pentagon is limited to use `writer` and `reader` role. The `admin` role is reserved for the Octagon organization itself.
Roles [#roles]
This scenario involves two organizations: Octagon and Pentagon.
Bill, a user from the Octagon organization, is assigned the admin role for the Portal project.
Dimitri and Michael are both part of the Pentagon organization and have been assigned the `writer` and `reader` roles, respectively.
> Note: Roles are intended for your application's internal business logic and require separate validation.
> The users in this example cannot manage role assignments for other users unless they have an appropriate ZITADEL administrator role.
If you build a dashboard for users to assign roles, you must use the Management API with the user's personal access token and not with a machine user's token. This ensures that all actions are properly recorded in the audit log. To enable this, the users performing these actions must be granted a ZITADEL manager role.
Noteworthy [#noteworthy]
As ZITADEL includes unlimited users, projects, and applications and comes with all security features in the FREE tier, ZITADEL can be considered a great alternative to other SaaS IAM systems such as Auth0 or Okta.
In such a case with this high potential of scalability where user counts can grow explosively, ZITADEL does not become the bottleneck and therefore is the valid choice. You can learn more on ZITADELs benefits and the pricing [here](https://zitadel.com/pricing).
Learn more [#learn-more]
* [Creating an organization](../manage/console/organizations)
* [Organization Branding](../manage/customize/branding)
* [Authorization](../integrate/login/oidc/oauth-recommended-flows)
# ZITADEL for B2C Scenarios
Business to Consumer [#business-to-consumer]
Users in general come with different needs. You may have end users, employees, or even customers from other parties (B2B).
This groups of users usually don't share the same intentions and use applications differently.
When planning your applications, investing time in researching your apps architecture is vital for later feature upgrades and enhancements as those changes come in with heftier price points if you have to make bigger changes.
This guide introduces you to the grouping and structuring of ZITADEL projects which forms the base for all projects. This can be used as a quick start to the [B2B scenario](./b2b), which is merely focused on planning considerations if you are having projects with multiple organizations.
The journey of this guide starts with creating an Organization, the outermost layer of ZITADEL within your instance, as it is the vessel for projects, roles, applications and users.
Creation can be done from [ZITADEL Management Console](https://$\{CUSTOM_DOMAIN}.zitadel.cloud/ui/console/orgs/create). You can choose your current account for the organization owner or create a new one.
Depending on your Software Development Life Cycle (SDLC) you can create multiple organizations or projects to keep your applications environments separated.
Organization Domain [#organization-domain]
Right after org creation you'll be greeted with the domain section of your organization.
ZITADEL automatically creates an Organization Domain of the form `[orgname].zitadel.cloud`, but you can create your own. Depending on the settings on the [Domain Policy](../../concepts/structure/policies) it has to be verified by saving a verification file on the specified location.
We recommend that you create your domains early as they create a sense of confidence and trust for your application and changes later on might create additional migration effort.
You can read more about how ZITADEL handles usernames [here](../manage/console/organizations-overview#login-name-formats).
Data Provisioning [#data-provisioning]
ZITADEL gives you a basic storage for users and manages phone and email addresses. It also allows you to store your own application data such as preferences or external identifiers to the metadata of a user.
If you are migrating an existing project and you already have an external identity store you can consider bulk importing your user datasets.
Read our [Management API definitions](/reference/api/management) for more info. If the users email is not verified or no password is set, a initialization mail will be send.
Requests to the management API are rate limited. Read our [Rate limit Policy](/legal/policies/rate-limit-policy) for more info.
User Authentication [#user-authentication]
User Authentication can be performed in multiple ways. Default method in ZITADEL is username and password with MFA enabled.
ZITADEL allows you to configure Multifactor- and Passkey Authentication in order to enhance security for your users. All authentication methods are available from the FREE Tier.
To setup your organizations login policy, go to your organizations detail in [Console](https://$\{CUSTOM_DOMAIN}.zitadel.cloud/ui/console/org).
When planning your application consider the following questions about User Authentication:
* What are the methods to authenticate your users?
* Where will your users enter their credentials?
* Do you offer password authentication?
* What do you do to keep your users credentials safe?
* Do you offer Multifactor Authentication?
* Do you offer Login via Identity Provider?
* Which languages do you have to provide?
When looking at these questions, you may have to admit that building an Identity Management System is much more complex and complicated than you thought initially and implementing if yourself may be too much work.
Particularly because you should focus building your applications.
Federation [#federation]
ZITADEL supports signup with OIDC Identity providers as well as JWT Identity Providers. On signup, ZITADEL imports user information to the own profile.
Hosted Login [#hosted-login]
ZITADEL offers a "secure by default approach" and comes with a Hosted Login which is a fixed endpoint for your users to authenticate.
It's safe and secure and comes with Multifactor, Federated Login and Passkey capabilities.
OIDC (OpenID Connect) opens the doors to Single Sign On (SSO). Especially if you have more than one application, you may want a central and familiar authentication experience.
With SSO, ZITADEL provides a seamless experience across all your apps.
Branding [#branding]
Branding and customization is a very important part of your application.
With ZITADEL you can modify colors, logos, icons as well as configure your typography styles, such that you can provide a consistent design throughout your applications.
In addition to visual modifications, you can edit notification texts for your users.
ZITADEL gives you handlebar similar templating for variables. Of course you can define texts for any language.
We'd appreciate if you could contribute to our repo with translations of your language. Read on how to contribute [here](../manage/customize/texts).
> Note that your management console design changes to your design too
Projects and applications [#projects-and-applications]
As our Hosted Login is a separate authentication screen, you have to determine how you are directing your users from your applications.
ZITADEL's Applications live under ZITADEL's Projects. You may add multiple applications for your different application-types (Native, Web, User Agent, or API). When setting up your applications consider reading our guide about [Authentication Flows](../integrate/login/oidc/login-users).
Access Control [#access-control]
By having authenticated a user you need to ensure users and services have access to your application and APIs. This process is called Access Control and comprises User Authentication, Authorization and Policy Enforcement.
Take the following considerations:
* Does your application call your own APIs?
* Does your application need to call third-party APIs?
* Do **you** offer an API for third-party applications?
The data required to check if a user has access to a certain API is stored within a user grant. This information typically is stored within roles or custom claims and can be accessed with an `access` or OIDC `id` token.
Read more about Authorization in our [Guide](../integrate/login/oidc/oauth-recommended-flows).
Learn more [#learn-more]
* [Creating an organization](../manage/console/organizations-overview#create-a-new-organization)
* [Organization Branding](../manage/customize/branding)
* [Authorization](../integrate/login/oidc/oauth-recommended-flows)
# Set up ZITADEL for your Scenario
Each customer does have different needs and use-cases. In ZITADEL you are able to adjust settings depending on your needs.
In this section we show you the different use-cases we have already experienced, that could interest you.
Automatically redirect users if the organization has only one identity provider [#automatically-redirect-users-if-the-organization-has-only-one-identity-provider]
You have different customers (organizations) in your ZITADEL instance and they have different needs on how to authenticate their users. One of your customers does only allow login with an external identity provider like Google, Entra ID, and so on.
If a user of this organization wants to login, you don't want them to enter their username in the ZITADEL Login UI, they should be redirected directly to the identity provider without their interaction.
Settings [#settings]
1. Go to the "Identity Providers" Settings of the organization
2. Set up the needed identity provider: Read this [guide](../integrate/identity-providers/introduction) if you don't know how
3. Go to the "Login Behavior and Security" settings of the organization
4. Disable "Username Password Allowed" and enable "External IDP allowed" in the Advanced Section
Now your application can send either the organizations id (`urn:zitadel:iam:org:id:{id}`) or Organization Domain (`urn:zitadel:iam:org:domain:primary:{domainname}`) scope on your authorization request to identify on which organization the users should be logged in.
More about the [scopes](/apis/openidoauth/scopes#reserved-scopes)
Custom Application Domain per Organization [#custom-application-domain-per-organization]
If you have an application that runs a dedicated domain for each customer you need to instruct ZITADEL to allow redirection for each domain specifically to safeguard against phishing attacks.
Example:
MyApplication: `customer-a.app.com`
ZITADEL Login: `login.app.com`
In the OIDC Authorization request you always have to send the redirect URI to where you like to be redirected after login.
To handle this scenario it is possible to register multiple URIs on each application in ZITADEL, the only criteria is that the requested URI has to match one of the registered URIs.
Read more about [applications](../manage/console/applications-overview) and the [redirect urls](../manage/console/applications-overview#redirect-uris)
Trigger organization in ZITADEL login [#trigger-organization-in-zitadel-login]
It is possible to trigger the organization directly with the authorization request to ZITADEL.
This will have the following impacts:
* Trigger organization login behavior settings
* Trigger organization branding
* Only allow users from selected organization to login
To request the organization send either the the organization id (`urn:zitadel:iam:org:id:{id}`) or Organization Domain (`urn:zitadel:iam:org:domain:primary:{domainname}`) scope on your authentication request from your application.
More about the [scopes](/apis/openidoauth/scopes#reserved-scopes)
Use email to login [#use-email-to-login]
There are two different possibilities to achieve login with an email.
1. Use an email address as username
2. Use the email field of the user as additional login to the username
Use an email address as username [#use-an-email-address-as-username]
To be able to use the email as username you have to disable the attribute "User Loginname must contain orgdomain" on your domain settings.
This means that all your users will not be suffixed with the domain of your organization and you can enter the email as username.
All usernames will then be globally unique within your instance.
You can either set this attribute on your whole ZITADEL instance or just on some specific organizations.
Use the email field of the user as additional login to the username [#use-the-email-field-of-the-user-as-additional-login-to-the-username]
No matter how the username of your user does look like.
You can additionally allow login with the email attribute of the user.
You can find this in the "Login Behavior and Security" Setting of your instance or organizations.
Go to the "Advanced" section, per default login with email address should be allowed. It is possible to disable it.
Use phone number to login [#use-phone-number-to-login]
There are two different possibilities to achieve login with a phone number.
1. Use a phone number as username
2. Use the phone number field of the user as additional login to the username
Use a phone number as username [#use-a-phone-number-as-username]
To be able to use the phone number as username you have to disable the attribute "User Loginname must contain orgdomain" on your domain settings.
This means that your users will not be suffixed with the domain of an organization and you can enter the phone number as username.
All usernames will then be globally unique within your instance.
You can either set this attribute on your whole ZITADEL instance or just on some specific organizations.
Use the phone number field of the user as additional login to the username [#use-the-phone-number-field-of-the-user-as-additional-login-to-the-username]
No matter how the username of your user looks like,
you can additionally allow login with the phone number attribute of the user.
You can enable this feature in the "Login Behavior and Security" Setting of your instance or organizations.
Go to the "Advanced" section, per default login with phone number should be allowed. It is possible to disable it.
Embedding ZITADEL in an iFrame [#embedding-zitadel-in-an-i-frame]
To maximize the security during login and in the Management Console UI, ZITADEL follows security best practices by setting a
Content-Security-Policy (CSP), X-Frame-Options and cookies with SameSite Lax:
```
Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: deny
```
These settings block the use of serving it in an iframe to prevents clickjacking attacks.
Enable iFrame embedding [#enable-i-frame-embedding]
This change can make you vulnerable to clickjacking attacks.
If your applications need to load ZITADEL inside an iframe, e.g. for a silent login or silent refresh, you can enable the use on an instance level.
1. Navigate to the Default Settings.
2. Click on the Security Policy tab.
3. Enable the "Allow IFrame" and add the host(s) you load the iframe from.
You can add further hosts later on.
This will change the CSP to the following:
```
Content-Security-Policy: frame-ancestors https://custom-domain.com
```
remove the X-Frame-Options header and change the SameSite to `None`.
Please note, that SameSite None requires the cookie to be flagged `secure`, which means it must be sent over TLS (HTTPS) or localhost.
This also means that domains other than localhost must use TLS for this option to work.
This is due to browser restrictions: [https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#none](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#none)
Disable Multi-factor (MFA) Prompt [#disable-multi-factor-mfa-prompt]
To encourage the users to more security for their accounts, a multi-factor prompt is shown after a certain time, to prompt them to configure an additional factor.
This prompt is shown even if multi-factor is not enforced for the users.
If you do want to disable the prompt for your users, go to the login behavior settings and set the "Multifactor Init Lifetime" to 0.
If the setting is not configured to 0, it means that after that time, the user will be asked again to setup a factor.
# Domain Discovery in ZITADEL
This guide should explain how domain discovery works and how to configure it in ZITADEL.
Overview [#overview]
Domain discovery is typically used in [B2B](./b2b) or [SaaS](./saas) scenarios where you have users from different organizations and you want to route them according to their login methods, which could be a user name or, depending on your settings, also an [email / phone number](./configurations#use-email-to-login).
In the example there is a service provider with a ZITADEL instance running on a [Custom Domain](/guides/manage/cloud/instances#add-custom-domain) on `login.mycompany.com`.
By default, all users login on the organization **CIAM** with their preferred social login provider.
Users of the two business customers **Alpha** and **Beta** should log in, according to their organization login and access policy settings.
In the case of Alpha, users will log in via an external identity provider (eg, [Entra ID](/guides/integrate/identity-providers/azure-ad-oidc)).
Beta users must only log in with username/password and MFA instead.
For this scenario you need to route the user `alice@alpha.com` to the **Alpha Organization** and `bob@beta.com` to the **Beta Organization** respectively.
Follow this guide to configure your ZITADEL instance for this scenario.
Instance [#instance]
Default Login Page [#default-login-page]
You will use the instance default settings for the login for the organization **CIAM**.
When opening `login.mycompany.com` then the login policy of the instance will be applied.
This means that you have to set up the [Login and Access](/guides/manage/console/default-settings#login-behavior-and-security) Policy and [Identity Providers](/guides/manage/console/default-settings#identity-providers) for the **CIAM** users on the instance itself.
You can also configure these settings on the default organization (see below) and send the scope `urn:zitadel:iam:org:id:{id}` with every [auth request](https://zitadel.com/playgrounds/oidc).
Default Organization [#default-organization]
Set **CIAM** as [default organization](/guides/manage/console/organizations-overview#default-organization).
You will find the overview of all organizations under the "Organizations" tab on the Default Settings.
The default organization will hold all unmatched users, ie. all users that are not specifically in the organizations **Alpha** or **Beta** in the example.
Enable Domain Discovery [#enable-domain-discovery]
In the [Login Behavior and Security Settings](/guides/manage/console/default-settings#login-behavior-and-security) enable "Domain discovery allowed"
Configure login with email [#configure-login-with-email]
Follow this [settings guide](/guides/solution-scenarios/configurations#use-email-to-login) to allow users to login with their email address.
Other considerations [#other-considerations]
You can also have multiple Custom Domains pointing to the same instance as described in this [settings guide](/guides/solution-scenarios/configurations#custom-application-domain-per-organization). In our example you could also use `alpha.mycompany.com` to show the login page of your instance.
The domain of your email notification can be changed by [setting up your SMTP](/guides/manage/console/default-settings#smtp).
Organization [#organization]
Alpha organization [#alpha-organization]
Users of **Alpha** should only be allowed to authenticate with their company's identity provider.
In the organization settings under Login Behavior and Access make sure the following settings are applied:
* **Username Password allowed**: Disabled
* **Register allowed**: Disabled - we will set this up on the external identity provider
* **External IDP allowed**: Enabled
Now you can set up an [external identity provider](/guides/manage/console/default-settings#identity-providers).
Given you have only one external identity provider set up, when a user tries to login on that organization, then the user will be automatically redirected to the external identity provider.
In case multiple providers are set up, then the user will be prompted to select an identity provider.
Beta organization [#beta-organization]
Users of **Beta** must create an account and login with password and 2FA.
In the organization settings under `Login Behavior and Access`, make sure the following settings are applied:
* **Username Password allowed**: Enabled
* **Register allowed**: Disabled - you may want [Administrators](/concepts/structure/administrators) to set up accounts.
* **External IDP allowed**: Disabled
Make sure to [Force MFA](/guides/manage/console/default-settings#multifactor-mfa) so that users must set up a second factor for authentication.
Verify domains [#verify-domains]
Switch to the organization **Alpha** and navigate to the settings and "Organization Domains".
Verify the domain alpha.com following the [organization guide](/guides/manage/console/organizations-overview#usernames-and-domains).
Do the same for the **Beta** organization.
You can also disable domain verification with acme challenge in the [default settings](/guides/manage/console/default-settings#domain-settings).
Conclusion [#conclusion]
You should be all setup to try out domain discovery.
The user journeys for the different users would look as follows:
* User (Alice, Bob, Chuck) clicks a login button in your application
* Redirected to `login.mycompany.com` (ZITADEL running under a Custom Domain)
Chuck
1. Select the Google button
2. Redirect to Google IDP
3. Chuck logs in with Google credentials
4. Redirected back to your application
Alice
1. Alice enters [alice@alpha.com](mailto:alice@alpha.com) and clicks next
2. Redirect to Entra ID Tenant (or any other IDP)
3. Alice logs in with her company credentials
4. Redirected back to your application
Bob
1. Bob enters [bob@beta.com](mailto:bob@beta.com) and clicks next
2. Bob will be redirected to a login with the branding of beta.com
3. Bob enters his password and MFA on the login screen
4. Redirected back to your application
# Frontend and Back-end API Communication in ZITADEL
This guide contains a use case and ZITADEL integration.
In a typical web application architecture, the front-end and back-end communicate to exchange data and provide functionality to users. Let's consider a use case where a front-end application needs to communicate with a back-end API using secure authentication and authorization. Let’s explore how ZITADEL can be used to add front-end login and facilitate this communication.
Single-Page Applications (SPAs) are web applications that run entirely in the browser, without a back-end server. In ZITADEL SPAs should use the Authorization Code Grant with PKCE or the Implicit Grant (if PKCE is not feasible) to obtain an access token.
While APIs are vital for communication between applications and services, they don't directly participate in user authentication. Instead, they often authorize client requests based on access tokens issued by an authorization server. APIs in ZITADEL use grant types like JWT Profile or Basic Authentication to access the authorization server's introspection endpoint for token validation.
A real-world scenario [#a-real-world-scenario]
Suppose there is a news portal web app that allows users to browse through various news articles and personalize their news feed based on their preferences. The back-end API handles fetching and delivering news content from the database.
**Front-End Login**: A user visits the news portal and opts to log in for a personalized news feed. They are redirected to the Identity Provider’s (IdP) login page, where they authenticate with their credentials. Upon successful authentication, the IdP issues access and ID tokens to the front-end app.
**Back-End API Communication**: When the user browses through the news feed, the front-end app makes an API request to the back-end, including the access token in the Authorization header. The back-end API, upon receiving the request, uses the IdP’s introspection endpoint to validate the access token. Once validated, it fetches personalized news data based on the user's preferences from the news database.
While it is true that the back-end API typically needs to authenticate with the IdP, in this specific use case, the back-end API can work with a client credential / JWT since it's not a public client. This means that instead of relying on user-specific authentication, the back-end API can obtain a client credential (such as a client ID and client secret) from the IdP to authenticate itself and validate the access token received from the front-end app. This approach ensures secure communication between the front-end app, the back-end API, and the IdP, while still allowing the back-end API to access user-specific data and provide personalized news feeds.
A simplified example with a React frontend and a Python Flask API [#a-simplified-example-with-a-react-frontend-and-a-python-flask-api]
In this example, the application is a web-based quote generator that employs a secure user authentication system via ZITADEL. The functionality of the app is outlined below:
1. Upon starting, the application provides a login button
2. The user is then redirected to ZITADEL's login page to enter their credentials.
3. Once the login is successful, the application then greets the user by extracting the user's name, thus providing a personalized experience.
4. The application presents an option for the user to generate a quote via a button. Upon pressing this button, the front-end application communicates with the back-end API using the user's access token received from ZITADEL.
5. The back-end API introspects the access token for validity using ZITADEL's introspection endpoint. If the token is valid, the API generates a quote and sends it as a response to the front-end application, which is then displayed to the user in their browser.
Setting up the applications and ZITADEL [#setting-up-the-applications-and-zitadel]
All code and instructions to run the sample application can be found at [https://github.com/zitadel/example-quote-generator-app/](https://github.com/zitadel/example-quote-generator-app/). You can also find the steps for the integration between the front-end, back-end API, and ZITADEL in the README.md.
You can create the front-end application (User Agent) and the API in the same project or in a different project. In this example, we have created both in one. Configure the applications with appropriate settings (as instructed).
Front-end login with ZITADEL [#front-end-login-with-zitadel]
* You must create a User Agent application in your project to add login to your React application using the Authorization Code with PKCE flow. This allows the front-end application to integrate with ZITADEL to enable user authentication and authorization.
* In the React front-end application, configure the ZITADEL OIDC client settings, including the client ID, ZITADEL URLs, redirect URIs, and required scopes.
* Implement the login flow, authentication callbacks, and token handling logic in the front-end application.
* When a user visits the front-end application, they are presented with a login option.
* Upon clicking the login button, the frontend initiates the Authorization Code with PKCE authentication flow and redirects the user to the ZITADEL login page. The user enters their credentials and authenticates with ZITADEL. Authorization Code Flow returns an authorization code to the client application, which can then exchange it for an ID token and an access token directly. This provides the benefit of not exposing any tokens to the user agent and possibly other malicious applications with access to the user agent.
* You must set up the required scopes and claims to ensure the front-end and back-end can exchange data securely. It’s important to note that when specifying the scope when calling the token API, the scope must contain the project ID of the ZITADEL project in which the API resides (to enable token validation by the back-end API):`scope:'openid profile email urn:zitadel:iam:org:project:id::aud'`
* Also, we want to include user info inside the token to avoid calling the user info endpoint, so go to Token Settings in the front-end app and select User Info inside ID Token.
* After a successful authentication, ZITADEL generates an access token and an ID token.
* The front-end application receives these tokens and stores them securely (e.g., in browser storage).
Token exchange and user information [#token-exchange-and-user-information]
* Once the frontend obtains the tokens, it can extract certain information from the ID token itself (e.g., user ID, email, etc.) without making an additional request.
* If more user information is required, the frontend can use the access token to call the ZITADEL User Info endpoint. This endpoint provides additional user details, such as name, profile picture, etc.
Back-end API communication [#back-end-api-communication]
* To communicate with the back-end API, the front-end includes the access token in the Authorization header of API requests.
* The back-end API receives the request and needs to validate and authorize the token before processing the request.
* The API performs token introspection using ZITADEL's introspection endpoint to validate the access token. This API uses Basic Authentication to invoke the [introspection endpoint](/apis/openidoauth/endpoints#introspection-endpoint), which means it sends its client ID and client secret along with the access token received.
* If the token is valid and active, the API proceeds to handle the requested action or fetch data from the underlying data sources.
# Guest users authentication
In certain scenarios, requiring users to create an account *before* they can interact with core features of your product often leads to increased drop-off rates. Whether it is an e-commerce platform requiring authentication to add items to a cart, or a SaaS application requesting an email before granting dashboard access, registration barriers can negatively impact user acquisition.
At the same time, backend microservices generally require cryptographically signed JSON Web Tokens (JWTs) from an Identity Provider (IdP) to authorize and secure these initial interactions.
**How do you issue a valid token to an anonymous guest without introducing a login barrier?**
The solution is the **Guest Account** (or Shadow Account) architecture.
What is a Guest Account? [#what-is-a-guest-account]
A Guest Account is a temporary identity provisioned for a user the moment they perform a high-intent action within your application, for example, adding items to the cart.
Instead of treating anonymous users as "unauthenticated" entities, the application silently creates a temporary profile for them in ZITADEL. The backend then impersonates this temporary user, generating a valid authentication token that is stored securely on the client side.
From the user's perspective, they are browsing anonymously. To the backend architecture, they operate as an authenticated user with a valid JWT.
The Benefits of this Approach [#the-benefits-of-this-approach]
Implementing guest authentication utilizing ZITADEL provides distinct advantages for both user experience and system architecture:
* **Increased Engagement:** Users can interact with the application immediately. Explicit registration is deferred until a transactional commitment is required (e.g., during checkout).
* **Unified Security Model:** Backend APIs do not require bifurcated authorization logic (e.g., `if (isGuest)`). Every request, regardless of the user's registration state, is authenticated via a cryptographically signed OIDC token.
* **Seamless Data Retention:** Because the guest session is bound to a persistent underlying ID, all actions (such as populating a cart or setting preferences) are stateful. Upon formal registration, data migration is unnecessary; the system simply updates the existing identity record with explicit credentials.
* **Automated Lifecycle Management:** By appending a timestamp via a `metadata` attribute upon creation, the IdP can systematically identify and purge abandoned guest accounts using scheduled batch jobs, mitigating database bloat.
***
How It Works: The Implementation Flow [#how-it-works-the-implementation-flow]
Implementing this with ZITADEL is efficient because the platform natively supports **OAuth 2.0 Token Exchange (RFC 8693)** and **User Impersonation**.
Here is the high-level lifecycle of how a guest account moves from an anonymous action to a fully registered user:
1\. Silent Provisioning (The Anonymous Intent) [#1-silent-provisioning-the-anonymous-intent]
An unauthenticated user performs an action such as "Add to Cart." The backend intercepts this request and, utilizing a privileged **ZITADEL Service Account**, calls the ZITADEL API to silently provision a temporary user record.
* *Mechanism:* The system appends a `metadata` key (e.g., `GUEST_26_02_27`) to the user profile. This attribute flags the account for future garbage collection if the session is abandoned.
2\. Token Exchange & Impersonation [#2-token-exchange-impersonation]
Rather than generating a localized session token, the backend requests a standard token from ZITADEL. It executes an **OAuth 2.0 Token Exchange** to trade its Service Account credentials for an **Impersonation Token** representing the newly created guest.
* *Mechanism:* The frontend receives a standard, valid JWT secured in an `httpOnly` cookie. The user proceeds with their workflow without interruption.
3\. The Upgrade Path (Frictionless Registration) [#3-the-upgrade-path-frictionless-registration]
When the user initiates formal registration, the system bypasses standard account creation. Instead, the application updates the existing guest profile in ZITADEL with explicit credentials (Name, Email, and Password).
* *Mechanism:* The `GUEST` metadata tag is deleted, protecting the account from lifecycle cleanup scripts. The temporary identity is seamlessly converted into a permanent account, retaining all historical state.
4\. Edge Case Handling: Cart Merging [#4-edge-case-handling-cart-merging]
If a guest user authenticates using a *pre-existing* account rather than registering a new one, the application detects a discrepancy between the `sub` claim in the guest cookie and the newly authenticated identity.
* *Mechanism:* The backend merges the local guest data (e.g., cart items) into the permanent user's database record. Subsequently, it issues a `DeleteUser` API call to ZITADEL to terminate the orphaned guest account.
***
Dive into the Code [#dive-into-the-code]
We have developed a complete, open-source Next.js application that demonstrates this exact architecture end-to-end, including NextAuth.js configuration, ZITADEL API integrations, and data merging logic.
👉 **[Explore the GitHub Repository](https://github.com/zitadel/zitadel-guest-accounts.git)**
The repository's `README.md` contains a comprehensive step-by-step guide on configuring your ZITADEL instance (enabling PKCE, Token Exchange, and Impersonation roles) alongside the exact code snippets required to execute the silent provisioning and token exchange flows.
# Solution Scenarios
Learn how to use ZITADEL for different scenarios.
* [B2B](/guides/solution-scenarios/b2b)
* [B2C](/guides/solution-scenarios/b2c)
* [SaaS](/guides/solution-scenarios/saas)
# Prevent users from accessing ZITADEL Management Console
ZITADEL includes a management console that allows Administrators to configure all resources. All uses, including end-users, by default, view and manage their profile information.
In some use cases you want to prevent users from accessing the management console, this could be:
* User management is integrated into your own app
* Users login via SSO and should not be able to change their data in the Management Console
* Only administrators should be able to access Management Console to manage their users
At the moment it is not possible to simply disable the Management Console, but with the settings described in this guide, you can prevent users from accessing the Console.
Self-hosted [#self-hosted]
If you are running ZITADEL self-hosted, we recommend restricting the access to ZITADEL Management Console via WAF/reverse-proxy for Non-Administrator users
ZITADEL Cloud (and self-hosted) [#zitadel-cloud-and-self-hosted]
Default redirect url [#default-redirect-url]
One goal is to never send the end user to the ZITADEL management console.
This does make sense if you build your own user profile page within your application.
In that case you probably want to redirect the user to your own application, instead of to the console.
Read more about how to set the default redirect URI: [Settings - Default Redirect URI](/guides/manage/console/default-settings#default-redirect-uri)
Restricting Management Console in default-project [#restricting-management-console-in-default-project]
With this workaround, you will limit users from accessing the [default-project](/guides/manage/console/projects-overview#the-default-project) if they are not explicitly granted to use this project.
When setting the "Check for Project on Authentication" setting on a project, only users of organizations with a grant to that project can access it.
By default, this setting is disabled, so all users can access the project.
Start by granting the organization of your administrators that should be able to access the Management Console, the [default project](/guides/manage/console/projects-overview#the-default-project):
1. Click on the default-project > Grants > New +
2. On the next screen select the organization to which you want to give the grant
3. You can skip the screen with the role selection and click save
4. Make sure the grant is in now in the overview and marked as active.
To avoid accidental lock-out, the [default project](/guides/manage/console/projects-overview#the-default-project) hides the checkbox "Check Project on Authentication" in the Management Console:
You need to use the [update project API call](/reference/api/management/zitadel.management.v1.ManagementService.UpdateProject) to set the settings on the project.
First, you need a user with administrator permissions to change the project settings in that organization.
This means either you add the manager to the organization or you use an administrator on the instance level with "IAM-OWNER" permissions.
After that create a Personal Access Token (PAT) for the administrator.
More detailed information about creating a PAT and administrator roles you can find [here](/guides/integrate/service-accounts/personal-access-token)
Then you have to send the following request:
```bash
curl -L -X PUT "https://${CUSTOM_DOMAIN}/management/v1/projects/${PROJECT_ID}" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H "Authorization: Bearer ${PAT}" \
--data-raw '{
"name": "ZITADEL",
"projectRoleAssertion": false,
"projectRoleCheck": false,
"hasProjectCheck": true,
"privateLabelingSetting": "PRIVATE_LABELING_SETTING_UNSPECIFIED"
}'
```
Where
* `${CUSTOM_DOMAIN}` is your Custom Domain, f.e. mydemo.zitadel.cloud
* `${PAT}` is your service account's personal access token
* `${PROJECT_ID}` is the default-project's ID that can be found in Management Console see screenshot above
You should now be able to log in with users of the organization that have the grant (in this example users of the organization "Customer-A").
All other users should see the following error message after authentication:
# SaaS Authentication and Authorization with ZITADEL
This is an example architecture for a typical SaaS product.
To illustrate it, a fictional organization and project is used.
Example Case [#example-case]
The Timing Company has a product called Time.
They have two environments, the development and the production environment.
In this case Time uses authentication and authorizations from ZITADEL.
This means that the users and also their authorizations will be managed within ZITADEL.
Organization [#organization]
An organization is the ZITADEL resource that contains users, projects, applications, policies and so on.
The organization manages the projects and users.
You need at least one organization for your own company, in our case "The Timing Company."
Your next step is to create an organization for each of your customers.
Project [#project]
The idea of projects is to have a vessel for all components who are closely related to each other.
In this use case, we would have two different projects, one each for the Development and Production environments.
Let us call them "Time Dev" and "Time Prod". These projects should be created in "The Timing Company" organization.
In the project you will configure all your roles and applications (clients and APIs).
Project Settings [#project-settings]
Enable the `Only authorized users can authenticate` setting on the project to restrict authentication to users with at least one role assigned to this project.
Project Grant [#project-grant]
To grant a customer permission to a project, a project grant to the customer's organization is needed (search the granted organization by its domain).
It is also possible to delegate only specific roles of the project to a certain customer.
As soon as a project grant exists, the customer will see the project in the granted projects section of their organization and will be able to assign roles to their own users to the given project.
Role Assignments [#role-assignments]
To give a user permission to a project, a role assignment is needed.
All organizations that own projects or received a grant can assign roles to users to have access to projects.
It is also possible to assign roles to users outside the own company if the exact login name of the user is known.
Project Login [#project-login]
There are some different use cases how the login should behave and look like:
1. Restrict Organization
With the Organization Domain scope the organization will be restricted to the requested domain, this means only users of the requested organization will be able to log in.
The private labeling (branding) and the login policy of the requested organization will be set automatically.
More about the [Scopes](/apis/openidoauth/scopes)
2. Show private labeling (branding) of the project organization
You can configure on project-level which branding should be shown to users.
In the default the design of the instance will be shown, but as soon as the user is identified, the policy of the users organization (if specified) will be triggered.
If the setting is set to `Ensure Project Resource Owner Setting`, the private labeling of the project organization will always be triggered.
The last possibility is to show the private labeling of the project organization, and as soon as the user is identified, the user organization settings will be triggered.
For this the Allow User Resource Owner Setting should be set.
More about [Private Labeling](/guides/manage/customize/branding)
# Acceptable Use Policy
Last updated on November 15, 2023
This policy is an annex to the [Terms of Service](../terms-of-service) and clarifies your obligations while using our Services.
Use [#use]
You will ensure that the use of our Subscription Services and Website by yourself, your customers, or third parties comply with all applicable legislation.
You may not:
1. Use Subscription Services or Website for phishing, social engineering, or committing fraud or any other illegal, malicious or fraudulent activity
2. Attempt to interfere with the functionality or proper working of the Subscription Services or Website
3. Upload any materials to the Subscription Services or Website in violation of any third-party privacy or data protection rights, to store and transmit any kind of malware
4. Attempt to probe, scan, penetrate or test the vulnerability of our Subscription Services, Website, systems, or network or try to circumvent our authentication. Any penetration testing must not be conducted without prior written consent by CAOS.
5. Use any organization or domain name that includes or is confusingly similar with trademarks, or any third parties. CAOS may determine any violation at its sole discretion
6. Collecting any information about our Customers, our Customers users, or our users without the consent of the person identified. This includes phishing, social engineering, scamming, spidering or harvesting information from any Subscription Service or Website
7. Use Actions to run workloads that are unrelated to the Subscription Services and Websites, such as excessively calling unrelated third party services, crypto mining, intentionally long running code
Fair use principles [#fair-use-principles]
The “fair use” principle applies to the use of our services.
We optimize our infrastructure in such a way that sufficient capacity is available to you even during short-term increased demand (“peaks”) and implement mitigation measures such as our [Rate Limit Policy](./rate-limit-policy).
You are nonetheless required to adhere to reasonable use of our resources in order to avoid negatively affecting the services for other customers.
You agree that we may delete any data on our systems or networks, if CAOS believes that this data may corrupt our systems, interfere or may compromise other customers' data.
You agree to adhere to the following fair use limits:
* Usage limits that were agreed by both parties in advance for the duration of the term
Violations of this policy [#violations-of-this-policy]
We may suspend or terminate your usage of our Services for any violation of this Acceptable Use Policy.
You will not be entitled to any Financial Credit or compensation for any interruptions caused by violation of this policy.
# ZITADEL Account Lockout Policy
Last updated on June 25, 2025
This policy is an annex to the [Terms of Service](../terms-of-service) and outlines your responsibilities, as well as our procedures, for handling situations where you are unable to access your ZITADEL Cloud services or data.
It applies specifically to cases where **ZITADEL** must restore your access to services that are otherwise operational, and does **not** cover service outages or unavailability.
Why do we have this policy? [#why-do-we-have-this-policy]
Users may lose access to ZITADEL services due to lost credentials or settings misconfiguration.
In some cases, it may not be possible to recover access through self-service options—for example, losing access to 2FA credentials or being unable to reverse a settings misconfiguration. These situations may require support from our team to help you regain access to your data.
To assist with such requests, we will require specific information and may request additional details throughout the process.
**ZITADEL reserves the right to decline any access recovery request without providing a reason if the required information cannot be verified or provided.**
Scope of This Policy [#scope-of-this-policy]
This policy applies to the following situations:
* Loss of access to your **ZITADEL Cloud Admin Account** (customer portal)
* Inability to access **Instance Manager accounts** for a specific instance
* Need to **undo settings changes** that caused a lockout (e.g., a misconfigured Action)
Out of Scope [#out-of-scope]
The following types of access recovery requests are **not** covered by this policy:
* Situations where you can request access from another **Admin** or **Instance Manager**
* Requests made by **end-users** who should instead contact their Admin or Manager
* Issues related to **self-hosted ZITADEL instances**
* **Free accounts/Instances**
Process [#process]
Before submitting a request to restore access to your account, please ensure that you are unable to regain access through your existing **Manager** or **Admin**, or by contacting another **Manager/Admin** within your organization.
ZITADEL Cloud account (Customer Portal) [#zitadel-cloud-account-customer-portal]
Please visit the [support page in the customer portal](https://zitadel.com/admin/support):
* State clearly in the subject line that this is related to an account lockout for a ZITADEL Cloud account
* The sender's email address must match the verified email address of the account owner
* State the reason why you're not able to recover the account yourself
Please allow us time to validate your request.
Our support team will follow up with additional verification steps if needed.
Instance Manager access recovery [#instance-manager-access-recovery]
If you need to recover a Manager account to an instance, please make sure you can't recover the account via another user or service account with Manager permissions.
Please visit the [support page in the customer portal](https://zitadel.com/admin/support):
* State clearly in the subject line that this is related to an account lockout **for** the affected instance
* The sender's email address must match the verified email address of the affected instance manager
* State the reason why you're not able to recover the account yourself
Please allow us time to validate your request.
Our support team will follow up with additional verification steps if needed.
# Use of brands and trademarks
Last updated on November 15, 2023
This policy is an annex to the [Terms of Service](../terms-of-service) that clarifies how you may use our brands and trademarks under fair use.
We reserve the right to object to any use or misuse of brands and trademarks in any jurisdiction worldwide.
If you are unsure about the use of our logos, please contact [legal@zitadel.com](mailto:legal@zitadel.com).
Conditions [#conditions]
ZITADEL's brand assets and trademarks are proprietary assets owned exclusively by us.
No third party may claim ownership over brand assets and trademarks or brands and trademarks that are confusingly similar. This extends to all trademarks in image, text-form, combined image and text, visual, and audio.
You must not include our brands and trademarks in the name of your product or service whether commercial or non-commercial, this includes, but is not limited to, websites, blogs, informational, advertising, and personal home pages, applications.
Spelling [#spelling]
When referring to ZITADEL, please make sure it is spelled correctly and written in uppercase letters.
Logo usage [#logo-usage]
When embedding our logo, always use the official version.
You must not alter the logo in any way, and avoid overlapping with other images.
To ensure the logo is used as intended, we provide specific examples below and reserve the right to object to any use or misuse.
Fair use [#fair-use]
* Use in architecture diagrams without implying affiliation or partnership
* Editorial and informational purposes such as blog posts or news articles
* Linking back to our [website](https://zitadel.com), official [repositories](https://github.com/zitadel), or [documentation](https://zitadel.com/docs)
* Indicating that the software is available for use or installation without implying any affiliation or endorsement
Not acceptable [#not-acceptable]
* Using our brands and trademarks, including our logo, or any variations for your own product or services
* Modification of our brands and trademarks
* Integration of our brands and trademarks into your own brands and trademarks
* Suggesting affiliation, endorsement, or partnership without our consent
# Feature Development Policy
Last updated on September 25, 2023
This policy clarifies how we handle requests for feature prioritization and development. This policy is applicable to situations where a user wants to prioritize certain features or development for our products and services.
Why to do we have this policy? [#why-to-do-we-have-this-policy]
Shaping of the roadmap and prioritization of features is done by ZITADEL.
You are welcome to give feedback on the [roadmap](https://zitadel.com/roadmap), and we are happy to [accept upstream pull requests](https://github.com/zitadel/zitadel/CONTRIBUTE.md) for ZITADEL.
In case you can't contribute directly to the open source version of ZITADEL, but want to accelerate development, we may develop the feature on request, under the conditions laid out in this policy.
Features that will be part of the publicly available ZITADEL [repositories](https://github.com/zitadel/), ZITADEL Cloud, or optional components to our products that might be available only under a commercial license.
Conditions [#conditions]
All intellectual property and ownership of the changes remain with ZITADEL.
Changes may be published under an Open Source or commercial license.
ZITADEL will guarantee maintenance and warranties according to the license and terms of services under which the changes are being released.
Process [#process]
1. [Contact us](https://zitadel.com/contact) about your feature request
2. We will share the scope of the feature and acceptance criteria with you
3. As soon as you confirm the scope we will provide you with a quote containing scope, timeline, estimated effort, and price for the feature development
4. When you accept 50% of the cost is due
5. We inform you when the feature is available and provide you with instructions on how to conduct the acceptance tests
6. You will need to report any issues within 14 days, if not otherwise agreed (see below)
7. On completion (see below) the remaining cost is due
Development cost [#development-cost]
We will estimate the overall effort and development cost of the changes.
For features that are not on the roadmap or feature requests where a defined deadline for general availability of the feature must be met, we take 100% of the development cost as base.
For prioritization of a features that are already on the roadmap, we take 50% of the development cost as base.
We will provide you with a quote for fixed price of the overall effort and ZITADEL will carry any overages.
Cost, scope and timeline will be agreed in a formal quote.
Payment [#payment]
50% of the cost will be due on accepting the quote.
We will send you an invoice and expect payment within the given deadline.
50% on completion of the feature development.
Completion means that the agreed scope is available according to the agreed acceptance criteria.
You had 14 days to verify the acceptance criteria and report any issues.
A feature is considered complete, if the outstanding issues are being solved, or a timeline for resolution of the issues has been mutually agreed, or if we haven't got any response within the last 14 days.
# Policies
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
# Privacy Policy
Last updated on October 23, 2025.
This privacy policy describes how Zitadel, Inc. and its wholly owned subsidiaries and affiliates (collectively, "**Zitadel**", "**CAOS**", "**we**" or "**us**") collect, use, disclose and otherwise process your personal data in connection with the management of our business and our relationships with customers, visitors and event attendees.
This privacy policy explains your rights and choices related to the personal data we collect when:
* You interact with our websites, including zitadel.com, zitadel.cloud and zitadel.ch as well any other websites that we operate and that link to this privacy policy (our "**Sites**")
* You visit, interact with, or use any of our offices, events, sales, marketing or other activities; and
* You use our platform, including Zitadel and our software, mobile application, and other products and services (the "**Services**").
This privacy policy does not cover:
* **Organizational Use**. When you use our Services on behalf of an organization (your employer), your use is administered and provisioned by your organization under its policies regarding the use and protection of personal data. If you have questions about how your data is being accessed or used by your organization, please refer to your organization's privacy policy and direct your inquiries to your organization's system administrator.
* **Third Parties**. Our Sites include links to websites and/or applications operated and maintained by third parties (e.g. GitHub, LinkedIn, etc.). This privacy policy does not apply to any products, services, websites, or content that are offered by third parties and/or have their own privacy policy.
If any inconsistencies arise between this privacy policy and the otherwise applicable contractual terms, framework agreement, or general terms of service, the provisions of this privacy policy shall prevail (where applicable). This privacy policy covers both existing personal data and personal data which may be collected from you in the future.
Zitadel determines the purposes for and means of the processing (i.e., we are the data controller) of your personal data as described in this privacy policy, unless expressly specified otherwise. The responsible party for the data processing described in this privacy policy and contact for questions and issues regarding data protection is:
**Zitadel, Inc.**
Data Protection Officer
1 Embarcadero Center
Suite 1200
San Francisco, CA 94111-4164
United States of America
[legal@zitadel.com](mailto:legal@zitadel.com)
**CAOS AG (Affiliate of Zitadel, Inc.)**
Data Protection Officer
Lerchenfeldstrasse 3
9014 St. Gallen
Switzerland
[legal@zitadel.com](mailto:legal@zitadel.com)
Our representative in the EU is
**VGS Datenschutzpartner GmbH**
Am Kaiserkai 69
20457 Hamburg
Germany
[info@datenschutzpartner.eu](mailto:info@datenschutzpartner.eu)
General notes [#general-notes]
Based on Article 13 of the Swiss Federal Constitution and the data protection provisions of the Swiss Confederation (Data Protection Act, DSG), every person has the right to protection of their privacy as well as protection against misuse of their personal data. The operators of these websites and services take the protection of your personal data very seriously. We treat your personal data confidentially and in accordance with the legal data protection regulations as well as this data protection declaration.
In cooperation with our suppliers, we make every effort to protect the databases and any of our users data as well as possible against unauthorized access, loss, misuse or falsification. We point out that data transmission over the internet in general may result in security risks. A complete protection of the data against access by third parties is not possible.
This website uses TLS encryption for security reasons and to protect the transmission of confidential content, such as requests that you send to us as the website operator. You can recognize an encrypted connection by the fact that the address line of the browser changes from "http\://" to "https\://".
Data Privacy Framework (DPF) Adherence Statement [#data-privacy-framework-dpf-adherence-statement]
Zitadel complies with the EU-U.S. Data Privacy Framework (EU-U.S. DPF), the UK Extension to the EU-U.S. DPF, and the Swiss-U.S. Data Privacy Framework (Swiss-U.S. DPF) as set forth by the U.S. Department of Commerce.
Zitadel has certified to the U.S. Department of Commerce that it adheres to the EU-U.S. Data Privacy Framework Principles (EU-U.S. DPF Principles) with regard to the processing of personal data received from the European Union in reliance on the EU-U.S. DPF and from the United Kingdom (and Gibraltar) in reliance on the UK Extension to the EU-U.S. DPF.
Zitadel has also certified to the U.S. Department of Commerce that it adheres to the Swiss-U.S.
Data Privacy Framework Principles (Swiss-U.S. DPF Principles) with regard to the processing of personal data received from Switzerland in reliance on the Swiss-U.S. DPF.
If there is any conflict between the terms in this privacy policy and the EU-U.S. DPF Principles and/or the Swiss-U.S. DPF Principles, the Principles shall govern.
To learn more about the Data Privacy Framework (DPF) program, and to view our certification, please visit [https://www.dataprivacyframework.gov/](https://www.dataprivacyframework.gov/)
Transfers of Employee Data [#transfers-of-employee-data]
Zitadel also commits to apply the DPF Principles to personal data of its employees collected in the context of their employment relationship with zitadel, where such data is transferred from the EU, UK, or Switzerland to the United States. In this regard, Zitadel commits to cooperate with the EU data protection authorities (DPAs), the UK Information Commissioner's Office (ICO) (and the Gibraltar Regulatory Authority (GRA)), and the Swiss Federal Data Protection and Information Commissioner (FDPIC) and comply with the advice given by these authorities with regard to such data.
Processing of personal data, legal basis, storage period [#processing-of-personal-data-legal-basis-storage-period]
**Personal data** is any information that relates to an identified or identifiable person. A **data subject** is a person about whom personal data is processed. Processing includes any handling of personal data, regardless of the means and procedures used, in particular the storage, disclosure, acquisition, deletion, storage, modification, destruction and use of personal data.
We process personal data in accordance with Swiss data protection law. In addition, we process - to the extent and insofar as the EU Data Protection Regulation is applicable - personal data in accordance with the following legal bases within the meaning of Art. 6 (1) DSGVO:
* Insofar as we obtain the consent of the data subject for processing operations, Art. 6 (1) a) DSGVO serves as the legal basis.
* When processing personal data for the fulfillment of a contract with the data subject as well as for the implementation of corresponding pre-contractual measures, Art. 6 para. 1 lit. b DSGVO serves as the legal basis.
* To the extent that processing of personal data is necessary to comply with a legal obligation to which we are subject under any applicable law of the EU or under any applicable law of a country in which the GDPR applies in whole or in part, Art. 6 para. 1 lit. c GDPR serves as the legal basis.
* For the processing of personal data in order to protect vital interests of the data subject or another natural person, Art. 6 para. 1 lit. d DSGVO serves as the legal basis.
* If personal data is processed in order to protect the legitimate interests of us or of third parties and if the fundamental freedoms and rights and interests of the data subject do not override our interests and the interests of third parties, Article 6 (1) (f) of the GDPR serves as the legal basis. Legitimate interests are in particular our business interest in being able to provide our website and our products, information security, the enforcement of our own legal claims and compliance with Swiss law.
We will retain personal data for the period of time necessary for the particular purpose for which it was collected and where we have an ongoing legitimate business need to do so (for example to comply with applicable legal, tax or accounting requirements). Subsequently, they are either deleted or made anonymous, unless we need them for a longer period of time in exceptional cases, e.g. due to legal storage and documentation obligations or our legitimate interests, such as the protection of rights to which we are entitled or the defense of claims.
Processing of personal data when using the website, contact forms and in connection with newsletters [#processing-of-personal-data-when-using-the-website-contact-forms-and-in-connection-with-newsletters]
Our websites can generally be visited without registration. Each time one of our website is requested, data such as content of the requested page, name of the requested file, IP address, date and time are automatically stored in log files on the server.
This data is processed to enable correct delivery and functioning of the website. In addition, we use the data to optimize the website and to ensure the security of our systems.
Personal data, in particular name, address or e-mail address are collected as far as possible on a voluntary basis, for example when you contact us via a contact form or by e-mail. Without your consent, the data will not be passed on to third parties, unless otherwise stated in this privacy policy.
If you send us inquiries via contact form, your data from the form, including any data you provided, will be stored by us for the purpose of processing the inquiry and in case of follow-up questions. We do not pass on this data without your consent, except insofar as this is shown in this privacy policy.
If you would like to receive newsletters offered on our Sites, we require an e-mail address from you as well as information that allows us to verify that you are the owner of the specified e-mail address and agree to receive the newsletter. Further data will not be collected. We use this data exclusively for sending the requested information and do not pass it on to third parties, except as described in this privacy policy.
You can revoke your consent to the storage of the data, the e-mail address and their use for sending the newsletter at any time, for example via the "unsubscribe link" in the newsletter.
Processing of personal data when applying for a job with us [#processing-of-personal-data-when-applying-for-a-job-with-us]
Our Sites can generally be visited without registration. If you apply for a job with us, we may collect and process according to the [Privacy policy for the Zitadel employer branding and recruitment](https://jobs.zitadel.com/privacy-policy). You may request and delete your data with the links on our [data & privacy page](https://jobs.zitadel.com/data-privacy).
Processing of personal data in connection with the use of our Services [#processing-of-personal-data-in-connection-with-the-use-of-our-services]
The use of our services is generally only possible with registration. During registration and in the course of using the services, we collect and process various personal data.
In particular, the following personal data are part of the processing:
Unless otherwise mentioned, the nature and purpose of the processing is as follows:
The data is uploaded by customers in our Services or collected by us based on requests from users. The personal data is processed by us exclusively for the provision of the requested Services or the use of the agreed Services.
The fulfillment of the contract includes in particular, but is not limited to, the processing of personal data for the purpose of:
* Authentication and authorization of users
* Storage and processing of user actions in the audit trail
* Processing of personal data and login information
* Verification of communication means
* Communication regarding service interruptions or service changes
Disclosure to third parties [#disclosure-to-third-parties]
Third party sub-processors [#third-party-sub-processors]
We use third-party services to provide the website and our offers. An up-to-date list of all the providers we use and their areas of activity can be found on our [Trust Center](https://zitadel.com/trust).
External payment providers [#external-payment-providers]
This Site uses external payment service providers through whose platforms users and we can make payment transactions. For example, via [Stripe](https://stripe.com/ch/privacy).
As an alternative, we offer customers the option to pay by invoice instead of using external payment providers. However, this may require a positive credit check in advance.
The data processed by the payment service providers includes personal data, such as the name and address, bank data, such as account numbers or credit card numbers, passwords, TANs and checksums, as well as the contract, totals and recipient-related information. The information is necessary to carry out the transactions. However, the data entered is only processed by the payment service providers and stored with them. We as the operator do not receive any information about (bank) account or credit card, but only information to confirm (accept) or reject the payment. Under certain circumstances, the data is transmitted by the payment service providers to credit agencies. The purpose of this transmission is to check the identity and creditworthiness of the payment service provider. In this regard, we refer to the terms and conditions and data protection information of the payment service providers.
For payment transactions, the terms and conditions and the data protection notices of the respective payment service providers apply, which can be accessed within the respective website or transaction applications. We also refer to these for the purpose of further information and assertion of revocation, information and other rights concerned.
Law enforcement [#law-enforcement]
We disclose personal data to law enforcement agencies, investigative authorities or in legal proceedings to the extent we are required to do so by law or when necessary to protect our rights or the rights of users.
Cookies [#cookies]
Our Sites use cookies. These are small text files that make it possible to store specific information related to the user on the user's terminal device while the user is using the website. Cookies enable us, in particular, to offer a single sign-on procedure, to control the performance of our Services, but also to make our offer more customer-friendly. Cookies remain stored beyond the end of a browser session and can be retrieved when the user visits the site again.
When you use our Services, we may collect information about your visit, including via cookies, beacons, invisible tags, and similar technologies (collectively "cookies") in your browser and on emails sent to you. This information may include personal data, such as your IP address, web browser, device type, and the web pages that you visit just before or just after you use the Services, as well as information about your interactions with the Services, such as the date and time of your visit, and where you have clicked.
Necessary cookies [#necessary-cookies]
Some cookies are strictly necessary to make our Services available to you. We cannot provide you with our Services without this type of cookies.
Necessary cookies provide basic functionality such as:
* Session Management
* Single Sign-On
* Rate Limiting
* DDoS Mitigation
* Remembering Preferences
Analytical cookies [#analytical-cookies]
We use cookies for website analytics purposes in order to operate, maintain, and improve the Services for you.
We use third-party providers, mentioned in our [List of Subprocessors](https://trust.zitadel.com/subprocessors), that collect and process certain analytics data on our behalf.
These third-party providers helps us understand how you engage with the Services and may also collect information about your use of other websites, applications, and online resources.
You can opt out by managing your cookie consent through our Services or a third-party tool of your choice.
If you do not want us to use cookies during your visit, you can disable their use in your browser settings.
In this case, certain parts of our Sites (e.g. language selection) may not function or may not function fully.
Where required by applicable law, we obtain your consent to use cookies.
How we protect personal data [#how-we-protect-personal-data]
Personal data is maintained on our servers or those of our service providers, and is accessible by authorized employees, representatives, and agents as necessary for the purposes described in this privacy policy.
We maintain a range of physical, electronic, and procedural safeguards designed to help protect personal data. While we attempt to protect your personal data in our possession, we cannot guarantee at all times the security of the data as no method of transmission over the internet or security system is perfect.
If you choose to remain logged in, you should be aware that anyone with access to your device will be able to access your account and we therefore strongly recommend that you take appropriate steps to protect against unauthorized access to, and use, of your account. Please also notify us as soon as possible if you suspect any unauthorized use of your account or password.
Rights of data subjects [#rights-of-data-subjects]
Depending on your location and subject to applicable law, you may have the following rights regarding the personal data we process:
Right to information [#right-to-information]
You have the right to know what personal data we hold and process about you and to access such personal data.
Right to rectification [#right-to-rectification]
You have the right to request the correction of inaccurate personal data concerning you.
Right to erasure (right to be forgotten) [#right-to-erasure-right-to-be-forgotten]
You have the right to request the deletion or erasure of the personal data concerning you.
Right to restrict processing [#right-to-restrict-processing]
You have the right to request to restrict the processing of your personal data in certain cases.
Right to data portability [#right-to-data-portability]
You have the right to receive the personal data concerning you in a structured, common and machine-readable format, and to have this data transferred to another data processor if the legal requirements are met.
Right to object [#right-to-object]
Depending on the circumstances, you have the right to object to the processing of personal data concerning you, insofar as we base the processing of your personal data on a balancing of interests. This is the case if the processing is not necessary, for example, to fulfill a contract or a legal obligation.
To exercise such an objection, please indicate your reasons why we should not process your personal data as we have done. We will then review the situation and either stop or adjust the data processing or explain our reasons for continuing the processing.
Right to revoke consent under data protection law [#right-to-revoke-consent-under-data-protection-law]
Insofar as our processing is based on consent, you have the right to revoke your consent at any time with effect. Withdrawing your consent will not affect the lawfulness of any processing we conducted prior to your withdrawal, nor will it affect processing of your personal data conducted in reliance on lawful processing grounds other than consent.
Assertion of rights by the data subjects [#assertion-of-rights-by-the-data-subjects]
If you wish to exercise your rights, you may do so by contacting the above-mentioned contact person.
You can opt out of receiving marketing emails from us by following the unsubscribe link in the emails or by emailing us. If you choose to no longer receive marketing information, we may still communicate with you regarding such things as your security updates, product functionality, responses to service requests, or other transactional, non-marketing purposes.
If you have a concern about how we collect and use personal data, please contact us using the contact details provided at the beginning of this privacy policy. You also have the right to contact your local data protection authority if you prefer, such as:
* Data protection authorities in the European Economic Area (EEA): [https://edpb.europa.eu/about-edpb/board/members\_en](https://edpb.europa.eu/about-edpb/board/members_en);
* Swiss data protection authorities: [https://www.edoeb.admin.ch/edoeb/en/home/deredoeb/kontakt.html](https://www.edoeb.admin.ch/edoeb/en/home/deredoeb/kontakt.html);
* UK data protection authority: [https://ico.org.uk/global/contact-us/](https://ico.org.uk/global/contact-us/).
Recourse, Enforcement, and Liability [#recourse-enforcement-and-liability]
Zitadel, Inc. is committed to addressing any concerns regarding its compliance with the Data Privacy Framework Principles.
Commitment to Resolve Complaints [#commitment-to-resolve-complaints]
Zitadel commits to resolving complaints about our collection or use of your DPF-covered personal information. Individuals from the EU, UK, or Switzerland with inquiries or complaints regarding our DPF policy should first contact Zitadel Inc. at the address given in this policy.
Zitadel will investigate and attempt to resolve DPF-related complaints and disputes within 45 days of receipt.
Alternative Dispute Resolution (ADR) [#alternative-dispute-resolution-adr]
In compliance with the EU-U.S. DPF and the UK Extension to the EU-U.S. DPF and the Swiss-U.S. DPF, Zitadel Inc. commits to refer unresolved complaints concerning our handling of personal data received in reliance on the EU-U.S. DPF and the UK Extension to the EU-U.S. DPF and the Swiss-U.S. DPF to JAMS Mediation, Arbitration and ADR Services ("JAMS"), an alternative dispute resolution provider based in the United States. If you do not receive timely acknowledgment of your DPF Principles-related complaint from us, or if we have not addressed your DPF Principles-related complaint to your satisfaction, please visit [https://www.jamsadr.com/dpf-dispute-resolution](https://www.jamsadr.com/dpf-dispute-resolution) for more information or to file a complaint. The services of JAMS are provided at no cost to you.
Binding Arbitration [#binding-arbitration]
Under certain conditions, more fully described on the [Data Privacy Framework website](https://www.dataprivacyframework.gov/framework-article/ANNEX-I-introduction), you may be entitled to invoke binding arbitration for DPF complaints not resolved by any of the other DPF mechanisms. This option serves as an ultimate recourse for individuals if other avenues for resolution have been exhausted.
Liability in Cases of Onward Transfers [#liability-in-cases-of-onward-transfers]
Zitadel is committed to the Data Privacy Framework (DPF) Principles, which includes accountability for personal data that it subsequently transfers to a third party acting as an agent on its behalf. Zitadel remains liable under the DPF Principles if an agent processes such personal data in a manner inconsistent with the Principles, unless Zitadel proves that it is not responsible for the event giving rise to the damage.
U.S. Regulatory Oversight [#u-s-regulatory-oversight]
Zitadel's compliance with the Data Privacy Framework Principles is subject to the investigatory and enforcement powers of the U.S. Federal Trade Commission (FTC). The FTC has jurisdiction over Zitadel's adherence to its DPF commitments. This oversight by a U.S. regulatory authority is a key component of the DPF's enforcement structure.
Cooperation with Data Protection Authorities [#cooperation-with-data-protection-authorities]
Zitadel commits to cooperate with the EU DPAs, the UK ICO (and GRA), and the Swiss FDPIC in the investigation and resolution of complaints brought under the DPF and will comply with the advice given by these authorities with regard to data transferred from the EU, UK, and Switzerland.
Additional Information for U.S. Residents [#additional-information-for-u-s-residents]
Categories of personal data we collect and our purposes for collection and use
You can find a list of the categories of personal data that we collect in the section above titled "Processing of personal data, legal basis, storage period". In the last 12 months, we collected the following categories of personal data depending on the Services used:
* Identifiers and account information, such as the username and email address;
* Commercial information, such as information about transactions undertaken with us;
* Internet or other electronic network activity information, such as information about activity on our Site and Services.
* Geolocation information based on the IP address.
* Audiovisual information in pictures, audio, or video content that you may choose to submit to us.
* Professional or employment-related information or demographic information, but only if you explicitly provide it to us, such as by filling out a survey or by applying for a job with us.
* Inferences we make based on other collected data, for purposes such as recommending content and analytics.
For details regarding the sources from which we obtain personal data, please see the "Processing of personal data, legal basis, storage period" section above.
We collect and use personal data for the business or commercial purposes described in the "Processing of personal data, legal basis, storage period" section above.
Categories of personal data disclosed and categories of recipients [#categories-of-personal-data-disclosed-and-categories-of-recipients]
We disclose the following categories of personal data for business or commercial purposes to the categories of recipients listed below:
* We disclose identifiers with businesses, service providers, and third parties, such as analytics providers and social media networks.
* We disclose Internet or other network activity with businesses, service providers, and third parties, such as analytics providers and social media networks.
* We disclose geolocation information with businesses, service providers, and third parties such as advertising networks, analytics, and social media.
* We disclose payment information with businesses and service providers who process payments.
* We disclose commercial information with businesses, service providers, and third parties, such as analytics providers and social media networks.
* We disclose audiovisual information with businesses and service providers who help administer customer service and fraud or loss prevention services.
* We disclose inferences with businesses and service providers who help administer marketing and personalization.
Privacy rights [#privacy-rights]
Right to Opt-Out of Cookies and Sale/Sharing: Although we do not sell personal data for monetary value, our use of cookies and automated technologies may be considered a "sale" / "sharing" in certain states, such as California. Visitors to our US website can opt out of such third parties by clicking the "Manage cookie preferences" link at the bottom of our Site. The categories of personal data disclosed that may be considered a "sale" / "sharing" include identifiers, device information, Internet or other network activity, geolocation data, and commercial data.
The categories of third parties to whom personal data was disclosed that may be considered "sale"/ "sharing" include data analytics providers and social media networks.
We do not have actual knowledge that we sell or share the personal data of individuals under 16 years of age.
If you are a resident of the State of Nevada, Chapter 603A of the Nevada Revised Statutes permits a Nevada resident to opt out of future sales of certain covered information that a website operator has collected or will collect about the resident. Although we do not currently sell covered information, please contact us to submit such a request.
Right to Limit the Use of Sensitive Personal Information: We only collect sensitive personal information, as defined by applicable privacy laws, for the purposes allowed by law or with your consent. We do not use or disclose sensitive personal information except to provide you the Services or as otherwise permitted by law. We do not collect or process sensitive personal information for the purpose of inferring characteristics.
Right to Access, Correct, and Delete Personal Data: Depending on your state of residence in the U.S., you may have:
(i) the right to request access to and receive details about the personal data we maintain and how we have processed it, including the categories of personal data, the categories of sources from which personal data is collected, the business or commercial purpose for collecting, selling, or sharing personal data, the categories of third parties to whom personal data is disclosed, and the specific pieces of personal data collected;
(ii) the right to delete personal data collected, subject to certain exceptions;
(iii) the right to correct inaccurate personal data.
When you make a request, we will verify your identity by asking you to sign into your account or if necessary by requesting additional information from you. You may also make a request using an authorized agent. If you submit a rights request through an authorized agent, we may ask such agent to provide proof that you gave a signed permission to submit the request to exercise privacy rights on your behalf. We may also require you to verify your own identity directly with us or confirm to us that you otherwise provided such agent permission to submit the request. Once you have submitted your request, we will respond within the time frame permitted by the applicable law.
If you have any questions or concerns, you may reach us by contacting using one of the contact details listed at the beginning of this privacy policy.
Depending on your state of residence, you may be able to appeal our decision to your request regarding your personal data. To do so, please contact us by using one of the contact details listed at the beginning of this privacy policy. We respond to all appeal requests as soon as we reasonably can, and no later than legally required.
We do not discriminate against customers who exercise any of their rights described in our privacy policy.
California Shine the Light: Customers who are residents of California may request information concerning the categories of personal data (if any) we disclose to third parties or affiliates for their direct marketing purposes. If you would like more information, please submit a written request to us by using one of the contact details listed at the beginning of this privacy policy.
Do Not Track signals: Most modern web browsers give you the option to send a 'Do Not Track' signal to the sites you visit, indicating that you do not wish to be tracked. However, there is currently no accepted standard for how a site should respond to this signal, and we do not take any action in response to this signal.
Note on international data transfers [#note-on-international-data-transfers]
Our Sites and Services make use of tools from companies based in countries outside of Switzerland or the EU/EEA, namely those based in the USA. When these tools are active, your personal data may be transferred to the servers of the respective companies abroad. If you are using the Site or Services from outside the United States, your personal data may be processed in a foreign country, where privacy laws may be less stringent than the laws in your country. In these cases, we only transfer personal data after we have implemented the legally required measures for this, such as concluding standard contractual clauses on data protection or obtaining the consent of the data subjects. If interested, the documentation on these measures can be obtained from the contact person mentioned above. By submitting your personal data to us you agree to the transfer, storage, and processing of your personal data in a country other than your country of residence including, but not necessarily limited to, the United States.
We actively try to minimize the use of tools from companies located in countries without equivalent data protection, however, due to the lack of alternatives, this is currently not always feasible without major inconvenience. If you have any concerns, please contact us directly and we will try to find a mutual solution for your needs.
Children's Privacy [#childrens-privacy]
Our Site is not intended for or directed to children under the age of 14. We do not knowingly collect personal data directly from children under the age of 14 without parental consent. If we become aware that a child under the age of 14 has provided us with personal data, we will delete the information from our records.
Changes to this Privacy Policy [#changes-to-this-privacy-policy]
We may revise this privacy policy from time to time and will post the date it was last updated at the top of this privacy policy.
We will provide additional notice to you if we make any changes that materially affect your privacy rights.
Please subscribe to updates in our [Trust Center](https://trust.zitadel.com/updates) to receive future notices and updates.
Contact us [#contact-us]
If you have any questions about our data processing, please email us or contact us by using the contact details listed at the beginning of this privacy notice.
# Rate Limit Policy
Last updated on February 24, 2025
This policy is an annex to the [Terms of Service](../terms-of-service) and clarifies your obligations while using our Services, specifically how we will use rate limiting to enforce certain aspects of our [Acceptable Use Policy](./acceptable-use-policy).
Why do we rate limit [#why-do-we-rate-limit]
To ensure the availability of our Services and to avoid slow or failed requests by our Customers, due to overloads, we impose rate limits on certain API. These limits helps us guarantee the performance and availability of ZITADEL Cloud.
How is the rate limit implemented [#how-is-the-rate-limit-implemented]
ZITADEL Clouds rate limit is built around a `IP` oriented model.
Please be aware that we also utilize a service for DDoS mitigation.
So if you simply change your `IP` address and run the same request again and again you might get blocked at some point.
If you are blocked you will receive a `http status 429`.
You should consider to implement [exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff) into your application to prevent a blocking loop.
We understand that there are certain scenarios where your users access ZITADEL from shared IP Addresses.
For example if you use a corporate proxy or Network Address Translation NAT.
Please [get in touch](https://zitadel.com/contact) with us to discuss your requirements, and we'll find a solution.
What rate limits do apply [#what-rate-limits-do-apply]
For ZITADEL Cloud, we have dedicated rate limits for the user interfaces (login, register, management console,...) and the APIs.
Rate limits are implemented with the following rules:
| Path | Description | Rate Limiting | One Minute Banning |
| --------------- | -------------------------------------- | ------------------------------------ | ------------------------------------- |
| /ui/\* | Global Login, Register and Reset Limit | 10 requests per second over a minute | 15 requests per second over 3 minutes |
| All other paths | All gRPC-, REST and OAuth APIs | 50 requests per second over a minute | 50 requests per second over 3 minutes |
Load Testing [#load-testing]
If you would like to conduct load testing of ZITADEL Cloud or a managed instance, you MUST request to do so with a minimum of 2 weeks notice before the test by contacting us at [support@zitadel.com](mailto:support@zitadel.com).
You MUST NOT conduct load testing without prior approval by us. Without prior approval and setup there is a high risk of being flagged by our DDoS solution as malicious traffic. This can have a severe impact on your service quality or result in termination of your agreement.
# Vulnerability Disclosure Policy
Last updated on March 16, 2023
At ZITADEL we are extremely grateful for security aware people who disclose vulnerabilities to us and the open source community.
All reports will be investigated by our team and we will work with you closely to validate and fix vulnerabilities reported to us.
We require that you keep vulnerabilities confidential until we are able to address them, since public disclosure of security vulnerabilities could put the ZITADEL community at risk.
ZITADEL (CAOS Ltd.) will not take legal action against you or terminate your access to our services, conditional that you report vulnerabilities in accordance to this policy.
Scope [#scope]
The scope of this policy applies to all Websites and Services operated by ZITADEL.
All security issues that concern our Product in form of Software in our [open source repositories](https://github.com/zitadel), should be reported according to [Security Policy](https://github.com/zitadel/zitadel/blob/main/SECURITY.md).
When in doubt about the scope of your vulnerability, please follow the process outlined in this policy.
Discovering a vulnerability [#discovering-a-vulnerability]
Responsible security research on our Websites, Products, and Services is encouraged and we allow you to conduct testing on our services to which you have authorized access.
You must not do research or testing that involves
* Any activity that violates applicable law
* Modify or destroy any data that does not belong to you
* Accessing or attempt to access data that does not belong to you
* Executing denial of service attacks
* Executing load testing
Exceptions may be granted after your initial report by a member of our security team.
Reporting a vulnerability [#reporting-a-vulnerability]
To file an incident, please disclose it by e-mail to [security@zitadel.com](mailto:security@zitadel.com) including the following details of the vulnerability:
* Target: ZITADEL, Website (zitadel.com), ZITADEL Cloud (zitadel.cloud), Other (please describe)
* Type: For example DoS, authentication bypass, information disclosure, broken authorization, ...
* Description: Provide a detailed explanation of the issue, steps to reproduce, and assumptions you have made
* URL / Location (optional): The URL of the vulnerability
* Contact details (optional): In case we should contact you on a different channel
At the moment GPG encryption is no yet supported, however you may sign your message at will.
Your email will be acknowledged within 48 hours.
We will follow-up within the next 3 business days indicating next steps in handling your report.
If you haven't received a response within 48 hours, or you didn't get a reply from our security team within the last 5 days, please contact [support@zitadel.com](mailto:support@zitadel.com).
Please inform us in your report whether we should mention your contribution.
We will not publish this information by default to protect your privacy.
What not to report [#what-not-to-report]
* Disclosure of known public files or directories, e.g. robots.txt, files under .well-known, or files that are included in our public repositories (eg, go.mod)
* DoS of users when [Lockout Policy is enabled](/guides/manage/console/default-settings#lockout)
* Suggestions on Certificate Authority Authorization (CAA) rules
* Suggestions on DMARC/DKIM/SPF settings
* Suggestions on DNSSEC settings
* Phishing or Social Engineering Attacks
* Lack of security flags on non-sensitive cookies
Disclosure Process [#disclosure-process]
Our security team will follow the disclosure process:
1. We will acknowledge the receipt of your vulnerability report
2. Our security team will try to verify, reproduce, and determine the impact of your report
3. A member of our team will respond to either confirm or reject your report, including an explanation
4. Code will be audited to assess if the report uncovers similar issues
5. Fixes are prepared for the latest release
6. On the date that the fixes are applied, we will create a CVE and publish a [security advisory](https://github.com/zitadel/zitadel/security/advisories). Affected users of our Product, Services, or Website will be informed of the fix and required actions.
We think it is crucial to publish advisories `ASAP` as mitigations are ready. But due to the unknown nature of the disclosures the time frame can range from 7 to 90 days.
Bug Bounty / Compensation [#bug-bounty-compensation]
At this moment, we do not pay out monetary compensation for reporting security vulnerabilities.
Please inform us in your report whether we should mention your contribution.
We will not publish this information by default to protect your privacy.
In case we have confirmed your report, we may compensate you, given prior written approval by ZITADEL, for costs
* incurred during research for using our paid services
* on time & material spend on analysis after confirming your report
# ZITADEL Cloud & Enterprise Service Description
Last updated on April 5, 2024
This annex of the [Framework Agreement](../terms-of-service) describes the services offered by us.
Services offered [#services-offered]
ZITADEL Cloud [#zitadel-cloud]
[ZITADEL Cloud](https://zitadel.com) is a fully managed cloud service of the [ZITADEL software](https://github.com/zitadel).
You will benefit from the same software as the open-source project, but we take care of the hosting, maintenance, backup, scaling, and operational security. The cloud service is managed and maintained by the team that also develops the software.
When creating a new instance, you are able to choose a [data location](#data-location).
We follow a single-provider strategy by minimizing the involved [sub-processors](../subprocessors) to increase security, compliance, and performance of our services. [Billing](https://help.zitadel.com/pricing-and-billing-of-zitadel-services) is based on effective usage of our services.
Enterprise license / self-hosted [#enterprise-license-self-hosted]
The ZITADEL Enterprise license allows you to use the [ZITADEL software](https://github.com/zitadel) on your own data center or private cloud.
You will benefit from the transparency of the open source and the hyper-scalability of the same software that is being used to operate [ZITADEL Cloud](#zitadel-cloud).
Benefits over using open source / community license [#benefits-over-using-open-source-community-license]
* [Enterprise supported features](https://help.zitadel.com/zitadel-support-states#enterprise) are only supported under an Enterprise license
* Individual [onboarding support](./support-services#onboarding-support) tailored to your needs and team
* Get access to our support with a [Service Level Agreement](./support-services#service-level-agreement) that is tailored to your needs
* Benefit from personal [technical account management](./support-services#technical-account-manager) provided by our engineers to help you with architecture, integration, migration, and operational improvements of your setup
Benefits over ZITADEL Cloud [#benefits-over-zitadel-cloud]
You can reduce your supply-chain risks by removing us as sub-processor of personal information about your users.
Support staff will have no access to your infrastructure and will only provide technical support.
Operation and direct maintenance of ZITADEL will be done by you.
You can freely choose the infrastructure and location to host ZITADEL.
Responsibilities [#responsibilities]
Your obligations while operating and using ZITADEL are detailed in our [terms of service](/legal/terms-of-service#your-obligations) given the provisions in our [acceptable use policy](/legal/policies/acceptable-use-policy).
When using ZITADEL Cloud, we may process data on behalf according to the [data processing agreement](/legal/data-processing-agreement).
In a self-hosted setup, you will be responsible for the cost, operations, and availability of your infrastructure.
For DDoS, bot, and threat detection and protection we rely on external services in ZITADEL Cloud.
In a self-hosted setup, it is your responsibility to secure the infrastructure to protect confidentiality, integrity, and availability of your data.
ZITADEL Cloud comes with pre-configured SMTP service, SMS service, and a generated domain name.
The SMTP service and SMS service are limited in use.
You should configure your own service providers for production use cases.
In a self-hosted setup, you will be responsible for SMTP / SMS services, domains and certificates.
| Responsibility | ZITADEL Cloud | Self-Hosted |
| ---------------------- | ------------------------- | ----------- |
| Data / Information | Customer | Customer |
| User Access | Customer | Customer |
| SMTP Service | Customer (trial: ZITADEL) | Customer |
| SMS Service | Customer (trial: ZITADEL) | Customer |
| Custom Domain / TLS | Customer (trial: ZITADEL) | Customer |
| DDoS & Bot protection | ZITADEL | Customer |
| WAF / Threat detection | ZITADEL | Customer |
| [Backup](#backup) | ZITADEL | Customer |
| Networking | ZITADEL | Customer |
| Compute / Scaling | ZITADEL | Customer |
| Database | ZITADEL | Customer |
| Application | ZITADEL | ZITADEL |
Data location [#data-location]
Data location refers to a region, consisting of one or many countries or territories, where the customer's data is being hosted.
We can not guarantee that during transit the data will only remain within this region. We take measures, as outlined in our [privacy policy](../policies/privacy-policy), to protect your data in transit and in rest.
Backup [#backup]
Our backup strategy executes daily full backups and differential backups on much higher frequency.
In a disaster recovery scenario, our goal is to guarantee a recovery point objective (RPO) of 1h, and a higher but similar recovery time objective (RTO).
Under normal operations, RPO and RTO goals are below 1 minute.
If you have different requirements we provide you with a flexible approach to back up, restore, and transfer data (f.e. to a self-hosted setup) through our APIs.
Please consult the [migration guides](/guides/migrate/introduction) for more information.
# Service description
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
# Service level description for ZITADEL Cloud
Last updated on November 15, 2023
This annex of the [Framework Agreement](../terms-of-service) describes the service levels offered by us for our Services (ZITADEL Cloud).
Definitions [#definitions]
Monthly Uptime Percentage [#monthly-uptime-percentage]
Monthly Uptime Percentage means total number of minutes in a month, minus the number of minutes of Downtime suffered from all Downtime Periods in a month, divided by the total number of minutes in a month.
Downtime Period [#downtime-period]
Downtime Period means a period of one or more consecutive minutes of Downtime. Partial minutes or intermittent Downtime for a period of less than one minute will not count towards any Downtime Period.
Downtime [#downtime]
Downtime means any period of time in which Core Services are not Available within the Region of the customer’s organization. Downtime excludes any time in which ZITADEL Cloud is not Available because of
* [Announced maintenance work](https://help.zitadel.com/zitadel-software-release-cycle#maintenance)
* Emergency maintenance
* Force majeure events.
Available [#available]
Available means that Core Services of ZITADEL Cloud respond to Customer Requests in such a way that results in a Successful Minute. The Availability of Core Services will be monitored from CAOS’ facilities from black-box monitoring jobs.
Customer Requests [#customer-requests]
Customer Requests means an HTTP request made by a Customer or a Customers’ users to Core Services within the Customer’s organization’s region.
Successful Minute [#successful-minute]
Successful Minute means a minute in which ZITADEL Cloud is not repeatedly returning Failed Customer Requests and includes minutes in which no Customer Requests were made.
Failed Customer Request means Customer Requests that
* Returns an server error; or
* is received by ZITADEL Cloud and results in no response where one is expected
This excludes specifically:
* Failed Customer Requests due to malformed requests, client-side application errors outside ZITADEL Cloud’s control
* Customer Requests that do not reach ZITADEL Cloud Core Services
Core Services [#core-services]
Core Services means the following ZITADEL Cloud Services and API’s:
* **Authentication API** Endpoints including the session endpoints
* **OpenID Connect 1.0 / OAuth 2.0 API** Endpoints
* **SAML 2.0** Endpoints
* **Login Service** means the graphical user interface of ZITADEL Cloud for users to Login, Self-Register, and conduct a Password Reset.
* **Identity Brokering Service** means the component of ZITADEL Cloud that handles federated authentication of users with third-party identity provider, excluding any failure or settings misconfiguration by the third party
Financial Credit [#financial-credit]
**Financial Credit** means the percent of the monthly subscription fee applicable to the month in which the guaranteed service level was not met, according to the actual achieved monthly uptime percentage, as shown in the following table
| Achieved vs. Guaranteed | 99.50% | 99.90% | 99.95% |
| ------------------------ | ------ | ------ | ------ |
| 99.5% - \< 99.9% | n/a | n/a | 10% |
| 99.0% - \< 99.5% | n/a | 10% | 25% |
| 95.0% - \< 99.0% | 10% | 25% | 50% |
| \< 95.0% | 50% | 50% | 50% |
Service Levels [#service-levels]
Availability Objective [#availability-objective]
1. During the term of the subscription agreement under which CAOS has agreed to provide ZITADEL Cloud to Customer, the Core Services will provide a Monthly Uptime Percentage to Customer conditional on the subscription plan as follows (the “SLO”):
| Option | Monthly Uptime Percentage |
| ------------ | ------------------------- |
| Default | 99.50% |
| Extended SLA | 99.95% |
1. If CAOS Ltd. does not meet the guaranteed service level, Customer might be eligible to receive Financial Credit as described in this document. Financial Credit shall be the sole and exclusive remedy for breach of this SLA.
2. The Customer must request Financial Credit and must notify CAOS Support in writing within 30 days of becoming eligible for Financial Credit and must prove Failed Customer Requests during Downtime Periods. Financial Credit will be made in the form of a monetary credit applied to the next possible subscription invoice of ZITADEL Cloud, may only be used to book services in the future, and will in no case be paid as a cash equivalent. No further guarantees are provided.
3. The Service Level commitments apply only to organizations with a subscription plan where a Service Level is applicable and does not include any other organizations of the same customer. The Customer is not entitled to any Financial Credit, if it is in breach of the Agreement at the time of the occurrence of the event giving rise to the credit.
# Support service description for ZITADEL
Last updated on November 15, 2023
This annex of the [Framework Agreement](../terms-of-service) and the [Support Service Terms and Conditions](../annex-support-services) describes the support services offered by us for our Services.
Support Services for products and services provided by ZITADEL is offered to customers according to the terms and conditions outlined in this document.
The customer may purchase support services from ZITADEL (CAOS Ltd.) directly.
Service Level Agreement [#service-level-agreement]
ZITADEL provides a Service Level Agreement for support of the [services offered](./cloud-service-description#services-offered).
Depending on your subscription plan you might be eligible to the following support service level agreement.
ZITADEL Cloud [#zitadel-cloud]
Based on your subscription plan you may be eligible for the support services as outlined in this document.
You may purchase additional premium support plans which replace the default support features.
Support in subscription plans [#support-in-subscription-plans]
Support features for ZITADEL Cloud subscriptions are as follows:
| Subscription Plans | Free | Production | Enterprise Cloud |
| ------------------------------------------------------------------------------------------- | -------------- | ----------------- | ------------------------- |
| [Support hours](#support-hours) | Business hours | Business hours | bespoke (up to 24x7) |
| [Response Time](#slo---initial-response-time) (Severity 1) | n/a | 48 business hours | bespoke (as low as 30min) |
| [Community support](#community-support) | yes | yes | yes |
| [Professional support](#professional-support) | no | yes | yes |
| [Enterprise supported features](https://help.zitadel.com/zitadel-support-states#enterprise) | no | no | yes |
| [Technical Account Management](#technical-account-manager) | no | no | bespoke |
Extended support [#extended-support]
Extended support can be added to ZITADEL Cloud subscription plans.
The default support features will be replaced as follows:
| Extended support | Default |
| ------------------------------------------------------------------------------------------- | --------------------------------- |
| [Support hours](#support-hours) | [Extended hours](#extended-hours) |
| [Response Time](#slo---initial-response-time) (Severity 1) | 1 business hour |
| [Community support](#community-support) | yes |
| [Professional support](#professional-support) | yes |
| [Enterprise supported features](https://help.zitadel.com/zitadel-support-states#enterprise) | no |
| [Technical Account Management](#technical-account-manager) | no |
ZITADEL Enterprise / self-hostable [#zitadel-enterprise-self-hostable]
With ZITADEL Enterprise you become eligible for support plans according to your purchase order for self-hosting ZITADEL.
Please refer to the [service description](./cloud-service-description#enterprise-license-self-hosted) for an overview of ZITADEL Enterprise.
| ZITADEL Enterprise self-hostable | Default |
| ------------------------------------------------------------------------------------------- | ------------------------- |
| [Support hours](#support-hours) | bespoke (up to 24x7) |
| [Response Time](#slo---initial-response-time) (Severity 1) | bespoke (as low as 30min) |
| [Community support](#community-support) | yes |
| [Professional support](#professional-support) | yes |
| [Enterprise supported features](https://help.zitadel.com/zitadel-support-states#enterprise) | yes |
| [Technical Account Management](#technical-account-manager) | bespoke |
Description of support services [#description-of-support-services]
Support hours [#support-hours]
Business hours [#business-hours]
Business hours means 08:00-17:00 Monday - Friday Switzerland time (or as per agreement with the customer). All times exclude public holidays in Switzerland / Canton St. Gallen.
Extended hours [#extended-hours]
Extended hours means 07:00-19:00 Monday - Friday Switzerland time (or as per agreement with the customer). All times exclude public holidays in Switzerland / Canton St. Gallen.
Ticket [#ticket]
Ticket means a discrete technical or non-technical issue that was submitted by the customer and exists in the support portal. A ticket includes a record of all communication associated with the issue.
SLO - Initial response time [#slo-initial-response-time]
ZITADEL service level objective (SLO) for Support Services is defined in terms of initial response time to a support request, as outlined in the table below per plan.
ZITADEL will use reasonable efforts to resolve support requests, but does not guarantee a work-around, resolution or resolution time.
| Subscription Plans | Default | Extended SLA | Custom |
| ------------------ | ----------- | ---------------- | ----------------- |
| Severity 1 | Best effort | 1 business hour | up to 30min |
| Severity 2 | Best effort | 2 business hour | 2 business hours |
| Severity 3 | Best effort | 12 business hour | 12 business hours |
| Severity 4 | Best effort | 24 business hour | 24 business hours |
If we fail to provide the initial response time objective, you will be entitled to service credits. For every 15 minutes exceeding the state objective, 1 day will be added as extension to the current term.
Communication [#communication]
Community support [#community-support]
Community support for ZITADEL is available on our website, our [public chat](https://zitadel.com/chat), and [GitHub](https://github.com/zitadel/).
We do only guarantee response times to Tickets reported via [professional support](#professional-support) channels only.
If you are an eligible customer, please use Tickets for critical or urgent issues.
Professional support [#professional-support]
* Support is available in English
* Default contact: Whenever customers require support, Customers should consult the documentation of the service or product or post a question to our community
* When Customer is eligible for support services through a Subscription Plan, Customer may contact ZITADEL support via the following channels
| Support Feature | Contact information |
| --------------- | ------------------------------------------------------------------------------------------------- |
| Ticket | Submit an issue via the [customer portal](https://zitadel.com/admin/support) |
| eMail Support | [support@zitadel.com](mailto:support@zitadel.com) |
| Chat Support | Private chat channel between ZITADEL and Customer that is opened when Subscription becomes active |
| Phone Support | +41 43 215 27 34 |
* ZITADEL Cloud system status, incidents and maintenance windows will be communicated via [our status page](https://zitadelstatus.com).
* Questions regarding pricing, billing, and invoicing of our services should be addressed to [billing@zitadel.com](mailto:billing@zitadel.com)
* Security related questions and incidents can also be directly addressed to [security@zitadel.com](mailto:security@zitadel.com)
Technical account manager [#technical-account-manager]
ZITADEL will enhance its support offering by providing eligible clients with a Technical Account Manager (TAM), who will perform the following tasks for up to the agreed amount of time during the term of service:
* Provide support and advice regarding best practices on platform, product and settings covered by the applicable Support Services;
* Participate in review calls every other week at mutually agreed times addressing customer’s operational challenges or complex support requests;
* Walk-through of new features and customer feedback.
We offer TAM services only bundled with specific subscription plans, and the option to add more TAM hours per period to these plans.
If you require consulting for your projects, please request a quote via our [website](https://zitadel.com/contact).
Onboarding support [#onboarding-support]
Our onboarding support should help you, as a new customer, to get a better understanding on how to integrate ZITADEL into your solution, how to tackle the migration, and ensure a highly-available day-to-day operation.
Onboarding support services can be offered to customers that enter a ZITADEL Cloud or a ZITADEL Enterprise subscription.
If you intend to use the open-source version exclusively then please join our community chat or GitHub.
Your questions might help other people in the community and will make our project better over time.
Please [contact us](https://zitadel.com/contact) for a quote and to get started with onboarding support.
Below you will find topics covered and scope of the offered services.
Proof of value [#proof-of-value]
Within a short time-frame, f.e. 3 weeks, we can show the value of using our services and have the ability to establish the proof a of working setup for your most critical use cases.
We may offer to support you during an initial period to evaluate next steps.
Before the start of the period we may ask you to provide a description of your critical use cases and a high-level overview of your planned integration architecture.
During this period you should make sure that you have the necessary resources on your side to complete the proof of value.
Onboarding term [#onboarding-term]
With the onboarding support we provide the initial knowledge transfer to configure and operate ZITADEL.
During the term you will get direct access to our engineering team via [Technical Account Management](#technical-account-manager).
Duration is typically 3 months but this could vary depending on your requirements.
We offer an onboarding term in combination with ZITADEL Enterprise subscriptions.
Topics covered [#topics-covered]
The scope will be tailored to your requirements.
Topics of the onboarding term may include
* Administration
* DevOps (Operation)
* Architecture
* Integration
* Migration
* Security Best Practices & Go-Live Checkup
Instance Settings
Walk-through all features
Users / Manuals
Authentication & Management APIs
Validation of tokens
Client integration best-practices
Event types
Database schemas and compute models
Accessing database
Observability (Logs, Errors, Metrics, Tracing)
Operations best practices (Deployment, Backup, Networking etc.)
Check prerequisites and architecture
Troubleshoot installation and configuration of ZITADEL
Troubleshoot and configuration connectivity to the database
Functional testing of the ZITADEL instance
Performance testing
Setting up or maintaining backup storage
Running multiple ZITADEL instances on the same cluster
Integration into internal monitoring and alerting
Multi-cluster architecture deployments
DNS, Network and Firewall configuration
Customer-specific Kubernetes configuration needs
Non-production environments
Production deployment
Application-side coding, configuration, or tuning
Support [#support]
Support request [#support-request]
ZITADEL agrees to handle support incidents in the following scenarios:
1. Service, product or settings as provided by ZITADEL contains errors or critical security-related issues
2. Service or product requires upgrades or changes through the customer
3. Service or product has incorrect or missing documentation
Support features include:
* Answer questions regarding usage of specific features or settings
* Provide high-level suggestions regarding appropriate usage, features or settings
* Assist in troubleshooting of issues to isolate potential root cause
* Document and advise alternative solutions for reported defects
Excluded are broader consulting & customer-specific engineering requests regarding use of our products and services. Moreover support requests from Customer’s end users must be handled by the Customer directly.
Support service process [#support-service-process]
The customer may submit support requests (“ticket”) through any means of eligible communication channels, consisting of
* Single discrete problem, issue, or request
* Initial severity level and impact statement for assessment
* Description of the issue and if possible a description of the observed and expected behavior, steps to reproduce the issue, evidence that issue is not caused by connectivity / compute, relevant anonymized log-files etc.
* All information requested by ZITADEL as we resolve the ticket (e.g. system logs)
ZITADEL will review the case information and determine the severity level (see below), working with the customer to assess the urgency of the request and use reasonable efforts to respond to support requests within the initial response time.
ZITADEL will use reasonable efforts to resolve support request as defined below, but does not guarantee a workaround, resolution or resolution time.
| Severity Level | Description |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Severity 1** Critical / Service down |
Widespread failure or complete unavailability of ZITADEL Core Services.
ZITADEL will use continuous effort to provide a workaround or permanent solution. When Core Services are available, the severity will be lowered to the new appropriate level.
Core Services of ZITADEL software continue to operate in severely restricted fashion, yet long-term productivity may be impacted.
When Core Services are no longer severely degraded (eg, through a viable workaround or release), the severity level will be lowered to Severity 3.
|
| **Severity 3** Standard support request |
Partial and non-critical loss of ZITADEL software functionality or major software defect, yet a workaround exists for viable long-term operation.
ZITADEL will continue to work on developing permanent resolution.
|
| **Severity 4** Non-urgent request | Defined as follows:
Request for information or general query
Feature request
Performance issues and little to none functional impact
Defects with workarounds and little to low functional impact
|
|
ZITADEL will continue to work on developing permanent resolution and response to general requests. ZITADEL does not provide a timeline or guarantee to include any feature requests.
| |
Escalation [#escalation]
The customer may escalate support requests following the escalation process:
1. For non-urgent needs, the client may request management escalation within the ticket. A manager will review the request and provide a response within one business day.
2. For urgent needs, the client may escalate directly by calling +41 71 560 28 06 and emailing to [hi@zitadel.com](mailto:hi@zitadel.com). A manager will review the request and provide response within two business hours.
If we fail to provide a response to the escalation, you will be entitled to service credits. For every 15 minutes exceeding the state objective, 1 day will be added as extension to the current term.
# Set up ZITADEL with Docker Compose
This guide takes you from zero to a running ZITADEL instance in minutes and then shows you how to harden it for a homelab or semi-production deployment.
Prerequisites [#prerequisites]
* Docker Engine 24+ with the Compose plugin (`docker compose`)
* A machine with at least 2 GB RAM
See [Requirements](/self-hosting/manage/requirements) for supported database, cache, and proxy versions.
Stage 1 — Quickstart (2 minutes) [#stage-1-quickstart-2-minutes]
Download the two required files, copy the example config, and start:
```shell
mkdir zitadel-compose && cd zitadel-compose
# Download the compose file and example environment
curl -fsSLO https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/docker-compose.yml &&
curl -fsSLO https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/.env.example
# Create your environment file and start
cp .env.example .env
docker compose up -d --wait
```
That's it. Visit [http://localhost:8080](http://localhost:8080) to open the login screen.
The base stack runs: **Traefik** ([reverse proxy](/self-hosting/manage/reverseproxy/reverse_proxy)) → **ZITADEL API** (Go) + **ZITADEL Login** (Next.js) → **PostgreSQL**.
All routing, including [gRPC over HTTP/2](/self-hosting/manage/http2), is handled automatically by Traefik — no extra configuration needed.
Stage 2 — Homelab / Semi-Production [#stage-2-homelab-semi-production]
Use your own domain [#use-your-own-domain]
Edit `.env` and set your real domain (see [Custom Domains](/self-hosting/manage/custom-domain) for details):
```dotenv
ZITADEL_DOMAIN=auth.example.com
```
Enable TLS [#enable-tls]
Pick a TLS mode and download the matching overlay file:
| Mode | Overlay file | When to use |
| ------------- | -------------------------------------- | ------------------------------------------------------- |
| Let's Encrypt | `docker-compose.mode-letsencrypt.yml` | Public domain, automatic certs |
| External TLS | `docker-compose.mode-external-tls.yml` | Behind a load balancer, CDN, or WAF that terminates TLS |
| Local TLS | `docker-compose.mode-local-tls.yml` | Self-signed certs for LAN-only access |
Download the overlay you need, then start with both files:
```shell
# Download the overlay (replace with your chosen mode)
curl -fsSLO https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/docker-compose.mode-letsencrypt.yml
# Start with the overlay
docker compose --env-file .env \
-f docker-compose.yml \
-f docker-compose.mode-letsencrypt.yml \
up -d --wait
```
Set `LETSENCRYPT_EMAIL` in `.env` to receive certificate expiry notifications.
Harden secrets [#harden-secrets]
The masterkey [encrypts sensitive data at rest](/concepts/architecture/secrets). Once ZITADEL has been initialized with a masterkey, it **cannot be changed** without losing access to encrypted data. Generate it **before first start** and store it safely.
```shell
# Generate a secure masterkey (must be exactly 32 characters)
ZITADEL_MASTERKEY=$(tr -dc A-Za-z0-9 > .env
# Set strong database passwords
echo "POSTGRES_ADMIN_PASSWORD=$(tr -dc A-Za-z0-9 > .env
echo "POSTGRES_ZITADEL_PASSWORD=$(tr -dc A-Za-z0-9 > .env
```
External URL settings [#external-url-settings]
These three settings **must match your public endpoint exactly**:
| Setting | Meaning | Example |
| ------------------------ | --------------------------------- | ----------------------- |
| `ZITADEL_DOMAIN` | The public domain users type in | `auth.example.com` |
| `ZITADEL_EXTERNALPORT` | The port visible to users | `443` for HTTPS |
| `ZITADEL_EXTERNALSECURE` | Whether the public URL uses HTTPS | `true` for any TLS mode |
If these don't match reality, ZITADEL returns **"Instance not found"** errors. This is the most common deployment issue — see [TLS Modes](/self-hosting/manage/tls_modes) for details.
Enable caching with Redis [#enable-caching-with-redis]
See [Cache Configuration](/self-hosting/manage/cache) for all available options. Add these to `.env`:
```dotenv
ZITADEL_CACHES_CONNECTORS_REDIS_ENABLED=true
ZITADEL_CACHES_INSTANCE_CONNECTOR=redis
ZITADEL_CACHES_MILESTONES_CONNECTOR=redis
ZITADEL_CACHES_ORGANIZATION_CONNECTOR=redis
```
Then start with the `cache` profile:
```shell
docker compose --env-file .env -f docker-compose.yml --profile cache up -d --wait
```
Stage 3 — Beyond Compose [#stage-3-beyond-compose]
Production-like init/setup/start split [#production-like-init-setup-start-split]
For controlled upgrades, separate [database initialization from the running API](/self-hosting/manage/updating_scaling):
```shell
curl -fsSLO https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/docker-compose.prodlike.yml &&
docker compose --env-file .env \
-f docker-compose.yml \
-f docker-compose.prodlike.yml \
up -d --wait
```
This creates three ZITADEL containers:
1. `zitadel-init` — runs database migrations (one-shot)
2. `zitadel-setup` — configures the instance (one-shot)
3. `zitadel-api` — starts the API server (long-running)
Enable observability [#enable-observability]
Set in `.env`:
```dotenv
ZITADEL_INSTRUMENTATION_TRACE_EXPORTER_TYPE=grpc
```
Download the collector configuration and start with the `observability` profile:
```shell
curl -fsSLO https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/otel-collector-config.yaml &&
docker compose --env-file .env -f docker-compose.yml --profile observability up -d --wait
```
Traces are logged to the collector's stdout by default (`docker compose logs otel-collector`).
To forward traces to your own backend (Grafana Tempo, Jaeger, OpenObserve, etc.), set `OTEL_BACKEND_ENDPOINT` in `.env` and uncomment the `otlp` exporter in `otel-collector-config.yaml`.
See [Metrics](/self-hosting/manage/metrics/overview) for Prometheus scraping and available metric types.
Scale API replicas [#scale-api-replicas]
Scaling requires the **prodlike overlay** so that migrations run once in `zitadel-init` instead of on every replica.
```shell
docker compose --env-file .env \
-f docker-compose.yml \
-f docker-compose.prodlike.yml \
up -d --scale zitadel-api=3
```
Updating ZITADEL [#updating-zitadel]
Edit `ZITADEL_VERSION` in `.env`, then:
```shell
docker compose --env-file .env -f docker-compose.yml pull
docker compose --env-file .env -f docker-compose.yml up -d --wait
```
`ZITADEL_FIRSTINSTANCE_*` and `ZITADEL_DEFAULTINSTANCE_*` environment variables are only applied during the **initial setup**.
To change settings on an existing installation, use the Admin Console or Admin API.
Moving to Kubernetes [#moving-to-kubernetes]
Docker Compose is ideal for getting started and homelab deployments.
For production workloads, review the [Production Checklist](/self-hosting/manage/productionchecklist) and deploy with the official [Helm chart for Kubernetes](/self-hosting/deploy/kubernetes).
The compose pack and the Helm chart share the same application configuration model (`ZITADEL_*` environment variables), so migration is straightforward.
# Developing ZITADEL with Dev Containers
Dev containers provide a convenient way to set up a development environment for ZITADEL with all the necessary dependencies pre-configured. This allows you to start contributing or working on ZITADEL locally with minimal setup.
Prerequisites [#prerequisites]
* Docker installed on your machine. You can find installation instructions for Docker on their official website: [https://docs.docker.com/engine/install/](https://docs.docker.com/engine/install/)
* A code editor or IDE with remote container development capabilities (optional, but recommended). [Visual Studio Code](https://code.visualstudio.com) with the [Remote Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) is a popular option.
Setting Up Dev Container [#setting-up-dev-container]
ZITADEL provides a `.devcontainer` folder that configures the development environment within a container. Here's how to get started:
1. Clone the ZITADEL repository from GitHub:
```bash
git clone https://github.com/zitadel/zitadel.git
```
2. Navigate to the project directory:
```bash
cd zitadel
```
3. Open the project in your code editor or IDE (if using one with remote container support).
4. Follow the instructions provided by your code editor/IDE to initiate the development container. This typically involves opening the "Command Palette" or similar functionality and searching for commands related to "Dev Containers" or "Remote Containers". The quick start guide for VS Code can found [here](https://code.visualstudio.com/docs/devcontainers/containers#_quick-start-open-an-existing-folder-in-a-container)
**Note**: The first time you run this command, it might take some time to download the container image.
Using Dev Container [#using-dev-container]
Once the container is running, you will have a development environment set up with all the necessary dependencies pre-installed. You can then follow the instructions in the ZITADEL [contribution guide](https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#developing-zitadel-with-dev-containers) to build and run ZITADEL, or develop the ZITADEL Management Console application.
# Install Zitadel on Linux
Be aware! This guide does not work for the newly updated version of Zitadel 4! [Learn more](https://github.com/zitadel/zitadel/issues/10526)
Please use Docker version if you want to use Zitadel 4.
Install PostgreSQL [#install-postgre-sql]
Download a `postgresql` binary as described [in the PostgreSQL docs](https://www.postgresql.org/download/linux/).
Zitadel is tested against PostgreSQL latest stable tag and latest Ubuntu LTS.
Run PostgreSQL [#run-postgre-sql]
```bash
sudo systemctl start postgresql
sudo systemctl enable postgresql
```
Install Zitadel [#install-zitadel]
Download the Zitadel release according to your architecture from [Github](https://github.com/zitadel/zitadel/releases/latest), unpack the archive and copy zitadel binary to /usr/local/bin
```bash
LATEST=$(curl -i https://github.com/zitadel/zitadel/releases/latest | grep location: | cut -d '/' -f 8 | tr -d '\r'); ARCH=$(uname -m); case $ARCH in armv5*) ARCH="armv5";; armv6*) ARCH="armv6";; armv7*) ARCH="arm";; aarch64) ARCH="arm64";; x86) ARCH="386";; x86_64) ARCH="amd64";; i686) ARCH="386";; i386) ARCH="386";; esac; wget -c https://github.com/zitadel/zitadel/releases/download/$LATEST/zitadel-linux-$ARCH.tar.gz -O - | tar -xz && sudo mv zitadel-linux-$ARCH/zitadel /usr/local/bin
```
Run Zitadel [#run-zitadel]
```bash
ZITADEL_DATABASE_POSTGRES_DSN=postgresql://root:postgres@localhost:5432/postgres?sslmode=disable ZITADEL_EXTERNALSECURE=false zitadel start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled
```
VideoGuide [#video-guide]
Setup Zitadel with a service account [#setup-zitadel-with-a-service-account]
```bash
ZITADEL_DATABASE_POSTGRES_DSN=postgresql://root:postgres@localhost:5432/postgres?sslmode=disable ZITADEL_EXTERNALSECURE=false ZITADEL_FIRSTINSTANCE_MACHINEKEYPATH=/tmp/zitadel-admin-sa.json ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME=zitadel-admin-sa ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_NAME=Admin ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINEKEY_TYPE=1 zitadel start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled
# then you can move your machine key
mv /tmp/zitadel-admin-sa.json $HOME/zitadel-admin-sa.json
```
This key can be used to provision resources with for example [Terraform](/guides/manage/terraform-provider).
# Install ZITADEL on MacOS
Install PostgreSQL [#install-postgre-sql]
Download a `postgresql` binary as described [in the PostgreSQL docs](https://www.postgresql.org/download/macosx/).
ZITADEL is tested against PostgreSQL latest stable tag and latest Ubuntu LTS.
Run PostgreSQL [#run-postgre-sql]
```bash
sudo pg_ctl init -D /tmp/postgresql
sudo pg_ctl start -D /tmp/postgresql
```
Install ZITADEL [#install-zitadel]
Homebrew [#homebrew]
```bash
brew install zitadel/tap/zitadel
```
Download from GitHub [#download-from-git-hub]
Download the ZITADEL release according to your architecture from [Github](https://github.com/zitadel/zitadel/releases/latest)
Unpack the archive [#unpack-the-archive]
move to your download location and unpack the archive
```bash
#unpack and copy to /usr/local/bin
LATEST=$(curl -i https://github.com/zitadel/zitadel/releases/latest | grep location: | cut -d '/' -f 8 | tr -d '\r'); wget -qO- https://github.com/zitadel/zitadel/releases/download/$LATEST/zitadel_Darwin_$(uname -m).tar.gz | tar -xJ zitadel && sudo mv zitadel /usr/local/bin
```
Run ZITADEL [#run-zitadel]
```bash
ZITADEL_DATABASE_POSTGRES_DSN="postgresql://root:postgres@localhost:5432/postgres?sslmode=disable" ZITADEL_EXTERNALSECURE=false zitadel start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled
```
VideoGuide [#video-guide]
Setup ZITADEL with a service account [#setup-zitadel-with-a-service-account]
```bash
ZITADEL_DATABASE_POSTGRES_DSN="postgresql://root:postgres@localhost:5432/postgres?sslmode=disable" ZITADEL_EXTERNALSECURE=false ZITADEL_FIRSTINSTANCE_MACHINEKEYPATH=/tmp/zitadel-admin-sa.json ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME=zitadel-admin-sa ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_NAME=Admin ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINEKEY_TYPE=1 zitadel start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled
# then you can move your machine key
mv /tmp/zitadel-admin-sa.json $HOME/zitadel-admin-sa.json
```
This key can be used to provision resources with for example [Terraform](/guides/manage/terraform-provider).
# Deploy ZITADEL
Choose your platform and run ZITADEL with the most minimal configuration possible.
* [Linux](./linux)
* [MacOS](./macos)
* [Docker Compose](./compose)
* [Kubernetes](./kubernetes)
Prerequisites [#prerequisites]
* For test environments, ZITADEL does not need many resources, 1 CPU and 512MB memory are more than enough. (With more CPU, the password hashing might be faster)
* A PostgreSQL instance. Make sure to check the **database configuration options** in our [Production Guide](/self-hosting/manage/production#prefer-postgre-sql).
Releases [#releases]
The easiest way to use ZITADEL is to run one of our container releases
* ZITADEL does provide latest and stable [container images](https://github.com/zitadel/zitadel/pkgs/container/zitadel)
* **stable** is the current **production** release of ZITADEL.
* **latest** is the **last created** release from our pipelines that gets updated in a high frequency.
Production Setup [#production-setup]
After you have successfully created your first test environment using one of the deployment guides in this section,
you might want to configure ZITADEL for production and embed it into your system landscape.
To do so, jump straight to the [production setup guide](/self-hosting/manage/production).
To achieve high availability, we recommend using a [Kubernetes](https://kubernetes.io/docs/home/) Cluster.
We have an official [Helm chart](https://artifacthub.io/packages/helm/zitadel/zitadel) for easy deployment and maintenance.
# Caches
ZITADEL supports the use of a caches to speed up the lookup of frequently needed objects. As opposed to HTTP caches which might reside between ZITADEL and end-user applications, the cache build into ZITADEL uses active invalidation when an object gets updated. Another difference is that HTTP caches only cache the result of a complete request and the built-in cache stores objects needed for the internal business logic. For example, each request made to ZITADEL needs to retrieve and set [instance](/concepts/structure/instance) information in middleware.
Caches is currently an [experimental beta](https://help.zitadel.com/zitadel-software-release-cycle#beta) feature.
Test the feature and add improvement or bug reports directly to the [github repository](https://github.com/zitadel/zitadel) or let us know your general feedback in the [discord thread](https://discord.com/channels/927474939156643850/1332343909900222506)!
Settings [#settings]
The `Caches` settings entry defines *connectors* which can be used by several objects. It is possible to mix *connectors* with different objects based on operational needs.
```yaml
Caches:
Connectors:
SomeConnector:
Enabled: true
SomeOption: foo
SomeObject:
# Connector must be enabled above.
# When connector is empty, this cache will be disabled.
Connector: "SomeConnector"
MaxAge: 1h
LastUsage: 10m
# Log enables cache-specific logging. Default to error log to stderr when omitted.
Log:
Level: error
```
For a full settings reference, please see the [runtime configuration file](/self-hosting/manage/configure/configure#runtime-configuration-file) section's `defaults.yaml`.
Connectors [#connectors]
ZITADEL supports a number of *connectors*. Connectors integrate a cache with a storage backend. Users can combine connectors with the type of object cache depending on their operational and performance requirements.
When no connector is specified for an object cache, then no caching is performed. This is the current default.
Auto prune [#auto-prune]
Some connectors take an `AutoPrune` option. This is provided for caches which don't have built-in expiry and cleanup routines. The auto pruner is a routine launched by ZITADEL and scans and removes outdated objects in the cache. Pruning can take a cost as they typically involve some kind of scan. However, using a long interval can cause higher storage utilization.
```yaml
Caches:
Connectors:
Memory:
Enabled: true
# AutoPrune removes invalidated or expired object from the cache.
AutoPrune:
Interval: 1m
TimeOut: 5s
```
Redis cache [#redis-cache]
Redis is supported in simple mode. Cluster and Sentinel are not yet supported. There is also a circuit-breaker provided which prevents a single point of failure, should the single Redis instance become unavailable.
Benefits:
* Centralized cache with single source of truth
* Consistent invalidation
* Very fast when network latency is kept to a minimum
* Built-in object expiry, no pruner required
Drawbacks:
* Increased operational overhead: need to run a Redis instance as part of your infrastructure.
* When running multiple servers of ZITADEL in different regions, network roundtrip time might impact performance, neutralizing the benefit of a cache.
Connect to Redis by providing a URL via environment variable. Use `redis://` for plain connections or `rediss://` for TLS:
```bash
export ZITADEL_CACHES_CONNECTORS_REDIS_ENABLED=true
export ZITADEL_CACHES_CONNECTORS_REDIS_URL=redis://user:password@localhost:6379
```
If you prefer configuration files, set the URL in your YAML config:
```yaml
Caches:
Connectors:
Redis:
Enabled: true
URL: redis://user:password@localhost:6379
```
Pool, timeout, retry, and circuit breaker settings can be configured alongside the URL as overlays. The DB number in the URL path is ignored for cache DB selection — ZITADEL derives the actual DB from `DBOffset` + purpose.
Circuit breaker [#circuit-breaker]
A [circuit breaker](https://learn.microsoft.com/en-us/previous-versions/msp-n-p/dn589784\(v=pandp.10\)?redirectedfrom=MSDN) is provided for the Redis connector, to prevent a single point of failure in the case persistent errors. When the circuit breaker opens, the cache is temporary disabled and ignored. ZITADEL will continue to operate using queries to the database.
```yaml
Caches:
Connectors:
Redis:
Enabled: true
URL: redis://user:password@localhost:6379
# Many other options...
CircuitBreaker:
# Interval when the counters are reset to 0.
# 0 interval never resets the counters until the CB is opened.
Interval: 0
# Amount of consecutive failures permitted
MaxConsecutiveFailures: 5
# The ratio of failed requests out of total requests
MaxFailureRatio: 0.1
# Timeout after opening of the CB, until the state is set to half-open.
Timeout: 60s
# The allowed amount of requests that are allowed to pass when the CB is half-open.
MaxRetryRequests: 1
```
PostgreSQL cache [#postgre-sql-cache]
PostgreSQL can be used to store objects in unlogged tables. [Unlogged tables](https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-UNLOGGED) do not write to the WAL log and are therefore faster than regular tables. If the PostgreSQL server crashes, the data from those tables are lost. ZITADEL always creates the cache schema in the `zitadel` database during [setup](./updating_scaling#the-setup-phase). This connector requires a [pruner](#auto-prune) routine.
Benefits:
* Centralized cache with single source of truth
* No operational overhead. Reuses the query connection pool and the existing `zitadel` database.
* Consistent invalidation
* Faster than regular queries which often contain `JOIN` clauses.
Drawbacks:
* Slowest of the available caching options
* Might put additional strain on the database server, limiting horizontal scalability
Local memory cache [#local-memory-cache]
ZITADEL is capable of caching object in local application memory, using hash-maps. Each ZITADEL server manages its own copy of the cache. This connector requires a [pruner](#auto-prune) routine.
Benefits:
* Fastest of the available caching options
* No operational overhead
Drawbacks:
* Inconsistent invalidation. An object validated in one ZITADEL server will not get invalidated in other servers.
* There's no single source of truth. Different servers may operate on a different version of an object
* Data is duplicated in each server, consuming more total memory inside a deployment.
The drawbacks restricts its usefulness in distributed deployments. However simple installations running a single server can benefit greatly from this type of cache. For example test, development or home deployments.
If inconsistency is acceptable for short periods of time, one can choose to use this type of cache in distributed deployments with short max age configuration.
**For example**: A ZITADEL deployment with 2 servers is serving 1000 req/sec total. The installation only has one instance[^1]. There is only a small amount of data cached (a few kB) so duplication is not a problem in this case. It is acceptable for [instance level setting](/guides/manage/console/default-settings) to be out-dated for a short amount of time. When the memory cache is enabled for the instance objects, with a max age of 1 second, the instance only needs to be obtained from the database 2 times per second (once for each server). Saving 998 of redundant queries. Once an instance level setting is changed, it takes up to 1 second for all the servers to get the new state.
Objects [#objects]
The following section describes the type of objects ZITADEL can currently cache. Objects are actively invalidated at the cache backend when one of their properties is changed. Each object cache defines:
* `Connector`: Selects the used [connector](#connectors) back-end. Must be activated first.
* `MaxAge`: the amount of time that an object is considered valid. When this age is passed the object is ignored (cache miss) and possibly cleaned up by the [pruner](#auto-prune) or other built-in garbage collection.
* `LastUsage`: defines usage based lifetime. Each time an object is used, its usage timestamp is updated. Popular objects remain cached, while unused objects are cleaned up. This option can be used to indirectly limit the size of the cache.
* `Log`: allows specific log settings for the cache. This can be used to debug a certain cache without having to change the global log level.
```yaml
Caches:
SomeObject:
# Connector must be enabled above.
# When connector is empty, this cache will be disabled.
Connector: ""
MaxAge: 1h
LastUsage: 10m
# Log enables cache-specific logging. Default to error log to stderr when omitted.
Log:
Level: error
AddSource: true
Formatter:
Format: text
```
Instance [#instance]
All HTTP and gRPC requests sent to ZITADEL receive an instance context. The instance is usually resolved by the domain from the request. In some cases, like the [system service](/reference/api/system), the instance can be resolved by its ID. An instance object contains many of the [default settings](/reference/api/system), the instance can be resolved by its ID. An instance object contains many of the [default settings](/guides/manage/console/default-settings):
* Instance [features](/guides/manage/console/default-settings#features)
* Custom Domains: generated and [custom](/guides/manage/cloud/instances#add-custom-domain)
* [Trusted domains](/apis/resources/admin/admin-service-add-instance-trusted-domain)
* Security settings ([IFrame policy](/guides/solution-scenarios/configurations#embedding-zitadel-in-an-iframe))
* Limits[^2]
* [Allowed languages](/guides/manage/console/default-settings#languages)
These settings typically change infrequently in production. ***Every*** request made to ZITADEL needs to query for the instance. This is a typical case of set once, get many times where a cache can provide a significant optimization.
Milestones [#milestones]
Milestones are used to track the administrator's progress in setting up their instance. Milestones are used to render *your next steps* in the [console](/guides/manage/console/console-overview) landing page.
Milestones are reached upon the first time a certain action is performed. For example the first application created or the first human login. In order to push a "reached" event only once, ZITADEL must keep track of the current state of milestones by an eventstore query every time an eligible action is performed. This can cause an unwanted overhead on production servers, therefore they are cached.
As an extra optimization, once all milestones are reached by the instance, an in-memory flag is set and the milestone state is never queried again from the database nor cache.
For single instance setups which fulfilled all milestone (*your next steps* in the management console) it is not needed to enable this cache. We mainly use it for ZITADEL cloud where there are many instances with *incomplete* milestones.
Organization [#organization]
Most resources like users, project and applications are part of an [organization](/guides/manage/console/organizations-overview). Therefore many parts of the ZITADEL logic search for an organization by ID or by their Organization Domain.
Organization objects are quite small and receive infrequent updates after they are created:
* Change of organization name
* Deactivation / Reactivation
* Change of Organization Domain
* Removal
Examples [#examples]
Currently caches are in beta and disabled by default. However, if you want to give caching a try, the following sections contains some suggested settings for different setups.
The following setup is recommended for single instance setups with a single ZITADEL server:
```yaml
Caches:
Memory:
Enabled: true
Instance:
Connector: "memory"
MaxAge: 1h
Organization:
Connector: "memory"
MaxAge: 1h
```
The following setup is recommended for single instance setups with high traffic on multiple servers, where Redis is not available:
```yaml
Caches:
Memory:
Enabled: true
Postgres:
Enabled: true
Instance:
Connector: "memory"
MaxAge: 1s
Milestones:
Connector: "postgres"
MaxAge: 1h
LastUsage: 10m
Organization:
Connector: "memory"
MaxAge: 1s
```
When running many instances on multiple servers, set the Redis URL and enable the connector:
```bash
export ZITADEL_CACHES_CONNECTORS_REDIS_ENABLED=true
export ZITADEL_CACHES_CONNECTORS_REDIS_URL=redis://user:password@redis.example.com:6379
export ZITADEL_CACHES_INSTANCE_CONNECTOR=redis
export ZITADEL_CACHES_MILESTONES_CONNECTOR=redis
export ZITADEL_CACHES_ORGANIZATION_CONNECTOR=redis
```
Or in a configuration file:
```yaml
Caches:
Connectors:
Redis:
Enabled: true
URL: redis://user:password@redis.example.com:6379
Instance:
Connector: "redis"
MaxAge: 1h
LastUsage: 10m
Milestones:
Connector: "redis"
MaxAge: 1h
LastUsage: 10m
Organization:
Connector: "redis"
MaxAge: 1h
LastUsage: 10m
```
***
[^1]: Many deployments of ZITADEL have only one or few [instances](/concepts/structure/instance). Multiple instances are mostly used for ZITADEL cloud, where each customer gets at least one instance.
[^2]: Limits are imposed by the system API, usually when customers exceed their subscription in ZITADEL cloud.
# External ZITADEL Access
Why do I get an "Instance not found" error? [#why-do-i-get-an-instance-not-found-error]
Also, ZITADEL has the [concept of virtual instances](/concepts/structure/instance#multiple-virtual-instances).
It uses a requests Host header to determine which virtual instance to use.
This is useful for multi-tenancy and resource sharing, for example in SaaS scenarios.
For most cases however, ZITADEL should run on exactly one domain.
This guide assumes you are already familiar with [configuring ZITADEL](./configure).
Standard Config [#standard-config]
ZITADEL only serves requests sent to the expected protocol, host and port.
For local testing purposes, you can use following configuration:
```yaml
ExternalDomain: localhost
ExternalPort: 8080
ExternalSecure: false
```
For productive setups however, we recommend using HTTPS and a Custom Domain:
```yaml
ExternalDomain: 'zitadel.my.domain'
ExternalPort: 443
ExternalSecure: true
```
Changing ExternalDomain, ExternalPort or ExternalSecure [#changing-external-domain-external-port-or-external-secure]
You can change the ExternalDomain, ExternalPort and ExternalSecure configuration options at any time.
However, for ZITADEL to be able to pick up the changes, [you need to rerun ZITADELs setup phase](/self-hosting/manage/updating_scaling#the-setup-phase).
Running ZITADEL behind a Reverse Proxy [#running-zitadel-behind-a-reverse-proxy]
If you run ZITADEL behind a reverse proxy, you need to ensure that it sends the correct request headers to ZITADEL.
The proxy must either ensure that
* the original *Host* header value is assigned to the *Forwarded* headers host directive.
* the original requests *Host* header value is unchanged by the proxy.
Check out the [reverse proxy configuration examples](/self-hosting/manage/reverseproxy/reverse_proxy) for more information.
Organization Domains [#organization-domains]
Note that by default, you cannot access ZITADEL at an organization's domain.
Organization level domains [are intended for routing users by their login methods to their correct organization](/guides/solution-scenarios/domain-discovery).
However, if you want to access ZITADEL at an Organization Domain, [you can add additional domains using the System API](/reference/api/system/zitadel.system.v1.SystemService.AddDomain).
Be aware that you won't automatically have the organizations context when you access ZITADEL like this.
Generated Subdomains [#generated-subdomains]
ZITADEL creates random subdomains for [each new virtual instance](/concepts/structure/instance#multiple-virtual-instances).
You can immediately access the ZITADEL Management Console and APIs using these subdomains without further actions.
More Information [#more-information]
* [Check out the production-near load-balancing example with Traefik](/self-hosting/manage/reverseproxy/traefik)
* [Explore some concrete proxy configuration examples for ZITADEL using the domain 127.0.0.1.sslip.io](/self-hosting/manage/reverseproxy/reverse_proxy)
# HTTP/2 Support in ZITADEL
ZITADEL follows a strict API first approach and makes heavy use of the modern API framework called [gRPC](https://grpc.io/).
Besides gRPC all APIs are also available in an openapi Rest fashion as well as in gRPC-web for compatibility towards browser integrations.
To make use of gRPC it is vital to allow your applications to communicate with ZITADEL with [HTTP/2](https://en.wikipedia.org/wiki/HTTP/2).
Sometimes you need to configure explicitly that you want to use HTTP/2 if you run ZITADEL behind a reverse proxy and below you should find examples for different vendors and projects.
Furthermore it is important to notice that by default HTTP/2 is always encrypted, but if you want to run ZITADEL without TLS from your reverse proxy or service mesh this is possible through [h2c](https://httpd.apache.org/docs/2.4/howto/http2.html).
Oftentimes when you run ZITADEL inside a service mesh, or a serverless offering (e.g. Google Cloud Run, Knative, ...) you will need h2c.
You can read more about ZITADEL's [TLSs modes here](/self-hosting/manage/tls_modes).
# Connect your Self-Hosted Login UI to Zitadel
To enable your self-hosted Login UI to connect to the Zitadel API, it needs a token for a user with the IAM\_LOGIN\_CLIENT role.
On new installations, the Zitadel setup job can be configured to automatically write a Personal Access Token (PAT) for the login client.
Check out [one of the deployment examples](https://zitadel.com/docs/self-hosting/deploy/overview) to learn how to do this.
However, if you want to replace the v1 login of an existing installation by a self-hosted v2 login, the setup job won't execute these steps.
In that case, you can create a new PAT for the login client manually.
Create a Login Client User [#create-a-login-client-user]
In the following URLs, replace the base URL and the user ID according to your environment.
1. Create a new service account, for example at [http://localhost:8080/ui/console/users/create-machine](http://localhost:8080/ui/console/users/create-machine)
2. Create a PAT, for example at [http://localhost:8080/ui/console/users/332169800719532035?new=true\&id=pat](http://localhost:8080/ui/console/users/332169800719532035?new=true\&id=pat)
3. Save the PAT to a file, for example `/path/on/your/host/login-client.pat`
4. Make sure the user has the `Instance Login Client` role (internally called `IAM_LOGIN_CLIENT`), for example at [http://localhost:8080/ui/console/instance/members](http://localhost:8080/ui/console/instance/members)
Configure the Login UI [#configure-the-login-ui]
Make sure your Login UI has the environment variable `ZITADEL_SERVICE_USER_TOKEN` set with your PAT.
If you run the Login UI with Docker, you can also mount the file into the container and reference it by passing the environment variable `ZITADEL_SERVICE_USER_TOKEN_FILE`.
For example:
```bash
docker run -p 3000:3000 -v /path/on/your/host/login-client.pat:/path/in/container/login-client.pat:ro -e ZITADEL_SERVICE_USER_TOKEN_FILE=/path/in/container/login-client.pat ghcr.io/zitadel/zitadel-login:latest
```
Enable the Login UI for all users [#enable-the-login-ui-for-all-users]
Before doing this, make sure you have a working PAT for an instance Owner user.
In case something goes wrong and you lock yourself out from the login screen, you can revert the changes.
Create a service account PAT like you created the [login client PAT above](#create-login-client), but give the user the instance Owner role (internally called `IAM_OWNER`).
Enable the `Login V2` feature flag, for example at the bottom of [http://localhost:8080/ui/console/instance?id=features](http://localhost:8080/ui/console/instance?id=features).
Enter the base URI of your Login UI, for example `http://localhost:3000/ui/v2/login`.
Test [#test]
That's it!
Click your users avatar in the top right corner of the management console and select `Log in With Another Account`.
You should see the new Login UI.
# ZITADEL Production Setup
As soon as you successfully deployed ZITADEL as a proof of concept using one of our [deployment guides](/self-hosting/deploy/overview),
you are ready to configure ZITADEL for production usage.
TL;DR [#tl-dr]
We created a [Production Checklist](./productionchecklist) as a technical step by step guide.
High Availability [#high-availability]
We recommend running ZITADEL highly available using an orchestrator that schedules ZITADEL on multiple servers,
like [Kubernetes](/self-hosting/deploy/kubernetes).
For keeping startup times fast when scaling ZITADEL,
you should also consider using separate jobs with `zitadel init` and `zitadel setup`,
so your workload containers just have to execute `zitadel start`.
Read more about separating the init and setup phases on the [Updating and Scaling page](/self-hosting/manage/updating_scaling).
Setup [#setup]
Read [on the configure page](/self-hosting/manage/configure) about the available options you have to configure ZITADEL.
Prefer passing .yaml files to the ZITADEL binary instead of environment variables.
Restricting access to these files to avoid leaking sensitive information is easier than restricting access to environment variables.
Also, not all configuration options are available as environment variables.
Networking [#networking]
* To make ZITADEL available at the domain of your choice, [you need to configure the ExternalDomain property](/self-hosting/manage/custom-domain).
* To enable and restrict access to **HTTPS**, head over to [the description of your TLS options](/self-hosting/manage/tls_modes).
* If you want to front ZITADEL with a reverse proxy, web application firewall or content delivery network, make sure to support **[HTTP/2](/self-hosting/manage/http2)**.
* You can also refer to some **[example reverse proxy configurations](/self-hosting/manage/reverseproxy/reverse_proxy)**.
* The ZITADEL Management Console web GUI uses many gRPC-Web stubs. This results in a fairly big JavaScript bundle. You might want to compress it using [Gzip](/self-hosting/manage/custom-domain) or [Brotli](https://www.gnu.org/software/gzip/) or [Brotli](https://github.com/google/brotli).
* Serving and caching the assets using a content delivery network could improve network latencies and shield your ZITADEL runtime.
Monitoring [#monitoring]
By default, [**metrics**](/apis/observability/metrics) are exposed at /debug/metrics in OpenTelemetry (otel) format.
Also, you can enable **tracing** in the ZITADEL configuration.
```yaml
Tracing:
# Choose one in "otel", "google", "log" and "none"
Type: google
Fraction: 1
MetricPrefix: zitadel
```
Logging [#logging]
ZITADEL follows the principles that guide cloud-native and twelve factor applications.
Logs are a stream of time-ordered events collected from all running processes.
[ZITADEL is configurable](#default-zitadel-logging-config) to write the following events to the standard output:
* Runtime Logs: Define the log level and record format in the `Log` configuration section.
* Access Logs: Enable logging all HTTP and gRPC responses from the ZITADEL binary by setting `LogStore.Access.Stdout.Enabled` to true.
* Actions Execution Logs: Actions can emit custom logs at different levels. For example, a log record can be emitted each time a user is created or authenticated. If you don't want to have these logs in STDOUT, you can disable this by setting `LogStore.Execution.Stdout.Enabled` to true.
Default ZITADEL Logging Config [#default-zitadel-logging-config]
```yaml
Log:
Level: info # ZITADEL_LOG_LEVEL
Formatter:
Format: text # ZITADEL_LOG_FORMATTER_FORMAT
LogStore:
Access:
Stdout:
# If enabled, all access logs are printed to the binary's standard output
Enabled: false # ZITADEL_LOGSTORE_ACCESS_STDOUT_ENABLED
Execution:
Stdout:
# If enabled, all execution logs are printed to the binary's standard output
Enabled: true # ZITADEL_LOGSTORE_EXECUTION_STDOUT_ENABLED
```
Why ZITADEL does not write logs to files [#why-zitadel-does-not-write-logs-to-files]
Log file management should not be in each business apps responsibility.
Instead, your execution environment should provide tooling for managing logs in a generic way.
This includes tasks like rotating files, routing, collecting, archiving and cleaning-up.
For example, systemd has journald and kubernetes has fluentd and fluentbit.
Telemetry [#telemetry]
If you want to have some data about reached usage milestones pushed to external systems, enable telemetry in the ZITADEL configuration.
The following table describes the milestones that are sent to the endpoints:
| Trigger | Description |
| ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A virtual instance is created. | This data point is also sent when the first instance is automatically created during the ZITADEL binaries setup phase in a self-hosting scenario. |
| An authentication succeeded for the first time on an instance. | This is the first authentication with the instances automatically created admin user during the instance setup, which can be a user or a service account. |
| A project is created for the first time in a virtual instance. | The ZITADEL project that is automatically created during the instance setup is omitted. |
| An application is created for the first time in a virtual instance. | The applications in the ZITADEL project that are automatically created during the instance setup are omitted. |
| An authentication succeeded for the first time in a virtual instances application. | This is the first authentication using a ZITADEL application that is not created during the instance setup phase. |
| A virtual instance is deleted. | This data point is sent when a virtual instance is deleted via ZITADELs system API |
ZITADEL pushes the metrics by projecting certain events.
Therefore, you can configure delivery guarantees not in the Telemetry section of the ZITADEL configuration,
but in the Projections.Customizations.Telemetry section
Database [#database]
Prefer PostgreSQL [#prefer-postgre-sql]
ZITADEL supports [PostgreSQL](https://www.postgresql.org/).
ZITADEL supports PostgreSQL. For migrating from CockroachDB to PostgreSQL, please refer to [the mirror guide](/self-hosting/manage/cli/mirror).
The indexes for the database are optimized using load tests from [ZITADEL Cloud](https://zitadel.com),
which runs with PostgreSQL.
If you identify problems with your database during load tests that indicate that the indexes are not optimized,
please create an issue in our [github repository](https://github.com/zitadel/zitadel).
Configure ZITADEL [#configure-zitadel]
Depending on your environment, you maybe would want to tweak some settings about how ZITADEL interacts with the database in the database section of your ZITADEL configuration. Read more about your [database configuration options](/self-hosting/manage/database).
Set the database connection via environment variable:
```bash
export ZITADEL_DATABASE_POSTGRES_DSN=postgresql://zitadel:password@localhost:5432/zitadel?sslmode=disable
export ZITADEL_DATABASE_POSTGRES_MAXOPENCONNS=10
export ZITADEL_DATABASE_POSTGRES_MAXIDLECONNS=5
export ZITADEL_DATABASE_POSTGRES_MAXCONNLIFETIME=30m
export ZITADEL_DATABASE_POSTGRES_MAXCONNIDLETIME=5m
```
Or in a configuration file:
```yaml
Database:
postgres:
DSN: postgresql://zitadel:password@localhost:5432/zitadel?sslmode=disable
//highlight-start
MaxOpenConns: 10
MaxIdleConns: 5
MaxConnLifetime: 30m
MaxConnIdleTime: 5m
//highlight-end
```
You also might want to configure how [projections](/concepts/eventstore/implementation#projections) are computed. These are the default values:
```yaml
# The Projections section defines the behavior for the scheduled and synchronous events projections.
Projections:
# Time interval between scheduled projections
RequeueEvery: 60s
# Time between retried database statements resulting from projected events
RetryFailedAfter: 1s
# Retried execution number of database statements resulting from projected events
MaxFailureCount: 5
# Number of concurrent projection routines. Values of 0 and below are overwritten to 1
ConcurrentInstances: 1
# Limit of returned events per query
BulkLimit: 200
# Only instance are projected, for which at least a projection relevant event exists withing the timeframe
# from HandleActiveInstances duration in the past until the projections current time
# Defaults to twice the RequeueEvery duration
HandleActiveInstances: 120s
# In the Customizations section, all settings from above can be overwritten for each specific projection
Customizations:
Projects:
BulkLimit: 2000
# The Notifications projection is used for sending emails and SMS to users
Notifications:
# As notification projections don't result in database statements, retries don't have an effect
MaxFailureCount: 0
# The NotificationsQuotas projection is used for calling quota webhooks
NotificationsQuotas:
# Delivery guarantee requirements are probably higher for quota webhooks
# Defaults to 45 days
HandleActiveInstances: 1080h
# As quota notification projections don't result in database statements, retries don't have an effect
MaxFailureCount: 0
# Quota notifications are not so time critical. Setting RequeueEvery every five minutes doesn't annoy the db too much.
RequeueEvery: 300s
```
Manage your data [#manage-your-data]
When designing your backup strategy,
it is worth knowing that
[ZITADEL is event sourced](/concepts/eventstore/overview).
That means, ZITADEL itself is able to recompute its
whole state from the records in the table eventstore.events.
The timestamp of your last record in the events table
defines up to which point in time ZITADEL can restore its state.
The ZITADEL binary itself is stateless,
so there is no need for a special backup job.
Generally, for maintaining your database management system in production,
please refer to the corresponding docs [for PostgreSQL](https://www.postgresql.org/docs/current/admin.html).
Data initialization [#data-initialization]
* You can configure instance defaults in the DefaultInstance section.
If you plan to eventually create [multiple virtual instances](/concepts/structure/instance#multiple-virtual-instances), these defaults take effect.
Also, these settings apply to the first instance, that ZITADEL automatically creates for you.
Especially the following properties are of special interest for your production setup.
```yaml
DefaultInstance:
OIDCSettings:
AccessTokenLifetime: 12h
IdTokenLifetime: 12h
RefreshTokenIdleExpiration: 720h #30d
RefreshTokenExpiration: 2160h #90d
# this sets the default email settings
SMTPConfiguration:
# settings of the host
SMTP:
#for example smtp.mailtrap.io:2525
Host:
User:
Password:
TLS:
# if the host of the sender is different from ExternalDomain set DefaultInstance.DomainPolicy.SMTPSenderAddressMatchesInstanceDomain to false
From:
FromName:
ReplyToAddress:
```
* If you don't want to use the DefaultInstance configuration for the first instance that ZITADEL automatically creates for you during the [setup phase](/self-hosting/manage/configure/configure#database-initialization-file), you can provide a FirstInstance YAML section using the --steps argument.
* Learn how to configure ZITADEL via the [Console user interface](/guides/manage/console/console-overview).
* Probably, you also want to [apply your custom branding](/guides/manage/customize/branding), [hook into certain events](/guides/manage/customize/behavior), [customize texts](/guides/manage/customize/texts) or [add metadata to your users](/guides/manage/customize/user-metadata).
* If you want to automatically create ZITADEL resources, you can use the [ZITADEL Terraform Provider](/guides/manage/terraform-provider).
Limits and Quotas [#limits-and-quotas]
If you host ZITADEL as a service,
you might want to [limit usage and/or execute tasks on certain usage units and levels](/self-hosting/manage/usage_control).
Minimum system requirements [#minimum-system-requirements]
General resource usage [#general-resource-usage]
ZITADEL itself requires approximately 512MB of RAM and can operate with less than one CPU core. The database component, under typical conditions, utilizes about one CPU core per 100 requests per second (req/s) and 4GB of RAM per core, which includes some caching.
Be aware of CPU spikes when hashing passwords. We recommend to have 4 CPU cores available for this purpose.
Production HA cluster [#production-ha-cluster]
For a minimal high-availability setup, we recommend a cluster of 3 nodes, each with 4 CPU cores and 16GB of memory.
If you exclude non-essential services like log collection and metrics, you can reduce the resources to approximately 4 CPU cores and 8GB of memory per node.
# ZITADEL Production Checklist
To apply best practices to your production setup we created a step by step checklist you may wish to follow.
Infrastructure Setup [#infrastructure-setup]
* [ ] Make use of infrastructure as code tools such as Terraform to provision all of the below
* [ ] Use a secrets manager to store your confidential information
* [ ] Reduce the manual interaction with your platform to an absolute minimum
HA Setup [#ha-setup]
* [ ] High Availability for ZITADEL containers
* [ ] Use a container orchestrator such as Kubernetes
* [ ] Use serverless platform such as Knative or a hyperscaler equivalent (e.g. CloudRun from Google)
* [ ] Split `zitadel init` and `zitadel setup` for fast start-up times when [scaling](/self-hosting/manage/updating_scaling) ZITADEL
* [ ] High Availability for database
* [ ] Follow [this guide](https://www.postgresql.org/docs/current/high-availability.html) to set up the database.
* [ ] Configure logging
* [ ] Configure timeouts
* [ ] Configure backups on a regular basis for the database
* [ ] Test the restore scenarios before going live
* [ ] Secure database connections from outside your network and/or use an internal subnet for database connectivity
* [ ] High Availability for critical infrastructure components (depending on your setup)
* [ ] Load-balancer
* [ ] [Reverse Proxies](/self-hosting/manage/reverseproxy/reverse_proxy)
* [ ] Web Application Firewall
Networking [#networking]
* [ ] Use a Layer 7 Web Application Firewall to secure ZITADEL that supports **[HTTP/2](/self-hosting/manage/http2)**
* [ ] Limit the access by IP addresses if needed
* [ ] Secure the access by rate limits for specific endpoints (e.g. API vs frontend) to secure availability on high load. See the [ZITADEL Cloud rate limits](/legal/policies/rate-limit-policy) for reference.
* [ ] Check that your firewall also filters IPv6 traffic
ZITADEL setup [#zitadel-setup]
* [ ] Set up a valid [SMTP Server](/guides/manage/console/default-settings#smtp) and test the email delivery
* [ ] Add [Custom Branding](/guides/manage/customize/branding) if required
* [ ] Set up a valid [SMS Service](/guides/manage/console/default-settings#sms) such as Twilio if needed
* [ ] Set your privacy policy, terms of service and a help Link if needed
* [ ] Keep your [masterkey](/self-hosting/manage/configure) in a secure storage
* [ ] Declare and apply zitadel settings using the zitadel terraform [provider](https://github.com/zitadel/terraform-provider-zitadel)
Security [#security]
* [ ] Ensure that your ZITADEL does not use [the default, example or *easy-to-guess* credentials](/self-hosting/manage/database#zitadel-credentials)
* [ ] Use a FQDN and a trusted valid certificate for external [TLS](/self-hosting/manage/tls_modes) connections
* [ ] Create service accounts for applications that interact with ZITADEL's APIs
* [ ] Make use of a CDN service to decrease the load for static assets served by ZITADEL
* [ ] Make use of a [security scanner](https://owasp.org/www-community/Vulnerability_Scanning_Tools) to test your application and deployment environment
Monitoring [#monitoring]
Use an appropriate monitoring solution to have an overview about your ZITADEL instance. In particular, you may want to watch out for things like:
* [ ] CPU and memory of ZITADEL and the database
* [ ] Open database connections
* [ ] Running instances of ZITADEL and the database
* [ ] Latency of requests
* [ ] Requests per second
* [ ] Requests by URL/endpoint
* [ ] Lifetime of TLS certificates
* [ ] ZITADEL and database logs
* [ ] ZITADEL [metrics](/apis/observability/metrics)
# ZITADEL Requirements
This page lists the infrastructure versions that ZITADEL is tested against and officially supports.
Database [#database]
ZITADEL requires a PostgreSQL database. Versions **14 to 18** are supported.
See the [Database](/self-hosting/manage/database) page for full configuration options, how to pre-create users and databases, and how to rotate credentials.
Cache (optional) [#cache-optional]
ZITADEL supports Redis as an optional cache to speed up lookups of frequently needed objects.
Redis must run in **standalone mode** (Cluster and Sentinel are not yet supported).
See the [Caches](/self-hosting/manage/cache) page for configuration options.
Container runtime [#container-runtime]
* **Docker Compose**: v2.x (Compose V1 `docker-compose` CLI is not supported)
* **Kubernetes**: see the [Kubernetes deployment guide](/self-hosting/deploy/kubernetes)
Reverse proxy [#reverse-proxy]
Any reverse proxy that supports HTTP/2 upstream connections (h2c or h2) works with ZITADEL.
The following proxies are tested and have dedicated configuration guides:
* **Traefik**: v3.x
* **NGINX**: v1.x stable releases
* **Caddy**: v2.x
* **Apache httpd**: 2.4.x
See the [Reverse Proxy Configuration](/self-hosting/manage/reverseproxy/reverse_proxy) page for details and Docker Compose examples.
# Service Ping
Service Ping is a feature that periodically sends anonymized analytics and usage data from your ZITADEL system to a central endpoint.
This data helps improve ZITADEL by providing insights into its usage patterns.
The feature is enabled by default, but can be disabled either completely or for specific reports.
Check out the settings below.
Data Sent by Service Ping [#data-sent-by-service-ping]
Base Information [#base-information]
If the feature is enabled, the base information will always be sent. To prevent that, you can opt out by disabling the entire Service Ping:
```yaml
ServicePing:
Enabled: false # ZITADEL_SERVICEPING_ENABLED
```
The base information sent back includes the following:
* your systemID
* the currently run version of ZITADEL
* information on all instances
* id
* creation date
* domains
Resource Counts [#resource-counts]
Resource counts is a report that provides us with information about the number of resources in your ZITADEL instances.
The following resources are counted:
* Instances
* Organizations
* Projects per organization
* Users per organization
* Users of type machine per organization
* SCIM provisioned users per organization
* Instance Administrators
* Identity Providers
* LDAP Identity Providers
* Actions (V1)
* Targets and set up executions
* Login Policies
* MFA enforcement (if either MFA is required for local or all users through the login policy)
* Password Complexity Policies
* Password Expiry Policies
* Lockout Policies
* Notification Policies with option "Password changed" enabled
The list might be extended in the future to include more resources.
To disable this report, set the following in your configuration file:
```yaml
ServicePing:
Telemetry:
ResourceCounts:
Enabled: false # ZITADEL_SERVICEPING_TELEMETRY_RESOURCECOUNT_ENABLED
```
Configuration [#configuration]
The Service Ping feature can be configured through the runtime configuration. Please check out the configuration file
for all available options. Below is a list of the most important options:
Interval [#interval]
This defines at which interval the Service Ping feature sends data to the central endpoint. It supports the extended cron syntax
and by default is set to `@daily`, which means it will send data every day. The time is randomized on startup to prevent
all systems from sending data at the same time.
You can adjust it to your needs to make sure there is no performance impact on your system.
For example, if you already have some scheduled job syncing data in and out of ZITADEL around a specific time or have regularly a
lot of traffic during the day, you might want to change it to a different time, e.g. `15 4 * * *` to send it every day at 4:15 AM.
The interval must be at least 30 minutes to prevent too frequent requests to the central endpoint.
MaxAttempts [#max-attempts]
This defines how many attempts the Service Ping feature will make to send data to the central endpoint before giving up
for a specific interval and report. If one report fails, it will be retried up to this number of times.
Other reports will still be handled in parallel and have their own retry count. This means if the base information
only succeeded after three attempts, the resource count still has five attempts to be sent.
BulkSize [#bulk-size]
Certain reports, like the resource counts, can generate a lot of data. To prevent sending too much data in one request,
the data is split into smaller chunks. This setting defines the maximum number of items that will be
sent in one request. If there are more items, they will be sent in multiple requests.
The size of the request is limited by the maximum request size of the central endpoint.
Each report will log its size before sending it, so you can adjust the bulk size if needed.
# TLS Modes
To run ZITADEL on any kind of infrastructure, you can configure on how to handle TLS connections.
There are three modes of operation: `disabled`, `external`, `enabled`.
Generally this command is set as argument while starting ZITADEL. For example like this:
```bash
zitadel start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled
```
Disabled [#disabled]
With the mode `disabled`, you instruct ZITADEL to await all connections with plain http without TLS.
Be aware this is not a secure setup and should only be used for test systems!
External [#external]
The mode `external` allows you to configure ZITADEL in such a way that it will instruct its clients to use https.
However ZITADEL delegates the management of TLS connections to a reverseproxy, web application firewall or a service mesh.
Enabled [#enabled]
When using the mode `enabled` ZITADEL is setup to await incoming connections in an encrypted fashion.
Whether it is from a client directly, a reverse proxy or web application firewall.
This allows HTTP connections to be secured at the transport level the whole way.
If you use the mode `enabled` you need to configure ZITADEL with the necessary TLS settings.
```yaml
TLS:
# if enabled, ZITADEL will serve all traffic over TLS (HTTPS and gRPC)
# you must then also provide a private key and certificate to be used for the connection
# either directly or by a path to the corresponding file
Enabled: true
# Path to the private key of the TLS certificate, it will be loaded into the Key
# and overwrite any existing value
KeyPath: #/path/to/key/file.pem
# Private key of the TLS certificate (KeyPath will this overwrite, if specified)
Key: #
# Path to the certificate for the TLS connection, it will be loaded into the Cert
# and overwrite any existing value
CertPath: #/path/to/cert/file.pem
# Certificate for the TLS connection (CertPath will this overwrite, if specified)
Cert: #
```
More Information [#more-information]
Beware that ZITADEL uses HTTP/2 for all its connections.
If you are using the mode `external` or `disabled` make sure to verify h2c compatibility.
* [Read more about how ZITADEL utilizes HTTP/2](/self-hosting/manage/http2).
* [Explore some concrete proxy configuration examples for ZITADEL](/self-hosting/manage/reverseproxy/reverse_proxy).
# Update and Scale ZITADEL
TL;DR [#tl-dr]
For getting started easily, you can just combine the init, setup and runtime phases
by executing the `zitadel` binary with the argument `start-from-init`.
However, to improve day-two-operations,
we recommend you run the init phase and the setup phase
separately using `zitadel init` and `zitadel setup` respectively.
Both the init and the setup phases are idempotent,
meaning you can run them as many times as you want without unexpectedly changing any state.
Nevertheless, they just **need** to be run at certain points in time, whereas further runs are obsolete.
The init phase has to run once over the full ZITADEL lifecycle, before the setup and runtime phases run first.
The setup phase has to run each time a new ZITADEL version is deployed.
After init and setup is done, you can start the runtime phase, that immediately accepts traffic, using `zitadel start`.
Scaling ZITADEL [#scaling-zitadel]
For production setups, we recommend that you [run ZITADEL in a highly available mode](/self-hosting/manage/production).
As all shared state is managed in the database,
the ZITADEL binary itself is stateless.
You can easily run as many ZITADEL binaries in parallel as you want.
If you use [Knative](https://knative.dev/docs/)
or [Google Cloud Run](https://cloud.google.com/run) (which uses Knative, too),
you can even scale to zero.
Especially if you use an autoscaler that scales to zero,
it is crucial that you minimize startup times.
Updating ZITADEL [#updating-zitadel]
When want to deploy a new ZITADEL version,
the new versions setup phase takes care of database migrations.
You generally want to run the job in a controlled manner,
so multiple executions don’t interfere with each other.
Also, after the setup is done,
rolling out a new ZITADEL version is much faster
when the runtime processes are just executed with `zitadel start`.
Separating Init and Setup from the Runtime [#separating-init-and-setup-from-the-runtime]
If you use the [official ZITADEL Helm chart](/self-hosting/deploy/kubernetes),
then you can stop reading now.
The init and setup phases are already separated and executed in dedicated Kubernetes Jobs.
If you use another orchestrator and want to separate the phases yourself,
you should know what happens during these phases.
The Init Phase [#the-init-phase]
The command `zitadel init` ensures the database connection is ready to use for the subsequent phases.
It just needs to be executed once over ZITADELs full life cycle,
when you install ZITADEL from scratch.
`zitadel init` performs two steps that require different privileges:
**Provisioning step** (requires a PostgreSQL superuser):
* If it doesn’t exist already, it creates the unprivileged database user.
Subsequent phases connect to the database with this user’s credentials only.
* If it doesn’t exist already, it creates a database with the configured database name.
* If not already done, it grants the necessary permissions to the unprivileged user.
**Schema bootstrapping step** (requires database owner privileges, uses the service user):
* If they don’t exist already, it creates all schemas (`eventstore`, `projections`, `system`) and base tables.
**Manual provisioning:** If you handle the provisioning step yourself (e.g. on a managed PostgreSQL service where you don’t have superuser access),
you can skip it entirely by running only the schema bootstrapping step:
```bash
zitadel init schema
```
This sub-command connects using the service user credentials and only performs schema bootstrapping.
No `Admin.*` credentials are required.
See the [database documentation](/self-hosting/manage/database) for full details.
The init phase is idempotent if executed with the same binary version.
The Setup Phase [#the-setup-phase]
During `zitadel setup`, ZITADEL creates projection tables and migrates existing data, if `--init-projections=true` is set.
Depending on the ZITADEL version and the runtime resources,
this step can take several minutes.
When deploying a new ZITADEL version,
make sure the setup phase runs before you roll out the new `zitadel start` processes.
The setup phase is executed in subsequent steps
whereas a new version's execution takes over where the last execution stopped.
Some configuration changes are only applied during the setup phase, like ExternalDomain, ExternalPort and ExternalSecure.
The setup phase is idempotent if executed with the same binary version.
The Runtime Phase [#the-runtime-phase]
The `zitadel start` command assumes the database is already initialized and set up.
It starts serving requests within fractions of a second.
Beware, in the background, out-of-date projections
[recompute their state by replaying all missed events](/concepts/eventstore/implementation#projections).
If a new ZITADEL version is deployed, this can take quite a long time,
depending on the amount of events to catch up.
You probably should consider providing `--init-projections=true`-flag to the [Setup Phase](#the-setup-phase) to shift the synchronization time to previous steps and delay the startup phase until events are caught up.
# Usage Control
If you have a self-hosted ZITADEL environment, you can limit the usage of your [instances](/concepts/structure/instance).
For example, if you provide your customers [their own virtual instances](/concepts/structure/instance#multiple-virtual-instances) with access on their own domains, you can design a pricing model based on the usage of their instances.
The usage control features are currently limited to the instance level only.
Block Instances [#block-instances]
You can block an instance using the [system API](/reference/api/system).
Most requests to a blocked instance are rejected with the HTTP status *429 Too Many Requests* or the gRPC status *8 Resource Exhausted*.
However, requests to the [system API](/apis/introduction#system) are still allowed.
Requests to paths with the prefix */ui/login* return a redirect with HTTP status *302 Found* to */ui/console*, where the user is guided to *InstanceManagementURL*.
Blocked HTTP requests additionally set a cookie to make it easy to block traffic before it reaches your ZITADEL runtime, for example with a WAF rule.
You can block new instances by default using the *DefaultInstance.Limits.Block* runtime configuration.
The following snippets shows the default YAML:
```yaml
DefaultInstance:
Limits:
# If Block is true, all requests except to /ui/console or the system API are blocked and /ui/login is redirected to /ui/console.
# /ui/console shows a message that the instance is blocked with a link to Console.InstanceManagementURL
Block: # ZITADEL_DEFAULTINSTANCE_LIMITS_BLOCK
```
Limit Audit Trails [#limit-audit-trails]
You can restrict the maximum age of events returned by the following APIs:
* [Events Search](/reference/api/admin/zitadel.admin.v1.AdminService.ListEvents), See also the [Event API guide](/guides/integrate/zitadel-apis/event-api)
* [My User History](/reference/api/auth/zitadel.auth.v1.AuthService.ListMyUserChanges)
* [A Users History](/reference/api/management/zitadel.management.v1.ManagementService.ListUserChanges)
* [A Applications History](/reference/api/management/zitadel.management.v1.ManagementService.ListAppChanges)
* [A Organizations History](/reference/api/management/zitadel.management.v1.ManagementService.ListOrgChanges)
* [A Projects History](/reference/api/management/zitadel.management.v1.ManagementService.ListProjectChanges)
* [A Project Grants History](/reference/api/management/zitadel.management.v1.ManagementService.ListProjectGrantChanges)
You can set a global default limit as well as a default limit [for new virtual instances](/concepts/structure/instance#multiple-virtual-instances) in the ZITADEL settings.
The following snippets shows the defaults:
```yaml
# AuditLogRetention limits the number of events that can be queried via the events API by their age.
# A value of "0s" means that all events are available.
# If an audit log retention is set using an instance limit, it will overwrite the system default.
AuditLogRetention: 0s # ZITADEL_AUDITLOGRETENTION
DefaultInstance:
Limits:
# AuditLogRetention limits the number of events that can be queried via the events API by their age.
# A value of "0s" means that all events are available.
# If this value is set, it overwrites the system default unless it is not reset via the admin API.
AuditLogRetention: # ZITADEL_DEFAULTINSTANCE_LIMITS_AUDITLOGRETENTION
```
You can also set a limit for [a specific virtual instance](/concepts/structure/instance#multiple-virtual-instances) using the [system API](/reference/api/system).
Quotas [#quotas]
Quotas enables you to limit usage and/or register webhooks that trigger on configurable usage levels for certain units.
For example, you might want to report usage to an external billing tool and notify users when 80 percent of a quota is exhausted.
ZITADEL supports limiting authenticated requests and action run seconds with quotas.
For using the quotas feature you have to activate it in your ZITADEL settings *Quotas* section.
The following snippets shows the defaults:
```yaml
Quotas:
Access:
# If enabled, authenticated requests are counted and potentially limited depending on the configured quota of the instance
Enabled: false # ZITADEL_QUOTAS_ACCESS_ENABLED
Debounce:
MinFrequency: 0s # ZITADEL_QUOTAS_ACCESS_DEBOUNCE_MINFREQUENCY
MaxBulkSize: 0 # ZITADEL_QUOTAS_ACCESS_DEBOUNCE_MAXBULKSIZE
ExhaustedCookieKey: "zitadel.quota.exhausted" # ZITADEL_QUOTAS_ACCESS_EXHAUSTEDCOOKIEKEY
ExhaustedCookieMaxAge: "300s" # ZITADEL_QUOTAS_ACCESS_EXHAUSTEDCOOKIEMAXAGE
Execution:
# If enabled, all action executions are counted and potentially limited depending on the configured quota of the instance
Enabled: false # ZITADEL_QUOTAS_EXECUTION_DATABASE_ENABLED
Debounce:
MinFrequency: 0s # ZITADEL_QUOTAS_EXECUTION_DEBOUNCE_MINFREQUENCY
MaxBulkSize: 0 # ZITADEL_QUOTAS_EXECUTION_DEBOUNCE_MAXBULKSIZE
```
Once you have activated the quotas feature, you can configure quotas [for your virtual instances](/concepts/structure/instance#multiple-virtual-instances) using the [system API](/reference/api/system) or the *DefaultInstances.Quotas* section.
The following snippets shows the defaults:
```yaml
DefaultInstance:
Quotas:
# Items take a slice of quota settings, whereas, for each unit type and instance, one or zero quotas may exist.
# The following unit types are supported
# "requests.all.authenticated"
# The sum of all requests to the ZITADEL API with an authorization header,
# excluding the following exceptions
# - Calls to the System API
# - Calls that cause internal server errors
# - Failed authorizations
# - Requests after the quota already exceeded
# "actions.all.runs.seconds"
# The sum of all actions run durations in seconds
Items:
# - Unit: "requests.all.authenticated"
# # From defines the starting time from which the current quota period is calculated.
# # This is relevant for querying the current usage.
# From: "2023-01-01T00:00:00Z"
# # ResetInterval defines the quota periods duration
# ResetInterval: 720h # 30 days
# # Amount defines the number of units for this quota
# Amount: 25000
# # Limit defines whether ZITADEL should block further usage when the configured amount is used
# Limit: false
# # Notifications are emitted by ZITADEL when certain quota percentages are reached
# Notifications:
# # Percent defines the relative amount of used units, after which a notification should be emitted.
# - Percent: 100
# # Repeat defines, whether a notification should be emitted each time when a multitude of the configured Percent is used.
# Repeat: true
# # CallURL is called when a relative amount of the quota is used.
# CallURL: "https://httpbin.org/post"
```
Exhausted Authenticated Requests [#exhausted-authenticated-requests]
If a quota is configured to limit requests and the quotas amount is exhausted, all further authenticated requests are blocked except requests to the [system API](/apis/introduction#system).
Also, a cookie is set, to make it easier to block further traffic before it reaches your ZITADEL runtime, for example with a WAF rule.
The management console is still served, but it only shows a dialog that says that the instance is blocked with a link to *InstanceManagementURL*.
Exhausted Action Run Seconds [#exhausted-action-run-seconds]
If a quota is configured to limit action run seconds and the quotas amount is exhausted, all further actions will fail immediately with a context timeout exceeded error.
The action that runs into the limit also fails with the context timeout exceeded error.
# Technical Advisory 10000
Date and Version [#date-and-version]
Version: 2.32.0
Date: Calendar Week 32
Description [#description]
Currently, by default, users are directed to the "Select Account Page" on the ZITADEL login.
However, this can be modified by including a [prompt or a login hint](/apis/openidoauth/endpoints#additional-parameters) in the authentication request.
As a result of this default behavior, users who already have an active session in one application and wish to log in to a second one will need to select their user account, even if no other session is active.
To address this, we are going to change this behavior so that users will be automatically authenticated when logging into a second application, as long as they only have one active session.
Statement [#statement]
This behavior change was tracked in the following issue: [Reuse current session if no prompt is selected](https://github.com/zitadel/zitadel/issues/4841)
and released in version [v2.32.0](https://github.com/zitadel/zitadel/releases/tag/v2.32.0)
Mitigation [#mitigation]
If you want to prompt users to always select their account on purpose, please make sure to include the `select_account` [prompt](/apis/openidoauth/endpoints#additional-parameters) in your authentication request.
Impact [#impact]
Once this update has been released and deployed, your users will be automatically authenticated
No action will be required on your part if this is the intended behavior.
# Technical Advisory 10001
Date and Version [#date-and-version]
Version: 2.35.0
Date: Calendar Week 34
Description [#description]
Currently, disabling the `Allow Register` setting in the Login Policy, will disable any registration - local and through External Identity Providers (IDP).
This might be a good solution, if you manage all users yourself and do not want them to create any new account.
If you on the other hand want users to be able to federate their accounts from another IDP and only want to disable local registration, there's currently no option to do so.
Further ZITADEL provided the possibility to disable registration on each IDP with the introduction of IDP Templates.
To address this, we are going to change the behavior of the setting mentioned above, so that if disabled, it will only prevent local registration. Registration of a federated user will still be possible - if not disabled by the corresponding IDP Template.
Statement [#statement]
This behavior change was tracked in the following PR: [Restrict AllowRegistration check to local registration](https://github.com/zitadel/zitadel/pull/5939).
The change was part of version [v2.35.0](https://github.com/zitadel/zitadel/releases/tag/v2.35.0)
Mitigation [#mitigation]
If you want to prevent user creation / registration through an IDP, be sure to disable the `isCreationAllowed` option on the desired IDP Templates.
Impact [#impact]
Once this update has been released and deployed, the `Allow Register` setting in the Login Policy will only affect local registrations and users might be able to create a ZITADEL account through an IDP, depending on your IDP provider options.
# Technical Advisory 10002
Date and Version [#date-and-version]
Version: 2.40.0
Date: Calendar week 44
Description [#description]
Since Angular Material v15 many of the UI components have been refactored
to be based on the official Material Design Components for Web (MDC).
These refactored components do not support dynamic styling, so in order to keep the library up-to-date,
the management console UI will lose its dynamic theming capability.
Statement [#statement]
This design change is tracked in the following PR: [feat(console): MDC components](https://github.com/zitadel/zitadel/pull/6482).
As soon as the release version is published, we will include the version here.
Mitigation [#mitigation]
If you need users to have your branding settings
(background-, button-, link and text coloring), you should implement your
own user facing UI yourself and not use ZITADELs management console UI. Assets like your logo and icons will still be used.
Impact [#impact]
Once this update has been released and deployed, the management console UI won't apply any coloring of your branding settings to the UI components.
ZITADEL hosted Login-UI is not affected by this change.
# Technical Advisory 10003
Date and Version [#date-and-version]
Version: 2.38.0
Date: Calendar week 41
Description [#description]
When users are redirected to the ZITADEL Login-UI without any organizational context, they're currently presented a login screen,
based on the instance settings, e.g. available IDPs and possible login mechanisms. If the user will then register themselves,
by the registration form or through an IDP, the user will always be created on the default organization.
This behavior led to confusion, e.g. when activating IDPs on default org would not show up in the Login-UI, because they would still be loaded from the instance settings.
To improve this, we're introducing the following change:
If users are redirected to the Login-UI without any organizational context, they will be presented a login screen based on the settings of the default organization (incl. IDPs).
If the registration (and also authentication) needs to occur on a specified organization, apps can already
specify this by providing [an organization scope](/apis/openidoauth/scopes#reserved-scopes).
Statement [#statement]
This change was tracked in the following PR:
[feat(login): use default org for login without provided org context](https://github.com/zitadel/zitadel/pull/6625), which was released in Version [2.38.0](https://github.com/zitadel/zitadel/releases/tag/v2.38.0)
Mitigation [#mitigation]
There's no action needed on your side currently as existing instances are not affected directly and IAM\_OWNER can activate the flag at their own pace.
Impact [#impact]
Once this update has been released and deployed, newly created instances will always use the default organization and its settings as default context for the login.
Already existing instances will still use the instance settings by default and can switch to the new default by ["Activating the 'LoginDefaultOrg' feature"](/reference/api/admin/zitadel.admin.v1.AdminService.ActivateFeatureLoginDefaultOrg) through the Admin API.
**This change is irreversible!**
Regardless of the change:
If a known username is entered on the first screen, the login switches its context to the organization of that user and settings will be updated to that organization as well.
# Technical Advisory 10004
Date and Version [#date-and-version]
Version: 2.39.0
Date: 2023-10-14
Description [#description]
Due to storage optimizations ZITADEL changes the behavior of sequences.
This change improves command (create, update, delete) performance of ZITADEL.
Sequences are no longer unique inside an instance.
From now on sequences are counting up per aggregate id.
For example sequences of newly created users begin at 1.
Existing sequences remain untouched.
Statement [#statement]
This change is tracked in the following PR: [new eventstore framework](https://github.com/zitadel/zitadel/issues/5358).
As soon as the release version is published, we will include the version here.
Mitigation [#mitigation]
If you use the ListEvents API to scrape events use the creation date instead of the sequence.
If you use sequences on a list of objects it's no longer guaranteed to have unique sequences across the list.
Therefore, it's recommended to use the change data of the objects instead.
Impact [#impact]
Once this update has been released and deployed, sequences are no longer unique inside an instance.
ZITADEL will increase parallel write capabilities, because there is no global sequence to track anymore.
Editor service does not respond the different services of ZITADEL anymore, it returns zitadel.
As we are switching to resource based API's there is no need for this field anymore.
# Technical Advisory 10005
Date and Version [#date-and-version]
Version: 2.39.0
Date: Calendar week 41/42 2023
Description [#description]
Migrating to version >= 2.39 from \< 2.39 will cause down time during setup starts and the new version is started.
This is caused by storage optimizations which replace the `eventstore.events` database table with the new `eventstore.events2` table.
All existing events are migrated during the execution of the `zitadel setup` command.
New events will be inserted into the new `eventstore.events2` table. The old table `evetstore.events` is renamed to `eventstore.events_old` and will be dropped in a future release of ZITADEL.
Statement [#statement]
This change is tracked in the following PR: [new eventstore framework](https://github.com/zitadel/zitadel/issues/5358).
As soon as the release version is published, we will include the version here.
Mitigation [#mitigation]
If you use this table for TRIGGERS or Change data capture please check the new table definition and change your code to use `eventstore.events2`.
Impact [#impact]
Once the setup step renamed the table. Old versions of ZITADEL are not able to read/write events.
If the upgrade fails make sure to rename `eventstore.events_old` to `eventstore.events`. This change enables older ZITADEL versions to work properly.
# Technical Advisory 10006
Date and Version [#date-and-version]
Version: 2.39.0
Date: Calendar week 41/42 2023
Description [#description]
Versions >= 2.39.0 require the cockroach database user of ZITADEL to be granted to the `VIEWACTIVITY` grant. This can either be reached by grant the role manually or execute the `zitadel init` command.
Cockroach versions 22.2 \< 22.2.11 and 23.1 \< 23.1.4 will fail the migration. Please make sure to upgrade to more recent versions first. ZITADEL recommends to use the latest stable version of CockroachDB.
Statement [#statement]
To query correct order of events the cockroach database user of ZITADEL needs additional privileges to query the `crdb_internal.cluster_transactions`-table
Mitigation [#mitigation]
Before migrating to versions >= 2.39.0 make sure the cockroach database user has sufficient grants.
CockroachDB version is up to date.
Impact [#impact]
If the user doesn't have sufficient grants, events won't be updated.
# Technical Advisory 10007
Date and Version [#date-and-version]
Version: Upcoming
Date: Upcoming
Affected Users [#affected-users]
This advisory applies to self-hosted ZITADEL installations with custom roles to permissions mappings in the *InternalAuthZ.RolePermissionMappings* configuration section.
Description [#description]
In upcoming ZITADEL versions, RBAC also applies to [system users defined in the ZITADEL runtime configuration](/guides/integrate/zitadel-apis/access-zitadel-system-api#runtime-settings).
This enables fine-grained access control to the system API as well as other APIs for system users.
ZITADEL defines the new default roles *SYSTEM\_OWNER* and *SYSTEM\_OWNER\_VIEWER*.
System users without any memberships defined in the configuration will be assigned the *SYSTEM\_OWNER* role.
**Self-hosting users who define their own custom mapping at the *InternalAuthZ.RolePermissionMappings* configuration section**, have to define the *SYSTEM\_OWNER* role in their configuration too to be able to access the system API with the default system user membership.
Statement [#statement]
This change is tracked in the following PR: [feat: add SYSTEM\_OWNER role](https://github.com/zitadel/zitadel/pull/6765).
As soon as the release version is published, we will include the version here.
Mitigation [#mitigation]
If you have a custom role mapping configured, make sure you configure the new role *SYSTEM\_OWNER* before migrating to upcoming ZITADEL versions.
As a reference, these are the default mappings:
```yaml
InternalAuthZ:
RolePermissionMappings:
- Role: "SYSTEM_OWNER"
Permissions:
- "system.instance.read"
- "system.instance.write"
- "system.instance.delete"
- "system.domain.read"
- "system.domain.write"
- "system.domain.delete"
- "system.debug.read"
- "system.debug.write"
- "system.debug.delete"
- "system.feature.write"
- "system.limits.write"
- "system.limits.delete"
- "system.quota.write"
- "system.quota.delete"
- "system.iam.member.read"
- Role: "SYSTEM_OWNER_VIEWER"
Permissions:
- "system.instance.read"
- "system.domain.read"
- "system.debug.read"
...
```
Impact [#impact]
If the system users don't have the correct memberships and roles which resolve to permissions, the system users lose access to the system API.
# Technical Advisory 10008
Date and Version [#date-and-version]
Versions: 2.44.0, 2.43 >= 2.43.6, 2.42 > 2.42.12
Date: 2024-01-25
Description [#description]
The versions mentioned above introduce a new flag `--init-projections` to `zitadel setup` commands (`setup`, `start-from-setup`, `start-from-init`)
This flag enables prefilling of newly added or changed projections (database tables) during setup phase instead of start phase which are used to query data, for example users.
This new feature adds the following fields to the `setup` configuration, previously only used in `start`-command:
* `AssetStore`: Storage for assets like user avatar, organization logo, icon, font, etc.
* `OIDC`
* `Login`
* `WebAuthNName`
* `Telemetry`
* `SystemAPIUsers`
If you use different settings on `setup` and `start` and have overwritten previously mentioned settings please make sure to also add them to the configuration provided to ZITADEL `setup` command.
Statement [#statement]
Filling of projections can get time-consuming as your system grows and this can cause downtime of self-hosted installations of ZITADEL because queries first need to ensure data consistency.
Before this release, this step was executed after the start of ZITADEL and therefore lead to inconsistent retrieval of data until the projections were up-to-date.
Mitigation [#mitigation]
Enable the flag (`--init-projections=true`) in setup phase and make sure the previous deployment of ZITADEL remains active until the new revision started properly.
Note that the flag is enabled by default starting with the following versions:
* \>=2.49.0
* 2.48.x: >=2.48.2
* 2.47.x: >=2.47.7
* 2.46.x: >=2.46.4
* 2.45.x: >=2.45.4
* 2.44.x: >=2.44.6
* 2.43.x: >=2.43.10
* 2.42.x: >=2.42.16
Impact [#impact]
Decreases downtime of starting new revisions with new or changed projections dramatically.
# Technical Advisory 10009
Date and Version [#date-and-version]
Version: 2.53.0
Date: 2024-05-28
Description [#description]
There were rare cases where CockroachDB got blocked during runtime of ZITADEL and returned `WRITE_TOO_OLD`-errors to ZITADEL. The root cause of the problem is described in [this GitHub issue of the database](https://github.com/cockroachdb/cockroach/issues/77119). The workaround provided by the database is enabling the `enable_durable_locking_for_serializable`-flag as described [here](https://github.com/cockroachdb/cockroach/issues/75456#issuecomment-1936277716).
Because enabling flags requires admin privileges the statement must be executed manually or by executing `zitadel init` command.
Statement [#statement]
Ensure lock distribution for `FOR UPDATE`-statements on CockroachDB.
Mitigation [#mitigation]
CockroachDB version >= 23.2.
Impact [#impact]
Adding additional raft queries to `FOR UPDATE`-statements can impact performance slightly but ensures availability of the system.
# Technical Advisory 10010
Date and Version [#date-and-version]
Version: 2.53.0
Date: 2024-05-28
Description [#description]
Version 2.53.0 optimizes the way tokens are created and migrates them to the v2 implementation already used by OAuth / OIDC tokens created through the session API.
Because of this tokens events are no longer created on the user itself. To be as backwards compatible as possible a separate event is created on the user for the audit log.
Statement [#statement]
This change was tracked in the following PR:
[perf(oidc): optimize token creation](https://github.com/zitadel/zitadel/pull/7822), which was released in Version [2.53.0](https://github.com/zitadel/zitadel/releases/tag/v2.53.0)
Mitigation [#mitigation]
If you use the ListEvents API to check the audit trail of a user or being able to compute Daily or Monthly Active Users, be sure to also include the `user.token.v2.added` event type in your search
if you already query for the `user.token.added` event type.
Impact [#impact]
Once this update has been released and deployed, the `user.token.added` event will no longer be created when a user access token is created, but instead a `user.token.v2.added`.
Existing `user.token.added` events will be untouched.
# Technical Advisory 10011
Date and Version [#date-and-version]
Version: 2.59.0
Date: 2024-08-19
Description [#description]
Version 2.59.0 allows more combinations in the identity provider options. As of now, **automatic creation** and **automatic linking options** were only considered if the corresponding **allowed option** (account creation / linking allowed) was enabled.
Starting with this release, this is no longer needed and allows administrators to address cases, where only an **automatic creation** is allowed, but users themselves should not be allowed to **manually** create new accounts using an identity provider or edit the information during the process.
Also, allowing users to only link to the proposed existing account is now possible with an enabled **automatic linking option**, while disabling **account linking allowed**.
Statement [#statement]
This change was tracked in the following PR:
[feat(idp): provide auto only options](https://github.com/zitadel/zitadel/pull/8420), which was released in Version [2.59.0](https://github.com/zitadel/zitadel/releases/tag/v2.59.0)
Mitigation [#mitigation]
If you previously enabled one of the **automatic** options with the corresponding **allowed** option, be sure that this is the intended behavior.
Impact [#impact]
Once this update has been released and deployed, the **automatic** options can be activated with the corresponding **allowed** option.
# Technical Advisory 10012
Date and Version [#date-and-version]
Version: 2.63.0
Date: 2024-09-26
Description [#description]
In version 2.63.0 we've increased the transaction duration for projections.
ZITADEL has an event driven architecture. After events are pushed to the eventstore,
they are reduced into projections in bulk batches. Projections allow for efficient lookup of data through normalized SQL tables.
We've investigated multiple reports of outdated projections.
For example created users missing in get requests, or missing data after a ZITADEL upgrade[^1].
The conclusion is that the transaction in which we perform a bulk of queries can time out.
The old setting defined a transaction duration of 500ms for a bulk of 200 events.
A single event may create multiple statements in a single projection.
A timeout may occur even if the actual bulk size is less than 200,
which then results in more back-pressure on a busy system, leading to more timeouts and effectively deadlocking a projection.
Increasing or disabling the projection transaction duration solved deadlocks in all reported cases.
We've decided to increase the transaction duration to 1 minute.
Due to the high value it is functionally similar to disabling,
however it still provides a safety net for transaction that do freeze,
perhaps due to connection issues with the database.
[^1]: Changes written to the eventstore are the main source of truth. When a projection is out of date, some request may serve incomplete or no data. The data itself is however not lost.
Statement [#statement]
A summary of bug reports can be found in the following issue: [Missing data due to outdated projections](https://github.com/zitadel/zitadel/issues/8517).
This change was submitted in the following PR:
[fix(projection): increase transaction duration](https://github.com/zitadel/zitadel/pull/8632), which will be released in Version [2.63.0](https://github.com/zitadel/zitadel/releases/tag/v2.63.0)
Mitigation [#mitigation]
If you have a custom configuration for projections, this update will not apply to your system or some projections. When encountering projection deadlock consider increasing the timeout to the new default value.
Note that entries under `Customizations` overwrite the global settings for a single projection.
```yaml
Projections:
TransactionDuration: 1m # ZITADEL_PROJECTIONS_TRANSACTIONDURATION
BulkLimit: 200 # ZITADEL_PROJECTIONS_BULKLIMIT
Customizations:
custom_texts:
BulkLimit: 400
project_grant_fields:
TransactionDuration: 0s
BulkLimit: 2000
```
Impact [#impact]
Once this update has been released and deployed, transactions are allowed to run longer. No other functional impact is expected.
# Technical Advisory 10013
Date [#date]
Date: 2024-12-09
Description [#description]
ZITADEL currently provides a "latest" and "stable" release tags as well as maintenance branches, where bug fixes are backported.
We also publish release candidates regularly.
The "stable" release channel was introduced for users seeking a more reliable and production-ready version of the software.
However, most customers have their own deployment policies and cycles.
Backports and security fixes are currently done as needed or required by customers.
zitadel.cloud follows a similar approach, where the latest release is deployed a few days after its creation.
Mitigation [#mitigation]
If you used the "stable" Docker release, please consider switching to a specific version tag and follow the [release notes on GitHub](https://github.com/zitadel/zitadel/releases) for latest changes.
Impact [#impact]
The "stable" version will no longer be published or updated, and the corresponding Docker image tag will not be maintained anymore.
# Technical Advisory 10014
Date [#date]
Versions: >= v2.67.3, v2.66 >= v2.66.6
Date: 2025-01-17
Description [#description]
Prior to version [v2.66.0](https://github.com/zitadel/zitadel/releases/tag/v2.66.0), some project grants were incorrectly created under the granted organization instead of the project owner's organization. To find these grants, users had to set the `x-zitadel-orgid` header to the granted organization ID when using the [`ListAllProjectGrants`](/reference/api/management/zitadel.management.v1.ManagementService.ListAllProjectGrants) gRPC method.
Zitadel [v2.66.0](https://github.com/zitadel/zitadel/releases/tag/v2.66.0) corrected this behavior for new grants. However, existing grants were not automatically updated. Version v2.66.6 corrects the owner of these existing grants.
Impact [#impact]
After the release of v2.66.6, if your application uses the [`ListAllProjectGrants`](/reference/api/management/zitadel.management.v1.ManagementService.ListAllProjectGrants) method with the `x-zitadel-orgid` header set to the granted organization ID, you will not retrieve any results.
Mitigation [#mitigation]
To ensure your application continues to function correctly after the release of v2.66.6, implement the following changes:
1. **Conditional Header:** Only set the `x-zitadel-orgid` header to the project owner's organization ID if the user executing the [`ListAllProjectGrants`](/reference/api/management/zitadel.management.v1.ManagementService.ListAllProjectGrants) method belongs to a different organization than the project.
2. **Use `grantedOrgIdQuery`:** Utilize the `grantedOrgIdQuery` parameter to filter grants for the specific granted organization.
# Technical Advisory 10015
Date [#date]
Versions: >= v3.0.0
Date: 2025-03-31
Description [#description]
CockroachDB was initially chosen due to its distributed nature and SQL compatibility. However, over time, it became apparent that the operational complexity and specific compatibility issues outweighed the benefits for our use case. We decided to focus on PostgreSQL to simplify our infrastructure and leverage its mature ecosystem.
Impact [#impact]
Zitadel v3 requires PostgreSQL as a database. Therefore, Zitadel v3 will not start if CockroachDB is configured as the database.
Mitigation [#mitigation]
To upgrade your self-hosted deployment to Zitadel v3 migrate to PostgreSQL. Please refer to [this guide](/self-hosting/manage/cli/mirror) to mirror the data to PostgreSQL before you deploy Zitadel v3.
# Technical Advisory 10016
Date [#date]
* v2.65.x: > v2.65.9
* v2.66.x > v2.66.17
* v2.67.x > v2.67.14
* v2.68.x > v2.68.10
* v2.69.x > v2.69.10
* v2.70.x > v2.70.11
* v2.71.x > v2.71.10
* v3.x > v3.2.1
Date: 2025-05-14
Last updated: 2025-05-19
Description [#description]
Background [#background]
Zitadel uses an eventstore table as main source of truth for state changes.
Projections are tables which provide alternative views of state, which are built using events.
In order to know which events are reduced into projections, we use a `position` column in the eventstore and a dedicated table which records the current state.
Problem [#problem]
Zitadel prior to the listed version had a precision bug. The `position` column uses a fixed-point numeric type. In Zitadel's Go code we used a `float64`. In certain cases we noticed a precision loss when Zitadel updated the `current_states` table.
Impact [#impact]
During a past attempt to fix this, we got reports of failing projections inside Zitadel. Because the precision became exact certain compare operations like *equal*, *less than*, etc. would now return different results. This was because the values in `current_states` would already have lost precision from a broken version. This might happen to **some** deployments or projections: there is only a small probability.
We are releasing the fix again and your system might get affected.
* Original issue: [8671](https://github.com/zitadel/zitadel/issues/8671)
* Follow-up issue: [8863](https://github.com/zitadel/zitadel/issues/8863)
Mitigation [#mitigation]
When **after** deploying a fixed version and only when experiencing problems described by issue [8863](https://github.com/zitadel/zitadel/issues/8863), the following queries can be executed to fix `current_state` rows which have "broken" values. We recommend doing this in a transaction in order to double-check the affected rows, before committing the update.
```sql
begin;
with
broken as (
select
s.projection_name,
s.instance_id,
s.aggregate_id,
s.aggregate_type,
s.sequence,
s."position" as old_position,
e."position" as new_position
from
projections.current_states s
join eventstore.events2 e on s.instance_id = e.instance_id
and s.aggregate_id = e.aggregate_id
and s.aggregate_type = e.aggregate_type
and s.sequence = e.sequence
and s."position" != e."position"
where
s."position" != 0
and projection_name != 'projections.execution_handler'
),fixed as (
update projections.current_states s
set
"position" = b.new_position
from
broken b
where
s.instance_id = b.instance_id
and s.projection_name = b.projection_name
and s.aggregate_id = b.aggregate_id
and s.aggregate_type = b.aggregate_type
and s.sequence = b.sequence
returning *
)
select
b.projection_name,
b.instance_id,
b.aggregate_id,
b.aggregate_type,
b.sequence,
b.old_position,
b.new_position,
b.old_position - b.new_position difference
from
broken b;
```
If the output from the above looks reasonable, for example not a huge number in the `difference` column, commit the transaction:
```sql
commit;
```
When there are no rows returned, your system was not affected by precision loss.
When there's unexpected output, use `rollback;` instead.
# Java Client
This guide covers the official Zitadel Management API Client for the JVM (Java 11+), which allows you to programmatically manage resources in your Zitadel instance.
**This is a Management API Client, not an Authentication SDK.**
This library is designed for server-to-server communication to manage your Zitadel instance (e.g., creating users, managing projects, and updating settings). It is **not** intended for handling end-user login flows in your web application. For user authentication, you should use a standard OIDC library like Spring Security.
The Zitadel Java Client provides an idiomatic way to access the full gamut of Zitadel's v2 Management APIs from your JVM-based backend applications.
> Please be aware that this client library is currently in an **incubating stage**.
> While it is available for use, the API and its functionality may evolve, potentially introducing
> breaking changes in future updates. We advise caution when considering it for production environments.
Installation [#installation]
You can add the client library to your project using Maven by adding the following dependency to your `pom.xml` :
```xml
io.github.zitadelclient4.0.0-beta-1
```
Using the SDK [#using-the-sdk]
Your SDK offers three ways to authenticate with Zitadel. Each method has its
own benefits—choose the one that fits your situation best.
1\. Private Key JWT Authentication [#1-private-key-jwt-authentication]
**What is it?**
You use a JSON Web Token (JWT) that you sign with a private key stored in a
JSON file. This process creates a secure token.
**When should you use it?**
* **Best for production:** It offers strong security.
* **Advanced control:** You can adjust token settings like expiration.
**How do you use it?**
1. Save your private key in a JSON file.
2. Build the authenticator using the helper method.
**Example:**
```java
class Demo {
public static void main(String[] args) throws ApiException {
Zitadel zitadel = Zitadel.withPrivateKey("https://example.us1.zitadel.cloud", "path/to/jwt-key.json");
UserServiceAddHumanUserResponse response = zitadel.users.userServiceAddHumanUser(
new UserServiceAddHumanUserRequest()
.username("john.doe")
.profile(new UserServiceSetHumanProfile()
.givenName("John")
.familyName("Doe"))
.email(new UserServiceSetHumanEmail()
.email("john@doe.com"))
);
System.out.println("User created: " + response);
}
}
```
2\. Client Credentials Grant [#2-client-credentials-grant]
**What is it?**
This method uses a client ID and client secret to get a secure access token,
which is then used to authenticate.
**When should you use it?**
* **Simple and straightforward:** Good for server-to-server communication.
* **Trusted environments:** Use it when both servers are owned or trusted.
**How do you use it?**
1. Provide your client ID and client secret.
2. Build the authenticator using the helper method.
**Example:**
```java
class Demo {
public static void main(String[] args) throws ApiException {
Zitadel zitadel = Zitadel.withClientCredentials("https://example.us1.zitadel.cloud", "id", "secret");
UserServiceAddHumanUserResponse response = zitadel.users.addHumanUser(
new UserServiceAddHumanUserRequest()
.username("john.doe")
.profile(new UserServiceSetHumanProfile()
.givenName("John")
.familyName("Doe"))
.email(new UserServiceSetHumanEmail()
.email("john@doe.com"))
);
System.out.println("User created: " + response);
}
}
```
3\. Personal Access Tokens (PATs) [#3-personal-access-tokens-pa-ts]
**What is it?**
A Personal Access Token (PAT) is a pre-generated token that you can use to
authenticate without exchanging credentials every time.
**When should you use it?**
* **Easy to use:** Great for development or testing scenarios.
* **Quick setup:** No need for dynamic token generation.
**How do you use it?**
1. Obtain a valid personal access token from your account.
2. Build the authenticator using the helper method.
**Example:**
```java
class Demo {
public static void main(String[] args) throws ApiException {
Zitadel zitadel = Zitadel.withAccessToken("https://example.us1.zitadel.cloud", "token");
UserServiceAddHumanUserResponse response = zitadel.users.addHumanUser(
new UserServiceAddHumanUserRequest()
.username("john.doe")
.profile(new UserServiceSetHumanProfile()
.givenName("John")
.familyName("Doe"))
.email(new UserServiceSetHumanEmail()
.email("john@doe.com"))
);
System.out.println("User created: " + response);
}
}
```
***
Choose the authentication method that best suits your needs based on your
environment and security requirements. For more details, please refer to the
[Zitadel documentation on authenticating service accounts](https://zitadel.com/docs/guides/integrate/service-accounts/authenticate-service-accounts).
Versioning [#versioning]
The client library's versioning is aligned with the Zitadel core project. The major version of the
client corresponds to the major version of Zitadel it is designed to work with. For example,
v2.x.x of the client is built for and tested against Zitadel v2, ensuring a predictable and stable integration.
Resources [#resources]
* [GitHub Repository](https://github.com/zitadel/client-java): For source code, examples, and to report issues.
* [Maven Package](https://central.sonatype.com/artifact/io.github.zitadel/client): The official package artifact for Maven.
# Node.js Client
This guide covers the official Zitadel Management API Client for Node.js (20+), which allows you to programmatically manage resources in your Zitadel instance.
**This is a Management API Client, not an Authentication SDK.**
This library is designed for server-to-server communication to manage your Zitadel instance (e.g., creating users, managing projects, and updating settings). It is **not** intended for handling end-user login flows in your web application. For user authentication, you should use a standard OIDC library with your Node.js framework of choice.
The Zitadel Node.js Client provides an idiomatic way to access the full gamut of Zitadel's v2 Management APIs from your Node.js backend.
> Please be aware that this client library is currently in an **incubating stage**.
> While it is available for use, the API and its functionality may evolve, potentially introducing
> breaking changes in future updates. We advise caution when considering it for production environments.
Installation [#installation]
This package is **published on GitHub Packages**, not on the public npm registry. Create or update a `.npmrc`
file in your project root with:
```ini
@zitadel:registry=https://npm.pkg.github.com
```
You can add the client library to your project using npm, pnpm, or yarn:
```bash
npm install @zitadel/zitadel-node
# or
pnpm add @zitadel/zitadel-node
# or
yarn add @zitadel/zitadel-node
```
Using the SDK [#using-the-sdk]
Your SDK offers three ways to authenticate with Zitadel. Each method has its
own benefits—choose the one that fits your situation best.
1\. Private Key JWT Authentication [#1-private-key-jwt-authentication]
**What is it?**
You use a JSON Web Token (JWT) that you sign with a private key stored in a
JSON file. This process creates a secure token.
**When should you use it?**
* **Best for production:** It offers strong security.
* **Advanced control:** You can adjust token settings like expiration.
**How do you use it?**
1. Save your private key in a JSON file.
2. Use the provided method to create an authenticator.
**Example:**
```ts
const client = await Zitadel.withPrivateKey(
"https://example.us1.zitadel.cloud",
"path/to/jwt-key.json",
);
try {
const response = await client.users.addHumanUser({
userServiceAddHumanUserRequest: {
username: "john.doe",
profile: {
givenName: "John",
familyName: "Doe"
},
email: {
email: "john.doe@example.com"
},
},
});
console.log("User created:", response);
} catch (e) {
if (e instanceof ApiException) {
console.error("Error:", e.message);
}
}
```
2\. Client Credentials Grant [#2-client-credentials-grant]
**What is it?**
This method uses a client ID and client secret to get a secure access token,
which is then used to authenticate.
**When should you use it?**
* **Simple and straightforward:** Good for server-to-server communication.
* **Trusted environments:** Use it when both servers are owned or trusted.
**How do you use it?**
1. Provide your client ID and client secret.
2. Use the provided method to create an authenticator.
**Example:**
```ts
const client = await Zitadel.withClientCredentials(
"https://example.us1.zitadel.cloud",
"id",
"secret",
);
try {
const response = await client.users.addHumanUser({
userServiceAddHumanUserRequest: {
username: "john.doe",
profile: {
givenName: "John",
familyName: "Doe"
},
email: {
email: "john.doe@example.com"
},
},
});
console.log("User created:", response);
} catch (e) {
if (e instanceof ApiException) {
console.error("Error:", e.message);
}
}
```
3\. Personal Access Tokens (PATs) [#3-personal-access-tokens-pa-ts]
**What is it?**
A Personal Access Token (PAT) is a pre-generated token that you can use to
authenticate without exchanging credentials every time.
**When should you use it?**
* **Easy to use:** Great for development or testing scenarios.
* **Quick setup:** No need for dynamic token generation.
**How do you use it?**
1. Obtain a valid personal access token from your account.
2. Use the provided method to create an authenticator.
**Example:**
```ts
const client = await Zitadel.withAccessToken(
"https://example.us1.zitadel.cloud",
"token",
);
try {
const response = await client.users.addHumanUser({
userServiceAddHumanUserRequest: {
username: "john.doe",
profile: {
givenName: "John",
familyName: "Doe"
},
email: {
email: "john.doe@example.com"
},
},
});
console.log("User created:", response);
} catch (e) {
if (e instanceof ApiException) {
console.error("Error:", e.message);
}
}
```
***
Choose the authentication method that best suits your needs based on your
environment and security requirements. For more details, please refer to the
[Zitadel documentation on authenticating service accounts](https://zitadel.com/docs/guides/integrate/service-accounts/authenticate-service-accounts).
Versioning [#versioning]
The client library's versioning is aligned with the Zitadel core project. The major version of the
client corresponds to the major version of Zitadel it is designed to work with. For example,
v2.x.x of the client is built for and tested against Zitadel v2, ensuring a predictable and stable integration.
Resources [#resources]
* [GitHub Repository](https://github.com/zitadel/zitadel-node): For source code, examples, and to report issues.
* [npm Package](https://github.com/zitadel/zitadel-node/pkgs/npm/zitadel-node): The official package artifact for npm.
# PHP Client
This guide covers the official Zitadel Management API Client for PHP, which allows you to programmatically manage resources in your Zitadel instance.
**This is a Management API Client, not an Authentication SDK.**
This library is designed for server-to-server communication to manage your Zitadel instance (e.g., creating users, managing projects, and updating settings). It is **not** intended for handling end-user login flows in your web application. For user authentication, you should use a standard OIDC library with your PHP framework of choice.
The Zitadel PHP Client provides an idiomatic way to access the full gamut of
Zitadel's v2 Management APIs from your PHP backend.
> Please be aware that this client library is currently in an **incubating stage**.
> While it is available for use, the API and its functionality may evolve, potentially introducing
> breaking changes in future updates. We advise caution when considering it for production environments.
Installation [#installation]
You can add the client library to your project using Composer:
```bash
composer require zitadel/client:"^4.0.0-beta1"
```
Using the SDK [#using-the-sdk]
Your SDK offers three ways to authenticate with Zitadel. Each method has its
own benefits—choose the one that fits your situation best.
1\. Private Key JWT Authentication [#1-private-key-jwt-authentication]
**What is it?**
You use a JSON Web Token (JWT) that you sign with a private key stored in a
JSON file. This process creates a secure token.
**When should you use it?**
* **Best for production:** It offers strong security.
* **Advanced control:** You can adjust token settings like expiration.
**How do you use it?**
1. Save your private key in a JSON file.
2. Use the provided method to load this key and create a JWT-based
authenticator.
**Example:**
```php
use \Zitadel\Client\Zitadel;
$zitadel = Zitadel::withPrivateKey("https://example.us1.zitadel.cloud", "path/to/jwt-key.json");
try {
$response = $zitadel->users->userServiceAddHumanUser([
'username' => 'john.doe',
'profile' => [
'givenName' => 'John',
'familyName' => 'Doe'
],
'email' => [
'email' => 'john@doe.com'
]
]);
echo "User created: " . print_r($response, true);
} catch (ApiException $e) {
echo "Error: " . $e->getMessage();
}
```
2\. Client Credentials Grant [#2-client-credentials-grant]
**What is it?**
This method uses a client ID and client secret to get a secure access token,
which is then used to authenticate.
**When should you use it?**
* **Simple and straightforward:** Good for server-to-server communication.
* **Trusted environments:** Use it when both servers are owned or trusted.
**How do you use it?**
1. Provide your client ID and client secret.
2. Build the authenticator
**Example:**
```php
use Zitadel\Client\Zitadel;
use Zitadel\Client\Model\UserServiceAddHumanUserRequest;
use \Zitadel\Client\Model\UserServiceAddHumanUserRequest;
use \Zitadel\Client\Model\UserServiceSetHumanProfile;
use \Zitadel\Client\Model\UserServiceSetHumanEmail;
$zitadel = Zitadel::withClientCredentials("https://example.us1.zitadel.cloud", "id", "secret");
try {
$response = $zitadel->users->addHumanUser((new UserServiceAddHumanUserRequest())
->setUsername('john.doe')
->setProfile(
(new UserServiceSetHumanProfile())
->setGivenName('John')
->setFamilyName('Doe')
)
->setEmail(
(new UserServiceSetHumanEmail())
->setEmail('john@doe.com')
));
echo "User created: " . print_r($response, true);
} catch (ApiException $e) {
echo "Error: " . $e->getMessage();
}
```
3\. Personal Access Tokens (PATs) [#3-personal-access-tokens-pa-ts]
**What is it?**
A Personal Access Token (PAT) is a pre-generated token that you can use to
authenticate without exchanging credentials every time.
**When should you use it?**
* **Easy to use:** Great for development or testing scenarios.
* **Quick setup:** No need for dynamic token generation.
**How do you use it?**
1. Obtain a valid personal access token from your account.
2. Create the authenticator with: `PersonalAccessTokenAuthenticator`
**Example:**
```php
use \Zitadel\Client\Zitadel;
use Zitadel\Client\Zitadel;
use Zitadel\Client\Model\UserServiceAddHumanUserRequest;
use \Zitadel\Client\Model\UserServiceAddHumanUserRequest;
use \Zitadel\Client\Model\UserServiceSetHumanProfile;
use \Zitadel\Client\Model\UserServiceSetHumanEmail;
$zitadel = Zitadel::withAccessToken("https://example.us1.zitadel.cloud", "token");
try {
$response = $zitadel->users->addHumanUser(
(new UserServiceAddHumanUserRequest())
->setUsername('john.doe')
->setProfile(
(new UserServiceSetHumanProfile())
->setGivenName('John')
->setFamilyName('Doe')
)
->setEmail(
(new UserServiceSetHumanEmail())
->setEmail('john@doe.com')
)
);
echo "User created: " . print_r($response, true);
} catch (ApiException $e) {
echo "Error: " . $e->getMessage();
}
```
***
Choose the authentication method that best suits your needs based on your
environment and security requirements. For more details, please refer to the
[Zitadel documentation on authenticating service accounts](https://zitadel.com/docs/guides/integrate/service-accounts/authenticate-service-accounts).
Versioning [#versioning]
The client library's versioning is aligned with the Zitadel core project. The major version of the
client corresponds to the major version of Zitadel it is designed to work with. For example,
v2.x.x of the client is built for and tested against Zitadel v2, ensuring a predictable and stable integration.
Resources [#resources]
* [GitHub Repository](https://github.com/zitadel/client-php): For source code, examples, and to report issues.
* [Packagist Package](https://packagist.org/packages/zitadel/client): The official package artifact for Composer.
# Python Client
This guide covers the official Zitadel Management API Client for Python (3.9+), which allows you to programmatically manage resources in your Zitadel instance.
**This is a Management API Client, not an Authentication SDK.**
This library is designed for server-to-server communication to manage your Zitadel instance (e.g., creating users, managing projects, and updating settings). It is **not** intended for handling end-user login flows in your web application. For user authentication, you should use a standard OIDC library with your Python framework of choice, such as `mozilla-django-oidc` for Django or `Authlib` for Flask.
The Zitadel Python Client provides an idiomatic way to access the full gamut of Zitadel's v2 Management APIs from your Python backend.
> Please be aware that this client library is currently in an **incubating stage**.
> While it is available for use, the API and its functionality may evolve, potentially introducing
> breaking changes in future updates. We advise caution when considering it for production environments.
Installation [#installation]
You can add the client library to your project using pip:
```bash
pip install --pre zitadel-client
```
Using the SDK [#using-the-sdk]
Your SDK offers three ways to authenticate with Zitadel. Each method has its
own benefits—choose the one that fits your situation best.
1\. Private Key JWT Authentication [#1-private-key-jwt-authentication]
**What is it?**
You use a JSON Web Token (JWT) that you sign with a private key stored in a
JSON file. This process creates a secure token.
**When should you use it?**
* **Best for production:** It offers strong security.
* **Advanced control:** You can adjust token settings like expiration.
**How do you use it?**
1. Save your private key in a JSON file.
2. Use the provided method to load this key and create a JWT-based
authenticator.
**Example:**
```python
import zitadel_client as zitadel
from zitadel_client.exceptions import ApiError
from zitadel_client.models import (
UserServiceAddHumanUserRequest,
UserServiceSetHumanEmail,
UserServiceSetHumanProfile,
)
zitadel = zitadel.Zitadel.with_private_key("https://example.us1.zitadel.cloud", "path/to/jwt-key.json")
try:
request = UserServiceAddHumanUserRequest(
username="john.doe",
profile=UserServiceSetHumanProfile(
givenName="John",
familyName="Doe"
),
email=UserServiceSetHumanEmail(
email="john@doe.com"
),
)
response = zitadel.users.add_human_user(request)
print("User created:", response)
except ApiError as e:
print("Error:", e)
```
2\. Client Credentials Grant [#2-client-credentials-grant]
**What is it?**
This method uses a client ID and client secret to get a secure access token,
which is then used to authenticate.
**When should you use it?**
* **Simple and straightforward:** Good for server-to-server communication.
* **Trusted environments:** Use it when both servers are owned or trusted.
**How do you use it?**
1. Provide your client ID and client secret.
2. Build the authenticator
**Example:**
```python
import zitadel_client as zitadel
from zitadel_client.exceptions import ApiError
from zitadel_client.models import (
UserServiceAddHumanUserRequest,
UserServiceSetHumanEmail,
UserServiceSetHumanProfile,
)
zitadel = zitadel.Zitadel.with_client_credentials("https://example.us1.zitadel.cloud", "id", "secret")
try:
request = UserServiceAddHumanUserRequest(
username="john.doe",
profile=UserServiceSetHumanProfile(
givenName="John",
familyName="Doe"
),
email=UserServiceSetHumanEmail(
email="john@doe.com"
),
)
response = zitadel.users.add_human_user(request)
print("User created:", response)
except ApiError as e:
print("Error:", e)
```
3\. Personal Access Tokens (PATs) [#3-personal-access-tokens-pa-ts]
**What is it?**
A Personal Access Token (PAT) is a pre-generated token that you can use to
authenticate without exchanging credentials every time.
**When should you use it?**
* **Easy to use:** Great for development or testing scenarios.
* **Quick setup:** No need for dynamic token generation.
**How do you use it?**
1. Obtain a valid personal access token from your account.
2. Create the authenticator with: `PersonalAccessTokenAuthenticator`
**Example:**
```python
import zitadel_client as zitadel
from zitadel_client.exceptions import ApiError
from zitadel_client.models import (
UserServiceAddHumanUserRequest,
UserServiceSetHumanEmail,
UserServiceSetHumanProfile,
)
zitadel = zitadel.Zitadel.with_access_token("https://example.us1.zitadel.cloud", "token")
try:
request = UserServiceAddHumanUserRequest(
username="john.doe",
profile=UserServiceSetHumanProfile(
givenName="John",
familyName="Doe"
),
email=UserServiceSetHumanEmail(
email="john@doe.com"
),
)
response = zitadel.users.add_human_user(request)
print("User created:", response)
except ApiError as e:
print("Error:", e)
```
***
Choose the authentication method that best suits your needs based on your
environment and security requirements. For more details, please refer to the
[Zitadel documentation on authenticating service accounts](https://zitadel.com/docs/guides/integrate/service-accounts/authenticate-service-accounts).
Versioning [#versioning]
The client library's versioning is aligned with the Zitadel core project. The major version of the
client corresponds to the major version of Zitadel it is designed to work with. For example,
v2.x.x of the client is built for and tested against Zitadel v2, ensuring a predictable and stable integration.
Resources [#resources]
* [GitHub Repository](https://github.com/zitadel/client-python): For source code, examples, and to report issues.
* [PyPI Package](https://pypi.org/project/zitadel-client): The official package artifact for pip.
# Ruby Client
This guide covers the official Zitadel Management API Client for Ruby (3.1+), which allows you to programmatically manage resources in your Zitadel instance.
**This is a Management API Client, not an Authentication SDK.**
This library is designed for server-to-server communication to manage your Zitadel instance (e.g., creating users, managing projects, and updating settings). It is **not** intended for handling end-user login flows in your web application. For user authentication, you should use a standard OIDC library with your Ruby framework of choice.
The Zitadel Ruby Client provides an idiomatic way to access the full gamut of Zitadel's v2 Management APIs from your Ruby backend.
> Please be aware that this client library is currently in an **incubating stage**.
> While it is available for use, the API and its functionality may evolve, potentially introducing
> breaking changes in future updates. We advise caution when considering it for production environments.
Installation [#installation]
You can add the client library to your project using RubyGems. Add this line to your application's Gemfile:
```ruby
gem install zitadel-client --pre
```
Using the SDK [#using-the-sdk]
Your SDK offers three ways to authenticate with Zitadel. Each method has its
own benefits—choose the one that fits your situation best.
1\. Private Key JWT Authentication [#1-private-key-jwt-authentication]
**What is it?**
You use a JSON Web Token (JWT) that you sign with a private key stored in a
JSON file. This process creates a secure token.
**When should you use it?**
* **Best for production:** It offers strong security.
* **Advanced control:** You can adjust token settings like expiration.
**How do you use it?**
1. Save your private key in a JSON file.
2. Use the provided method to create an authenticator.
**Example:**
```ruby
require 'zitadel-client'
require 'securerandom'
client = Zitadel::Client::Zitadel.with_private_key("https://example.us1.zitadel.cloud", "path/to/jwt-key.json")
begin
response = client.users.add_human_user(
Zitadel::Client::UserServiceAddHumanUserRequest.new(
username: SecureRandom.hex,
profile: Zitadel::Client::UserServiceSetHumanProfile.new(
given_name: 'John',
family_name: 'Doe'
),
email: Zitadel::Client::UserServiceSetHumanEmail.new(
email: "john.doe@example.com"
)
)
)
puts "User created: #{response}"
rescue StandardError => e
puts "Error: #{e.message}"
end
```
2\. Client Credentials Grant [#2-client-credentials-grant]
**What is it?**
This method uses a client ID and client secret to get a secure access token,
which is then used to authenticate.
**When should you use it?**
* **Simple and straightforward:** Good for server-to-server communication.
* **Trusted environments:** Use it when both servers are owned or trusted.
**How do you use it?**
1. Provide your client ID and client secret.
2. Use the provided method to create an authenticator.
**Example:**
```ruby
require 'zitadel-client'
require 'securerandom'
client = Zitadel::Client::Zitadel.with_client_credentials("https://example.us1.zitadel.cloud", "id", "secret")
begin
response = client.users.add_human_user(
Zitadel::Client::UserServiceAddHumanUserRequest.new(
username: SecureRandom.hex,
profile: Zitadel::Client::UserServiceSetHumanProfile.new(
given_name: 'John',
family_name: 'Doe'
),
email: Zitadel::Client::UserServiceSetHumanEmail.new(
email: "john.doe@example.com"
)
)
)
puts "User created: #{response}"
rescue StandardError => e
puts "Error: #{e.message}"
end
```
3\. Personal Access Tokens (PATs) [#3-personal-access-tokens-pa-ts]
**What is it?**
A Personal Access Token (PAT) is a pre-generated token that you can use to
authenticate without exchanging credentials every time.
**When should you use it?**
* **Easy to use:** Great for development or testing scenarios.
* **Quick setup:** No need for dynamic token generation.
**How do you use it?**
1. Obtain a valid personal access token from your account.
2. Use the provided method to create an authenticator.
**Example:**
```ruby
require 'zitadel-client'
require 'securerandom'
client = Zitadel::Client::Zitadel.with_access_token("https://example.us1.zitadel.cloud", "token")
begin
response = client.users.add_human_user(
Zitadel::Client::UserServiceAddHumanUserRequest.new(
username: SecureRandom.hex,
profile: Zitadel::Client::UserServiceSetHumanProfile.new(
given_name: 'John',
family_name: 'Doe'
),
email: Zitadel::Client::UserServiceSetHumanEmail.new(
email: "john.doe@example.com"
)
)
)
puts "User created: #{response}"
rescue StandardError => e
puts "Error: #{e.message}"
end
```
***
Choose the authentication method that best suits your needs based on your
environment and security requirements. For more details, please refer to the
[Zitadel documentation on authenticating service accounts](https://zitadel.com/docs/guides/integrate/service-accounts/authenticate-service-accounts).
Debugging [#debugging]
The SDK supports debug logging, which can be enabled for troubleshooting
and debugging purposes. You can enable debug logging by setting the `debug`
flag to `true` when initializing the `Zitadel` client, like this:
```ruby
zitadel = zitadel.Zitadel("your-zitadel-base-url", 'your-valid-token', lambda config: config.debug = True)
```
When enabled, the SDK will log additional information, such as HTTP request
and response details, which can be useful for identifying issues in the
integration or troubleshooting unexpected behavior.
Versioning [#versioning]
The client library's versioning is aligned with the Zitadel core project. The major version of the
client corresponds to the major version of Zitadel it is designed to work with. For example,
v2.x.x of the client is built for and tested against Zitadel v2, ensuring a predictable and stable integration.
Resources [#resources]
* [GitHub Repository](https://github.com/zitadel/client-ruby): For source code, examples, and to report issues.
* [RubyGems Package](https://rubygems.org/gems/zitadel-client): The official package artifact for RubyGems.
# APIs / Backend Quickstart Guides
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
# Frontend (SPA) Quickstart Guides
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
# Full-Stack / SSR Quickstart Guides
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
# Mobile & Native Quickstart Guides
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
# Web App (Server-Side) Quickstart Guides
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
# Migrate from Actions 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.
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.
Why Actions Changed from V1 to V2 [#why-actions-changed-from-v-1-to-v-2]
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 [#architecture-deep-dive-v-1-vs-v-2]
Actions V1: The Embedded Model [#actions-v-1-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 settings
* **V1 Triggers:** Pre-event hooks (fire before operation) and Post-event hooks (fire after success)
Actions V2: The Webhook Model [#actions-v-2-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 [#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 [#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 [#when-to-use-each-v-2-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 [#detailed-flow-explanations]
Flow 1: Internal Authentication [#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 [#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 [#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 [#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 [#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)
# 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 [#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/administrators)
* Your ZITADEL instance needs to have the actions feature enabled.
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.
Start example target [#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.
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).
```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)
}
```
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)).
Create target [#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 ' \
--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 [#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 ' \
--data-raw '{
"condition": {
"event": {
"event": "user.human.added"
}
},
"targets": [
""
]
}'
```
Example call [#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 user.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/users/new' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer ' \
--data-raw '{
"organizationId": "336392597046099971",
"human":
{
"profile":
{
"givenName": "Minnie",
"familyName": "Mouse",
"nickName": "Mini",
"displayName": "Minnie Mouse",
"preferredLanguage": "en",
"gender": "GENDER_FEMALE"
},
"email":
{
"email": "mini@mouse.com"
}
}
}'
```
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": "mini@mouse.com",
"gender": 1,
"lastName": "Mouse",
"nickName": "Mini",
"userName": "mini@mouse.com",
"firstName": "Minnie",
"displayName": "Minnie Mouse",
"preferredLanguage": "en"
}
}
```
The event\_payload is base64 encoded and has the following content:
```json
{
"email": "mini@mouse.com",
"gender": 1,
"lastName": "Mouse",
"nickName": "Mini",
"userName": "mini@mouse.com",
"firstName": "Minnie",
"displayName": "Minnie Mouse",
"preferredLanguage": "en"
}
```
Conclusion [#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).
# 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 [#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/administrators)
* Your ZITADEL instance needs to have the actions feature enabled.
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.
Available functions [#available-functions]
The available conditions can be found under [all available Functions](/reference/api/action/zitadel.action.v2.ActionService.ListExecutionFunctions).
Start example target [#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.
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).
```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)
}
```
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)).
Create target [#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 ' \
--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 [#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 ' \
--data-raw '{
"condition": {
"function": {
"name": "preuserinfo"
}
},
"targets": [
""
]
}'
```
Example call [#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 [#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).
# 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 [#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/administrators)
* Your ZITADEL instance needs to have the actions feature enabled.
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.
Available functions [#available-functions]
The available conditions can be found under [all available Functions](/reference/api/action/zitadel.action.v2.ActionService.ListExecutionFunctions).
Start example target [#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.
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).
```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)
}
```
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)).
Create target [#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 ' \
--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 [#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 ' \
--data-raw '{
"condition": {
"function": {
"name": "preuserinfo"
}
},
"targets": [
""
]
}'
```
Example call [#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" : "user@example.com",
"preferred_login_name" : "zitadel@zitadel.localhost",
"human" : {
"first_name" : "Example firstname",
"last_name" : "Example lastname",
"display_name" : "Example displayname",
"preferred_language" : "en",
"email" : "user@example.com",
"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 [#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).
# 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 [#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/administrators)
* Your ZITADEL instance needs to have the actions feature enabled.
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.
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.
Start example target [#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.
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).
```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)
}
```
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)).
Create target [#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 ' \
--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 [#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 ' \
--data-raw '{
"condition": {
"request": {
"method": "/zitadel.user.v2.UserService/CreateUser"
}
},
"targets": [
""
]
}'
```
Example call [#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 user.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/users/new' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer ' \
--data-raw '{
"organizationId": "344648897353810062",
"human":
{
"profile":
{
"givenName": "Minnie",
"familyName": "Mouse",
"nickName": "Mini",
"displayName": "Minnie Mouse",
"preferredLanguage": "en",
"gender": "GENDER_FEMALE"
},
"email":
{
"email": "mini@mouse.com"
}
}
}'
```
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 ' \
--data-raw '{
"organizationId": "344648897353810062",
"human":
{
"profile":
{
"givenName": "Minnie",
"familyName": "Mouse",
"nickName": "Mini",
"displayName": "Minnie Mouse",
"preferredLanguage": "en",
"gender": "GENDER_FEMALE"
},
"email":
{
"email": "mini@mouse.com"
},
"metadata":
[
{
"key": "organization",
"value": "ZHVuZGVyIG1pZmZsaW4="
}
]
}
}'
```
Conclusion [#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).
# Verify Payload Integrity
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 [#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/administrators)
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.
Payload and Validation Types [#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 [#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 ' \
--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 [#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 `` 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//publickeys' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer ' \
--data-raw '{
"publicKey": ""
}'
```
Be sure to also activate the public key for the target using the returned `` from the previous request:
```shell
curl -L -X PUT 'https://${CUSTOM_DOMAIN}/v2/actions/targets//publickeys//activate' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer '
```
Set 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](/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 ' \
--data-raw '{
"condition": {
"request": {
"method": "/zitadel.user.v2.UserService/CreateUser"
}
},
"targets": [
""
]
}'
```
Start example target [#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.
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)).
Example call [#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 user.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/users/new' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer ' \
--data-raw '{
"organizationId": "344648897353810062",
"human":
{
"profile":
{
"givenName": "Minnie",
"familyName": "Mouse",
"nickName": "Mini",
"displayName": "Minnie Mouse",
"preferredLanguage": "en",
"gender": "GENDER_FEMALE"
},
"email":
{
"email": "mini+test@mouse.com"
}
}
}'
```
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": "mini+test@mouse.com"
}
}
},
"headers":
{
"Content-Type":
[
"application/grpc"
],
"Host":
[
"localhost:8080"
],
"X-Forwarded-For":
[
"::1"
],
"X-Forwarded-Host":
[
"localhost:8080"
]
}
}
```
Conclusion [#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).
# 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 [#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/administrators)
* Your ZITADEL instance needs to have the actions feature enabled.
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.
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.
Start example target [#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.
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).
```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)
}
```
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)).
Create target [#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 ' \
--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 [#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 ' \
--data-raw '{
"condition": {
"request": {
"method": "/zitadel.user.v2.UserService/CreateUser"
}
},
"targets": [
""
]
}'
```
Example call [#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 user.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/users/new' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer ' \
--data-raw '{
"organizationId": "344648897353810062",
"human":
{
"profile":
{
"givenName": "Minnie",
"familyName": "Mouse",
"nickName": "Mini",
"displayName": "Minnie Mouse",
"preferredLanguage": "en",
"gender": "GENDER_FEMALE"
},
"email":
{
"email": "mini+test@mouse.com"
}
}
}'
```
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": "mini+test@mouse.com"
}
}
},
"headers":
{
"Content-Type":
[
"application/grpc"
],
"Host":
[
"localhost:8080"
],
"X-Forwarded-For":
[
"::1"
],
"X-Forwarded-Host":
[
"localhost:8080"
]
}
}
```
Conclusion [#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).
# 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 [#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/administrators)
* Your ZITADEL instance needs to have the actions feature enabled.
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.
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.
Start example target [#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.
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).
```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)
}
```
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)).
Create target [#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 ' \
--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 [#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 ' \
--data-raw '{
"condition": {
"response": {
"method": "/zitadel.user.v2.UserService/RetrieveIdentityProviderIntent"
}
},
"targets": [
""
]
}'
```
Example call [#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": "test-user@zitadel.ch",
"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": "test-user@zitadel.ch",
"isVerified": true
},
"metadata": [
{"key": "organization", "value": "Y29tcGFueQ=="}
]
}
}
```
Conclusion [#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 applications including the new login UI.
Find more information about the actions feature in the [API documentation](/concepts/features/actions_v2).
# 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 [#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/administrators)
* Your ZITADEL instance needs to have the actions feature enabled.
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.
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.
Start example target [#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.
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).
```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)
}
```
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)).
Create target [#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 ' \
--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 [#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 ' \
--data-raw '{
"condition": {
"response": {
"method": "/zitadel.user.v2.UserService/CreateUser"
}
},
"targets": [
""
]
}'
```
Example call [#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 user.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/users/new' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer ' \
--data-raw '{
"organizationId": "344648897353810062",
"human":
{
"profile":
{
"givenName": "Minnie",
"familyName": "Mouse",
"nickName": "Mini",
"displayName": "Minnie Mouse",
"preferredLanguage": "en",
"gender": "GENDER_FEMALE"
},
"email":
{
"email": "mini@mouse.com"
}
}
}'
```
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": "mini@mouse.com"
}
}
},
"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 [#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).
# 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 [#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 [#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"
}
}
```
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.
Sent information Response [#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"
}
}
```
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.
Sent information Function [#sent-information-function]
Information sent and expected back are specific to the function.
PreUserinfo [#pre-userinfo]
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": "Organization Domain 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 [#pre-access-token]
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": "Organization Domain 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 [#pre-saml-response]
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 [#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 [#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 [#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 [#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 [#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 [#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": [
""
]
}
```
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": [
"",
""
]
}
```
The called Targets on `/zitadel.user.v2.UserService/AddHumanUser` would be, in order:
1. ``
2. ``
Condition for Requests and Responses [#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 [#condition-for-functions]
The available conditions can be found under [all available Functions](/reference/api/action/zitadel.action.v2.ActionService.ListExecutionFunctions).
Condition for Events [#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 [#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.
To interrupt the execution while forwarding this error, "interruptOnError" must be **true** for the Target
# Testing Actions V2 with Webhook.site
1\. Introduction [#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-prerequisites]
2.1. Local Listener [#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 [#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 [#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.
***
4\. Run Your Listener [#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.
***
5\. Create Target in ZITADEL [#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 ' \
--data-raw '{
"name": "local webhook",
"restWebhook": {
"interruptOnError": true
},
"endpoint": "https://webhook.site/29fa2769-e2f6-44ff-8eb0-2434b83507d8/webhook",
"timeout": "10s"
}'
```
***
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 [#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, `` 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 ' \
--data-raw '{
"condition": {
"request": {
"method": "/zitadel.user.v2.UserService/AddHumanUser"
}
},
"targets": [
""
]
}'
```
***
7\. Example Call [#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 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 ' \
--data-raw '{
"profile": {
"givenName": "Example_given",
"familyName": "Example_family"
},
"email": {
"email": "example@example.com"
}
}'
```
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:
Your Webhook.site should look like this:
***
8\. Done [#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.
# Profile Pre-filling from External IdP
Automatically pre-fill user data [#automatically-pre-fill-user-data]
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
```
# Set up Apple as an Identity Provider in ZITADEL
Open the Apple Identity Provider Template [#open-the-apple-identity-provider-template]
Click on the ZITADEL Callback URL to copy it to your clipboard.
You will have to paste it in the Apple service later.
For Apple with V1 hosted login, the callback URL ends with `/form` (e.g., `${CUSTOM_DOMAIN}/ui/login/login/externalidp/callback/form`) because Apple uses a Form Post to return the authorization response.
For V2, use the standard callback URL `${CUSTOM_DOMAIN}/idps/callback`, which already accepts POST requests.
Apple Configuration [#apple-configuration]
Register a new App [#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 [#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
Register a new key [#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 Setup [#zitadel-setup]
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 log in 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.
Activate IdP [#activate-id-p]
Ensure your Login Policy allows External IDPs [#ensure-your-login-policy-allows-external-id-ps]
Test the setup [#test-the-setup]
# Set up Entra ID as an Identity Provider in ZITADEL
Open the Microsoft Identity Provider Template [#open-the-microsoft-identity-provider-template]
Click on the ZITADEL Callback URL to copy it to your clipboard.
You will have to paste it in the Entra ID Client later.
Entra ID Configuration [#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 application [#register-a-new-application]
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 log in (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
Add client secret [#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 afterward
3. Copy the value of the secret. You will not be able to see the value again after some time
Token settings [#token-settings]
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 settings 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
API permissions [#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"
ZITADEL Setup [#zitadel-setup]
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 log in.
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 settings.
Activate IdP [#activate-id-p]
Ensure your Login Policy allows External IDPs [#ensure-your-login-policy-allows-external-id-ps]
Test the setup [#test-the-setup]
# Set up Entra ID as a SAML Service Provider in ZITADEL
Entra ID SAML Configuration [#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 [#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"
Disable required assignment [#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
Setup SAML [#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
ZITADEL Setup [#zitadel-setup]
Go to the IdP Providers Overview [#go-to-the-id-p-providers-overview]
Create a new SAML Service Provider (SP) [#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
Configure Basic SAML Configuration [#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.
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
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).
Enable the Microsoft Entra Button in ZITADELs Login Page [#enable-the-microsoft-entra-button-in-zitade-ls-login-page]
Go back to ZITADEL and activate the IdP.
Activate IdP [#activate-id-p]
Ensure your Login Policy allows External IDPs [#ensure-your-login-policy-allows-external-id-ps]
Test the setup [#test-the-setup]
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.
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.
Click **Microsoft Entra**
Add Action to map user attributes [#add-action-to-map-user-attributes]
```js reference
https://github.com/zitadel/actions/blob/main/examples/entra_id_saml_prefil_register_form.js
```
# Configure a Generic OIDC Identity Provider in ZITADEL
Open the Generic OIDC Provider Template [#open-the-generic-oidc-provider-template]
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 settings later.
OIDC Provider Configuration [#oidc-provider-configuration]
Register a new client/application [#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 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 Setup [#zitadel-setup]
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.
Activate IdP [#activate-id-p]
Ensure your Login Policy allows External IDPs [#ensure-your-login-policy-allows-external-id-ps]
Test the setup [#test-the-setup]
# Configure GitHub as an Identity Provider in ZITADEL
Open the GitHub Identity Provider Template [#open-the-git-hub-identity-provider-template]
Click on the ZITADEL Callback URL to copy it to your clipboard.
You will have to paste it to the GitHub OAuth application later.
GitHub Configuration [#git-hub-configuration]
Register a new application [#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".
Client ID and secret [#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.
ZITADEL Setup [#zitadel-setup]
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.
Activate IdP [#activate-id-p]
Ensure your Login Policy allows External IDPs [#ensure-your-login-policy-allows-external-id-ps]
Test the setup [#test-the-setup]
Optional: Add ZITADEL action to autofill userdata [#optional-add-zitadel-action-to-autofill-userdata]
```js reference
https://github.com/zitadel/actions/blob/main/examples/github_identity_provider.js
```
# Configure GitLab as an Identity Provider in ZITADEL
Open the GitLab Identity Provider Template [#open-the-git-lab-identity-provider-template]
Click on the ZITADEL Callback URL to copy it to your clipboard.
You will have to paste it to the GitLab Application later.
GitLab Configuration [#git-lab-configuration]
Register a new application [#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.
Client ID and secret [#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.
ZITADEL Setup [#zitadel-setup]
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.
Activate IdP [#activate-id-p]
Ensure your Login Policy allows External IDPs [#ensure-your-login-policy-allows-external-id-ps]
Test the setup [#test-the-setup]
Optional: Add ZITADEL action to autofill userdata [#optional-add-zitadel-action-to-autofill-userdata]
```js reference
https://github.com/zitadel/actions/blob/main/examples/gitlab_identity_provider.js
```
# Configure Google as an Identity Provider in ZITADEL
Open the Google Identity Provider Template [#open-the-google-identity-provider-template]
Click on the ZITADEL Callback URL to copy it to your clipboard.
You will have to paste it in the Google Cloud Platform later.
Google Configuration [#google-configuration]
Register a new application [#register-a-new-application]
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
Client ID and secret [#client-id-and-secret]
You will need the Client ID and Client secret to configure the Google Identity Provider in ZITADEL.
ZITADEL Setup [#zitadel-setup]
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.
Activate IdP [#activate-id-p]
Ensure your Login Policy allows External IDPs [#ensure-your-login-policy-allows-external-id-ps]
Test the setup [#test-the-setup]
# Let Users Login with Preferred Identity Provider
External Identity Providers and SSO authentication [#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 [#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 [#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 set up 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-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 [#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 [#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 [#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 setup 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**.
Setting up IdP Providers [#setting-up-id-p-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 set up, along with available provider templates. Selecting any listed IdP will guide you through the process of setting up that specific Identity Provider.
Available guides [#available-guides]
In the guides below, some of which utilize the Generic OIDC or SAML templates for settings, 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 [#configuring-id-ps-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 settings.
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 [#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 [#configure-external-id-ps-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 [#create-an-external-id-p-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 [#create-an-external-id-p-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 [#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)
# JWT IdP
In simple terms [#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.
Step-by-Step Process [#step-by-step-process]
1. User opens new application
2. New app initiates an authorization code flow via ZITADEL
3. If no session is found, ZITADEL redirects user to JWT endpoint
4. JWT endpoint forwards token to ZITADEL
5. ZITADEL responds to the JWT endpoint with an authorization code
6. JWT endpoint redirects user back to the new app
7. New app completes the authorization code flow
When should I use JWT IdP? [#when-should-i-use-jwt-id-p]
Use JWT IdP if:
* You have an existing application, gateway, 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-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 following parameters.
Issuer [#issuer]
The party who issued the token. This is used to validate the `iss` claim in the JWT.
*Example: `https://issuer.test.internal`*
Header name [#header-name]
The name of the header in which the JWT will be sent in the proxy request to ZITADEL.
Keys Endpoint [#keys-endpoint]
Where ZITADEL fetches the public keys for signature validation of the JWTs. This endpoint should return the keys in
the format specified in [RFC7517](https://datatracker.ietf.org/doc/html/rfc7517).
*Example: `https://issuer.test.internal/keys`*
JWT endpoint [#jwt-endpoint]
Where ZITADEL redirects users to retrieve the JWT.
*Example: `https://apps.test.com/existing/auth-new`*
The old app generates a JWT which it sends to ZITADEL. ZITADEL passes some query parameters in the request to the JWT
endpoint. These parameters should be included in the proxy request to ZITADEL. They ensure the request is authentic.
Example implementation of the endpoint:
```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: ZITADEL_JWT_IDP_ENDPOINT must match your configured JWT IdP endpoint (differs between Login V1 and V2)
// For example, Login V2: https://accounts.test.com/idps/jwt, Login V1: https://accounts.test.com/ui/login/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`.
In case you have a WAF/gateway which handles the authentication and forwards the JWT to services behind the WAF, the
JWT endpoint would exist behind the WAF and `getJwtForCurrentSession` would get the token from that header.
`ZITADEL_JWT_IDP_ENDPOINT` should be set to the custom domain of your ZITADEL instance with the respective path of
the login callback:
Login V1: `https://accounts.test.com/ui/login/idps/jwt`
Login V2: `https://accounts.test.com/idps/jwt`
After receiving the JWT, ZITADEL will validate that the JWT is valid. It does that by ensuring that:
* the **signature** matches using keys from the Keys Endpoint.
* the `issuer` (`iss` claim) matches the configured issuer.
* The JWT is **not expired** using the `exp` claim.
- 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.
***
Use-cases [#use-cases]
Legacy app in internal network [#legacy-app-in-internal-network]
You have an existing application which exists in an internal network which handles its own user authentication. A new
application is built using ZITADEL but the userbase of the existing application needs to be able to open the new
application from within the existing one without creating a new account or having to sign in.
* Existing application: `apps.test.com/existing/`
* Existing internal auth service: `issuer.test.internal`
* New application: `new.test.com`
* ZITADEL Login UI: `accounts.test.com`
What needs to happen [#what-needs-to-happen]
* Add a link in the existing application which directs the user to `new.test.com`.
* Create an application on the domain of the existing application which serves the JWT and keys endpoints.
* JWT endpoint: `apps.test.com/existing/auth-new`
* Keys endpoint: `issuer.test.internal/keys`
* Configure the JWT endpoint to send the JWT in the specified header
* Header name: `x-custom-tkn`
* Configure the JWT IdP in ZITADEL with the parameters above
* JWT endpoint: `apps.test.com/existing/auth-new`
* Keys endpoint: `issuer.test.internal/keys`
* Header name: `x-custom-tkn`
* Issuer: `issuer.test.internal`
Result flow [#result-flow]
1. User opens existing app
2. User authenticates against existing IdP
3. User clicks on link to new app
4. User is redirected to ZITADEL
5. ZITADEL redirects to JWT endpoint
6. JWT endpoint creates JWT from existing session (using cookie)
7. JWT endpoint forwards request with JWT to ZITADEL
8. ZITADEL responds with authorization code
9. New app can exchange authorization code with access-token
Clarifications and best practices [#clarifications-and-best-practices]
Why must the JWT Endpoint be on the same domain as the existing app? [#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? [#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? [#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.
# Configure Keycloak as an Identity Provider in ZITADEL
Open the Generic OIDC Provider Template [#open-the-generic-oidc-provider-template]
Click on the ZITADEL Callback URL to copy it to your clipboard.
You will have to paste it in the Keycloak Client later.
Keycloak Configuration [#keycloak-configuration]
Register a new client [#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
ZITADEL settings [#zitadel-settings]
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.
Activate IdP [#activate-id-p]
Ensure your Login Policy allows External IDPs [#ensure-your-login-policy-allows-external-id-ps]
Test the setup [#test-the-setup]
# Configure LDAP as an Identity Provider in ZITADEL
How it works [#how-it-works]
ZITADEL Setup [#zitadel-setup]
Go to the IdP Providers Overview [#go-to-the-id-p-providers-overview]
Create a new LDAP Provider [#create-a-new-ldap-provider]
Fill in the following fields in the LDAP template.
We highly recommend to use LDAPS or StartTLS enable servers.
Otherwise, your users passwords are sent in clear text through the wire.
**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 [user@example.com](mailto:user@example.com) and filters "uid" and "email" the resulting SearchQuery contains "(|(uid=[user@example.com](mailto:user@example.com))(email=[user@example.com](mailto:user@example.com)))"
**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.
Activate IdP [#activate-id-p]
Ensure your Login Policy allows External IDPs [#ensure-your-login-policy-allows-external-id-ps]
Test the setup [#test-the-setup]
# LinkedIn OAuth Identity Provider in ZITADEL
LinkedIn Configuration [#linked-in-configuration]
Register a new client [#register-a-new-client]
1. Go to the LinkedIn Developer console and create a new App: [https://www.linkedin.com/developers/apps/new](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: `${CUSTOM_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
ZITADEL Setup [#zitadel-setup]
Add custom login policy [#add-custom-login-policy]
Go to the IdP Providers Overview [#go-to-the-id-p-providers-overview]
Create a new Generic OAuth Provider [#create-a-new-generic-o-auth-provider]
Activate IdP [#activate-id-p]
Test the setup [#test-the-setup]
Optional: Add ZITADEL action to autofill userdata [#optional-add-zitadel-action-to-autofill-userdata]
```js reference
https://github.com/zitadel/actions/blob/main/examples/linkedin_identity_provider.js
```
# Migrate from Generic to Specific Identity Provider
Migrate Generic OIDC Provider [#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.
Google Configuration [#google-configuration]
The available settings are described in [Google Configuration](./google).
Entra ID Configuration [#entra-id-configuration]
The available settings are described in [Entra ID Configuration](./azure-ad-oidc).
Migrate with Terraform [#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.
# Set up MockSAML as an Identity Provider in ZITADEL
MockSAML is not intended for any production environment, only for test purposes
MockSAML [#mock-saml]
Download metadata [#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 settings [#zitadel-settings]
Go to the IdP providers overview [#go-to-the-id-p-providers-overview]
Create a new SAML ServiceProvider [#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 you ZITADEL instance.
All the necessary settings are contained in the metadata which has to be exchanged by the ServiceProvider and the IdentityProvider.
Download metadata [#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-id-p]
Ensure your Login Policy allows External IDPs [#ensure-your-login-policy-allows-external-id-ps]
Configure an action to autofill user data [#configure-an-action-to-autofill-user-data]
Required for Login V2 [#required-for-login-v-2]
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) [#optional-for-login-v-1-using-actions-v-1]
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 [#test-the-setup]
# Set up OKTA as an OIDC Identity Provider in ZITADEL
Open the Generic OIDC Provider Template [#open-the-generic-oidc-provider-template]
Click on the ZITADEL Callback URL to copy it to your clipboard.
You will have to paste it in the OKTA application later.
OKTA Configuration [#okta-configuration]
Register a new client [#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
ZITADEL Setup [#zitadel-setup]
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.
Activate IdP [#activate-id-p]
Ensure your Login Policy allows External IDPs [#ensure-your-login-policy-allows-external-id-ps]
Test the setup [#test-the-setup]
Optional: Add ZITADEL action to autofill userdata [#optional-add-zitadel-action-to-autofill-userdata]
```js reference
https://github.com/zitadel/actions/blob/main/examples/okta_identity_provider.js
```
# Set up OKTA as a SAML Identity Provider in ZITADEL
ZITADEL Setup [#zitadel-setup]
Go to the IdP Providers Overview [#go-to-the-id-p-providers-overview]
Create a new SAML Provider [#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 settings 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)
After you created the SAML Provider in ZITADEL, you can copy the URLs you need to configure in your OKTA application.
OKTA Configuration [#okta-configuration]
Register a new client [#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 settings
6. Copy the metadata URL from the details
Add Attribute Statements [#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 |
Assign Users to Application [#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
Finish ZITADEL Setup [#finish-zitadel-setup]
You are now finished with the settings in OKTA and you can switch back to your identity provider settings in ZITADEL.
Add Metadata Xml [#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 settings 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)
You can also fill the optional fields if needed:
Activate IdP [#activate-id-p]
Ensure your Login Policy allows External IDPs [#ensure-your-login-policy-allows-external-id-ps]
Test the setup [#test-the-setup]
Add Action to map user attributes [#add-action-to-map-user-attributes]
```js reference
https://github.com/zitadel/actions/blob/main/examples/okta_saml_prefil_register_form.js
```
# Set up OneLogin as a SAML Identity Provider in ZITADEL
OneLogin SAML settings [#one-login-saml-settings]
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 settings using the ZITADEL URLs.
Create a new SAML application for ZITADEL [#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.
Export OneLogin IdP metadata [#export-one-login-id-p-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 setup steps.
ZITADEL settings [#zitadel-settings]
Go to the IdP providers overview [#go-to-the-id-p-providers-overview]
Create a new SAML Service Provider [#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 settings are contained in the metadata which has to be exchanged between the Service Provider and the Identity Provider.
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 [#complete-one-login-configuration-with-zitadel-ur-ls]
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 **Settings** tab (or the section where SAML SP settings such as ACS and Audience are set up).
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.
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 [#configure-saml-attributes-in-one-login]
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-id-p]
Ensure your Login Policy allows External IdPs [#ensure-your-login-policy-allows-external-id-ps]
Configure an action to autofill user data [#configure-an-action-to-autofill-user-data]
Required for Login V2 [#required-for-login-v-2]
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 [#test-the-setup]
# OpenLDAP Identity Provider Setup in ZITADEL
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).
Beware that these example settings 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.
How it works [#how-it-works]
OpenLDAP Configuration [#open-ldap-configuration]
Basic settings [#basic-settings]
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 are the default settings 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 [#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: test@example.com
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 Setup [#zitadel-setup]
Go to the IdP Providers Overview [#go-to-the-id-p-providers-overview]
Create a new LDAP Provider [#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.
Activate IdP [#activate-id-p]
Ensure your Login Policy allows External IDPs [#ensure-your-login-policy-allows-external-id-ps]
Test the setup [#test-the-setup]
# PingFederate SAML Identity Provider in ZITADEL
PingFederate SAML settings [#ping-federate-saml-settings]
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 settings using the ZITADEL URLs.
Create a new SP Connection for ZITADEL [#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 [#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 [#export-ping-federate-id-p-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 setup steps.
ZITADEL settings [#zitadel-settings]
Go to the IdP providers overview [#go-to-the-id-p-providers-overview]
Create a new SAML Service Provider [#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 settings are contained in the metadata which has to be exchanged by the ServiceProvider and the IdentityProvider.
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) [#import-zitadel-metadata-into-ping-federate-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 [#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 [#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 [#if-you-need-to-update-the-sp-connection-with-zitadel-ur-ls]
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-id-p]
Ensure your Login Policy allows External IdPs [#ensure-your-login-policy-allows-external-id-ps]
Configure an action to autofill user data [#configure-an-action-to-autofill-user-data]
Required for Login V2 [#required-for-login-v-2]
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 [#test-the-setup]
# Hosted Login UI: Authenticate Your Users
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 [#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 [#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 [#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 [#key-features-of-the-hosted-login]
Flexible usernames [#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 [Administrators](/concepts/structure/administrators) to meet their requirements.
Support for multiple authentication methods [#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 [#enterprise-single-sign-on]
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 [#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](/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. [Administrators](/concepts/structure/administrators) for an organization have only access to data and settings within their Organization.
2. **Custom Authentication Settings**: Allows tailored [authentication settings](/guides/manage/console/default-settings#login-behavior-and-security), [branding](/guides/manage/customize/branding), and policies for each tenant.
3. **Centralized Management**: Provides [centralized administration](/guides/manage/console/administrators) 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 [#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.
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 an Organization Domain scope (`urn:zitadel:iam:org:domain:primary:{domainname}`) with the authorization request, or define the behavior on your Project's settings.
Currently, interface texts for the new Hosted Login V2 cannot be customized directly through the Management Console.
To update translations in Login V2, you must use the [Settings V2 API](/apis/resources/settings_service_v2/settings-service-set-hosted-login-translation). You can reference the default keys in the [`en.json` locale file](https://github.com/zitadel/zitadel/blob/main/apps/login/locales/en.json) from the ZITADEL repository. You can send a payload that includes only the keys you want to override; Login V2 will automatically merge your keys with the defaults and handle any fallbacks.
Fast account switching [#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.
This behavior can be changed with the authorization request. Please refer to our [guide](/guides/integrate/login/oidc/login-users).
Self-service for users [#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 [#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 [#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-security) 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.
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.
Enroll passkeys [#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 [#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 [#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 [#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.
*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 [#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](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.
Zitadel Cloud provides the V2 login at the fixed path `/ui/v2/login`.
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 settings. 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.
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](https://your-zitadel-instance.zitadel.cloud/ui/v2/login))
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 account](/guides/integrate/service-accounts/personal-access-token#create-a-service-account-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 Administrator Guide](/guides/manage/console/administrators)
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](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.
You have three different options on how to achieve this:
1. Enable the new login on your application settings 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.
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](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.
# Login users with ZITADEL
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
# User Authentication Options with ZITADEL
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.
Using industry-standard protocols [#using-industry-standard-protocols]
Authenticate users with OpenID Connect 1.0 [#authenticate-users-with-open-id-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 [#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 settings, 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 [#zitade-ls-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 [#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.
Token exchange between Session API and OIDC / SAML tokens is not possible at this moment.
Key features of the Session API [#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 [#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 [#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.
At the moment developers can only integrate with ZITADEL's Session API and not with standard compliant OpenID Connect or SAML flows.
When to build your custom Login UI [#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 [#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
# Authenticate users with 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? [#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 [#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 settings between the identity provider and the service provider
* **Metadata**: Trust information exchanged between the identity provider and service provider
SAML explained [#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 [#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-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
https://zitadel-test.sp/metadata
WSz/EQ72RZ0DTh3DBSRCElpITqM=HHjsNh0OLj7...MIIDtzCCAp+gAwIBAgIUfITRQGue...
```
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](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](http://127.0.0.1:5000/acs)) indicates where the response should be sent after user authentication.
* **Issuer** ([https://zitadel-test.sp/metadata](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
https://my-instance-xtzfbc.zitadel.cloud/saml/v2/metadata
https://my-instance-xtzfbc.zitadel.cloud/saml/v2/metadata/b1R9LJJSeNX...n5GbV4xhkXV...MIIFITCCAwmgAwIBAgIBUTANBgkqh...dakshitha.devrel@gmail.comhttps://zitadel-test.sp/metadataurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransporttony.stark@gmail.comStarkTonyTony Starktony.stark@gmail.com260242264868201995
```
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](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](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 [#saml-identity-brokering]
How SAML identity brokering works [#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 [#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 [#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 [#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](/guides/integrate/identity-providers/azure-ad-saml)
* [Configure Okta as a SAML IdP](/guides/integrate/identity-providers/okta_saml)
* [Configure MockSAML as a SAML IdP](/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).
# Device Authorization Grant in Custom Login UI
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.
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 [#example]
Let's assume you host your login UI on the following URL:
```
https://login.example.com
```
Device Authorization Request [#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.
The version and the optional custom URI for the available login UI is configurable under the application settings.
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 [#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 [#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 [#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 [#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 [#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).
# Handle External Logins in a Custom Login UI
Flow [#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)
Start the Provider Flow [#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 [#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 [#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 [#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.
The redirect URL differs between login versions. V2 uses `https://${CUSTOM_DOMAIN}/idps/callback`, while V1 uses `https://${CUSTOM_DOMAIN}/ui/login/login/externalidp/callback`.
For Form Post based callbacks, V1 requires a separate `/form`-suffixed URL (`https://${CUSTOM_DOMAIN}/ui/login/login/externalidp/callback/form`), whereas V2's standard callback URL handles Form Post callbacks natively, so no separate URL is needed.
Get Provider Information [#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 [#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 [#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": "minni@mouse.com",
"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 [#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 [#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 [#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 [#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 [#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": "minni-mouse@mouse.com",
"profile": {
"givenName": "Minnie",
"familyName": "Mouse",
"nickName": "Mini",
"displayName": "Minnie Mouse",
"preferredLanguage": "en",
"gender": "GENDER_FEMALE"
},
"email": {
"email": "minni-mouse@mouse.com",
"isVerified": true
},
"idpLinks": [
{
"idpId": "218528353504723201",
"userId": "111392805975715856637",
"userName": "Minnie Mouse"
}
]
}'
```
Add External Login to Existing User [#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 [#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"
}
}'
```
# Build your own Login UI
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
# 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 [#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 [#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 [#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 service account'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.
*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 [#implemented-features]
OIDC Standard [#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 [#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 [#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 [#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 [#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 [#deploy-to-vercel]
To deploy your own version on Vercel, navigate to your instance and create a service account.
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.
By default, verification codes are not automatically submitted on page load. This protects against enterprise email link scanners that pre-fetch URLs and could inadvertently consume one-time codes before users click them.
If you want to enable automatic code submission (e.g. for a smoother UX when link scanners are not a concern), set `NEXT_PUBLIC_AUTO_SUBMIT_CODE` to `true`.
# Logging Out via a Custom Login UI
# Multi-Factor Authentication (MFA) in a Custom Login UI
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
* OTP sent as SMS
* OTP sent as Email
* Universal Second Factor (U2F), which is authentication with your device like Windows Hello, Apple FaceID, Fingerprint, FIDO2 keys, Yubikey, etc.
TOTP Registration [#totp-registration]
Flow [#flow]
List the Possible Methods [#list-the-possible-methods]
Start TOTP Registration [#start-totp-registration]
The user has selected to setup 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 setup.
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:minni-mouse@mouse.com?algorithm=SHA1&digits=6&issuer=ZITADEL&period=30&secret=TJOPWSDYILLHXFV4MLKNNJOWFG7VSDCK",
"secret": "TJOPWSDYILLHXFV4MLKNNJOWFG7VSDCK"
}
```
Verify TOTP Registration [#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 [#totp-authentication]
Flow [#flow]
Check User [#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": "minnie-mouse@mouse.com"
}
}
}'
```
Example Response
```bash
{
"details": {
"sequence": "580",
"changeDate": "2023-06-14T05:32:39.007096Z",
"resourceOwner": "163840776835432705"
},
"sessionId": "218480890961985793",
"sessionToken": "yMDi6uVPJAcphbbz0LaxC07ihWkNTe7m0Xqch8SzfM5Cz3HSIQIDZ65x1f5Qal0jxz0MEyo-_zYcUg"
}
```
Check TOTP [#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 [#sms-code-registration]
Flow [#flow]
List the Possible Methods [#list-the-possible-methods]
Add Phone Number [#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 [#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 [#add-otp-sms-to-the-user]
Now that the user has a verified phone number you can enable OTP SMS 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 [#sms-code-authentication]
Flow [#flow]
Check User [#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": "minni-mouse@mouse.com"
}
},
"challenges": {
"otpSms": {
"returnCode": false
}
}
}'
```
Check SMS Code [#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 [#email-code-registration]
Flow [#flow]
List the Possible Methods [#list-the-possible-methods]
Verified Email [#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 [#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 [#email-code-authentication]
Flow [#flow]
Check User [#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": "minni-mouse@mouse.com"
}
},
"challenges": {
"otpEmail": {
"returnCode": false
}
}
}'
```
Check Email Code [#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 [#u-2-f-registration]
Flow [#flow]
List the Possible Methods [#list-the-possible-methods]
Start U2F Registration [#start-u-2-f-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 [#register-new-u-2-f-on-current-device]
Verify U2F Registration [#verify-u-2-f-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 [#u-2-f-authentication]
Flow [#flow]
Check User [#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": "minni-mouse@mouse.com"
}
},
"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 [#signin-in-browser]
Update Session with WebAuthN [#update-session-with-web-auth-n]
# OpenID Connect (OIDC) in Custom Login UI
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.
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 [#example]
Let's assume you host your login UI on the following URL:
```
https://login.example.com
```
Authorize Request [#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.
The version and the optional custom URI for the available login UI is configurable under the application settings.
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 [#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": "mini@mouse.com"
}
}
```
Perform Login [#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 [#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 [#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 [#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.
# Using Passkeys in a Custom Login UI
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 [#register-passkey]
Flow [#flow]
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 [#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 [#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": "minni-mouse@mouse.com"
}
}
}
}
```
Register new Passkey on current device [#register-new-passkey-on-current-device]
Verify Passkey in ZITADEL [#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 [#login-with-passkey]
Flow [#flow]
Create Session [#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": "minni-mouse@mouse.com"
}
},
"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 [#signing-in-browser]
Update Session with WebAuthN [#update-session-with-web-auth-n]
# Password Reset/Change in a Custom Login UI
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 [#flow]
Request Password Reset [#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 [#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 [#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 [#zitadel-returns-the-code]
Send the request with asking for the return Code in the body of the request.
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 [#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 [#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 [#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)
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.
Request [#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"
}'
```
# Support for the SAML Standard in a Custom Login UI
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.
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 [#example]
Let's assume you host your login UI on the following URL:
```
https://login.example.com
```
SAML Request [#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.
The version and the optional custom URI for the available login UI is configurable under the application settings.
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 [#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 [#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 [#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 [#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 application 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 [#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 application to redirect to the url '[https://myapp.example.com/acs?RelayState=...\&SAMLResponse=](https://myapp.example.com/acs?RelayState=...\&SAMLResponse=)...' with all information as URL parameters.
SAML Endpoints [#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
# Select Account in a Custom Login UI
# How to Handle 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 [#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": "minnie-mouse@fabi.zitadel.app",
"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 [#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": "minnie-mouse@fabi.zitadel.app",
"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.
Note that ZITADEL will reject any expired sessions automatically and that they cannot be updated anymore.
Set a lifetime [#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"
}
```
Note that if the `lifetime` was not set, the session will never expire.
Token Introspection [#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).
# Username & Password Login in Custom Login UI
Flow [#flow]
Register [#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 [#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 [#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": "mini@mouse.com",
"isVerified": true
},
"metadata": [
{
"key": "my-key",
"value": "VGhpcyBpcyBteSB0ZXN0IHZhbHVl"
}
],
"password": {
"password": "Secr3tP4ssw0rd!",
"changeRequired": false
}
}'
```
Response [#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": "mini@mouse.com",
"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 [#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 [#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": "minnie-mouse@fabi.zitadel.app"
}
}
}'
```
Response [#response]
```bash
{
"details": {
"sequence": "580",
"changeDate": "2023-06-14T05:32:39.007096Z",
"resourceOwner": "163840776835432705"
},
"sessionId": "218480890961985793",
"sessionToken": "yMDi6uVPJAcphbbz0LaxC07ihWkNTe7m0Xqch8SzfM5Cz3HSIQIDZ65x1f5Qal0jxz0MEyo-_zYcUg"
}
```
Session State [#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": "minnie-mouse@fabi.zitadel.app",
"displayName": "Minnie Mouse"
}
}
}
}
```
Checkout how to handle [session validation](./session-validation).
Update Session with Password [#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 [#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 [#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 [#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": "minnie-mouse@fabi.zitadel.app",
"displayName": "Minnie Mouse"
},
"password": {
"verifiedAt": "2023-06-14T05:42:11.619484Z"
}
}
}
}
```
List the Sessions (Account Chooser) [#list-the-sessions-account-chooser]
Logout User [#logout-user]
# Onboard B2B customers using organizations
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 [Administrators](/guides/manage/console/administrators) to delegate self-service team management and setting up 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 [#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/administrators) with permissions on an instance level such as "INSTANCE\_OWNER"
Create a customer [#create-a-customer]
1. Create a new Organization. Click the dropdown in the header and click "+ New Organization" to 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.
Click on the newly created organization in the list and you will switch your context to that organization.
Add First Administrator [#add-first-administrator]
Create the first user for your customer and ensure the user has enough permissions to self-manage the needed settings.
Create User [#create-user]
Add Administrator Role to User [#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 Administrators Guide with the Role List](/guides/manage/console/administrators)
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 administrators.
Invite Team Members [#invite-team-members]
The first user of your customer is now ready to authenticate and manage resources in ZITADEL.
The first task will be inviting a team member.
We are now switching to the view of your customer's administrator user.
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.
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 set up 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) [#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 already see a list of activated providers here. If so, this is because some default providers are configured on the ZITADEL instances.
3. Set up the identity provider you need.
Follow our detailed setup description of the different providers we have: [Set up the needed identity provider](/guides/integrate/identity-providers/introduction)
Auto-register users with SSO [#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 settings 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"
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)
Login with your user [#login-with-your-user]
The last step is that your user can log in to ZITADEL and use the SSO account.
Your user is now ready to log in with SSO.
Automated onboarding for your customers [#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 [#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 administrators, roles and their meanings [here](/guides/manage/console/administrators)
Disable built-in 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 [#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 set up 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 necessary information like the profile, email, password. etc.
This request allows you only to set up a user with password authentication at the moment.
By specifying the roles you can define which permission the user should have within ZITADEL.
By default, the user will automatically get "ORG\_OWNER".
Example Request [#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 ' \
--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": "gigi@zitadel.com",
"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)
# Onboard Users
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 [#manually-add-invite-users]
Automated / Self-registration possibilities [#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 [#built-in-register-form]
Prompt registration 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 [#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.
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.
The options are dependent on the settings of the organization.
If only one option is possible, the option will directly be selected and shown.
Local User Registration [#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.
If nothing else is specified, a user will be registered to the 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.
Registration with Social Login [#registration-with-social-login]
To allow your users to register with social logins you have to set up 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 set up 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 setup 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.
Registration with Organization External Identity Provider [#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 set up their providers directly for their organization.
Set up the needed provider such as Entra ID or OKTA.
Please follow the setup guides for the needed providers: [Let Users Login with Preferred Identity Provider in ZITADEL](/guides/integrate/identity-providers/introduction)
Build your own registration form [#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 [#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 [#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)
# Onboard Customers and Users
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
# Authenticate service accounts and client applications
This guide explains ZITADEL service accounts and their role in facilitating secure machine-to-machine communication within your applications.
What are Service Accounts? [#what-are-service-accounts]
Service accounts 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 accounts in combination with [Administrator](/concepts/structure/administrators) permissions are used to access ZITADEL's APIs, for example, to manage user resources.
Unlike users, service accounts don't rely on traditional login methods (e.g., username/password) and require alternative authentication mechanisms.
Benefits of using Service Accounts [#benefits-of-using-service-accounts]
Enhanced security [#enhanced-security]
* **Principle of Least Privilege:** Grant service accounts 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 account credentials can be rotated independently.
Segregated authorization [#segregated-authorization]
Manage authorization for service accounts separately from human users, providing an extra layer of control.
API and backend access [#api-and-backend-access]
Service accounts offer a secure way to authenticate and access various API endpoints and protected backend services.
You can [use service accounts 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 [#improved-auditability]
Actions performed by service accounts 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 [#authentication-methods]
ZITADEL supports two primary authentication methods for service accounts:
Private key JWT authentication [#private-key-jwt-authentication]
How private key JWT authentication works [#how-private-key-jwt-authentication-works]
* Generate a private/public key pair associated with the service account.
* Sign JWTs with the private key.
* ZITADEL validates the signature using the service account'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 accounts and applications.
Benefits of private key JWT authentication [#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 [#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 [#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 [#client-credentials-grant]
* Presents a client ID and client secret associated with the service account.
* Simpler than the JWT profile in specific scenarios.
Follow our guide on using [client credentials grant](./client-credentials) to get started authenticating service accounts and applications.
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 account until rotation occurs.
Personal Access Tokens (PATs) [#personal-access-tokens-pa-ts]
* **Ready-to-use tokens:** Generated for specific service accounts and can be directly included in the authorization header of API requests.
* **Currently available only for service accounts** and not users.
Follow our guide on using [personal access tokens](./personal-access-token) to get started authenticating service accounts and applications.
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 Accounts [#using-service-accounts]
1. **Creation:** Access the ZITADEL management console and create a new service account. Assign a descriptive name that reflects its purpose. Follow our detailed guide on [how to create service accounts](../../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 account 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 [#important-considerations]
* **Secure Credentials:** Treat service account 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 account's function.
Choosing the right authentication method [#choosing-the-right-authentication-method]
For most service account 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 [#further-resources]
* Read about the [different methods to authenticate service accounts](./authenticate-service-accounts)
* [Service Account 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 using private key JWT](./private-key-jwt)
# OAuth Client Credentials for Service Accounts
This guide demonstrates how developers can leverage Client Credential authentication to secure communication between [service accounts](/guides/manage/console/users-overview#service-accounts) and client applications within ZITADEL.
In ZITADEL, the Client Credentials Flow can be used for this [non-interactive authentication](./authenticate-service-accounts) as alternative to the [JWT profile authentication](./authenticate-service-accounts).
Steps to authenticate a Service Account with client credentials [#steps-to-authenticate-a-service-account-with-client-credentials]
1\. Create a Service Account with a client secret [#1-create-a-service-account-with-a-client-secret]
1. Navigate to Service Accounts
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
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.
2\. Authenticating a service account and request a token [#2-authenticating-a-service-account-and-request-a-token]
In this step, we will authenticate a service account 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 account 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 account will get an opaque access token.
If you want to get a JSON 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
3\. Include the access token in the authorization header [#3-include-the-access-token-in-the-authorization-header]
When making API requests on behalf of the service account, 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 [#accessing-zitadel-ap-is]
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 account using client credentials.
Token introspection [#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 [#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 account and client application communication within ZITADEL using client credential authentication.
Notes [#notes]
* Read about the [different methods to authenticate service accounts](./authenticate-service-accounts)
* [Service Account 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)
# Personal Access Token Auth for Service Accounts
A Personal Access Token (PAT) is a ready-to-use token that can be used as an *Authorization* header.
At the moment ZITADEL only allows PATs for service accounts.
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 accounts](./authenticate-service-accounts).
Create a Service Account with a PAT [#create-a-service-account-with-a-pat]
1. Navigate to Service Accounts
2. Click on **New**
3. Enter a username 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)
Grant role for ZITADEL [#grant-role-for-zitadel]
To be able to access the ZITADEL APIs, your service account 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 account
4. Give the user the role you need — for example, we choose Org Owner (More about [ZITADEL Permissions](/guides/manage/console/administrators))
Accessing ZITADEL APIs [#accessing-zitadel-ap-is]
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 account.
Token introspection [#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 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 [#call-zitadel-api-with-pat]
Because the PAT is a ready-to-use token, you can add it as an Authorization Header and send it in your requests to the ZITADEL API.
In this example we read the organization of the service account.
```bash
curl --request GET \
--url ${CUSTOM_DOMAIN}/management/v1/orgs/me \
--header 'Authorization: Bearer {PAT}'
```
Client application authentication [#client-application-authentication]
The above steps demonstrate service account 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 [#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 account and client application communication within ZITADEL using private key JWT authentication.
# Private Key JWT Auth for Service Accounts
This guide explains how developers can use private key JWT authentication to secure communication between service accounts 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 accounts](./authenticate-service-accounts), including their benefits, drawbacks, and security considerations.
How private key JWT authentication works [#how-private-key-jwt-authentication-works]
1. Generate a private/public key pair associated with the service account.
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 account 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 account'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/).
Prerequisites [#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 Account with private JWT [#steps-to-authenticate-a-service-account-with-private-jwt]
Follow these steps to authenticate a service account and obtain an access token for subsequent requests.
1\. Create a Service Account [#1-create-a-service-account]
1. Navigate to **Service Accounts**.
2. Click on **New**.
3. Enter a username and a display name.
4. Click on **Create**.
2\. Register a private key for your service account [#2-register-a-private-key-for-your-service-account]
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 [#option-a-generate-and-download-a-private-key-from-zitadel]
1. Open the ZITADEL web console and go to your service account.
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 account [#option-b-register-an-existing-public-key-for-your-service-account]
If you already have a key pair, you can upload the public key for your service account. 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)
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.
If you set an expiration date when generating a key in ZITADEL, that key will expire at midnight on the specified day.
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 [#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 account 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 [#4-request-an-o-auth-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 account 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 [#5-include-the-access-token-in-the-authorization-header]
When making API requests as the service account, 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 [#accessing-zitadel-ap-is]
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 account.
Token introspection [#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 [#client-application-authentication]
The steps above describe service account 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 [#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 account and client application authentication within ZITADEL using private key JWTs.
Notes [#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)
# Log in with ZITADEL on Atlassian through SAML 2.0
This guide shows how to enable login with ZITADEL on Atlassian.
It covers how to:
* create and configure the application in your project
* create and configure the connection in Atlassian Access
Prerequisites:
* existing ZITADEL Instance, if not present follow [this guide](/guides/start/quickstart)
* existing ZITADEL Organization, if not present follow [this guide](/guides/manage/console/organizations-overview)
* existing ZITADEL project, if not present follow the first 3 steps [here](/guides/manage/console/projects-overview)
* existing Atlassian Access setup, including verified domain
> We have to switch between ZITADEL and Atlassian. If the headings begin with "ZITADEL" switch to the ZITADEL
> Management Console and
> if the headings start with "Atlassian" please switch to the Atlassian Admin GUI.
**Atlassian**: Create a new external identity provider [#atlassian-create-a-new-external-identity-provider]
Please follow the instructions on [Atlassian's support page](https://support.atlassian.com/security-and-access-policies/docs/configure-saml-single-sign-on-with-an-identity-provider/) to configure a SAML identity provider for SSO.
The following instructions give you a quick overview of the most important steps.
Login to Atlassian's security center and select Identity providers.
Select the option to Set up SAML single sign-on.
For Identity Provider select "Other provider" and enter a Directory Name.
Follow the wizard.
Fill in the following information:
* `Identity provider Entity ID`: \{your\_instance\_domain}/saml/v2/metadata
* `Identity provider SSO URL`: \{your\_instance\_domain}/saml/v2/SSO
* `Public x509 certificate`: You need to download and paste the value of the certificate from \{your\_instance\_domain}/saml/v2/certificate
Create a new .xml file with the following minimal SAML metadata contents:
```xml
```
Set or replace the variables with the values from the next screen as follows:
* `${ENTITYID}`: Copy the value from "Service provider entity URL"
* `${ACSURL}`: Copy the value from "Service provider assertion consumer service URL"
**ZITADEL**: Create the application [#zitadel-create-the-application]
In your existing project:
Press the "+"-button to add an application
Fill in a name for the application and chose the SAML type, then click "Continue".
Either fill in the URL where ZITADEL can read the metadata from, or upload the metadata XML directly, then click "Continue".
Check your application, if everything is correct, press "Create".
**Atlassian**: Setup authentication policies [#atlassian-setup-authentication-policies]
Under Authentication policies, select "Edit" on the directory that you have created.
Then check the box "Enforce single sign-on" and confirm by clicking "Update".
Add members to your policy.
Verify settings [#verify-settings]
Now you should be all set to verify your setup:
* Create a user in ZITADEL with the same email address as a member in your authentication policy.
* In a new browser session go to [https://id.atlassian.com](https://id.atlassian.com)
* Enter the user's email address
* You should be redirected to ZITADEL's Login screen
* Enter the email address and password
* Continue and you should be redirected back to Atlassian
# Log in with ZITADEL on Auth0 through OIDC
This guide shows how to enable login with ZITADEL on Auth0.
It covers how to:
* create and configure the application in your project
* create and configure the connection in your Auth0 tenant
Prerequisites:
* existing ZITADEL Instance, if not present follow [this guide](/guides/start/quickstart)
* existing ZITADEL Organization, if not present follow [this guide](/guides/manage/console/organizations-overview)
* existing ZITADEL project, if not present follow the first 3 steps [here](/guides/manage/console/projects-overview)
* existing Auth0 tenant as described [here](https://auth0.com/docs/get-started/auth0-overview/create-tenants)
> We have to switch between ZITADEL and Auth0. If the headings begin with "ZITADEL" switch to the ZITADEL Management Console and if the headings start with "Auth0" please switch to the Auth0 GUI.
**Auth0**: Create a new connection [#auth-0-create-a-new-connection]
In Authentication > Enterprise
1. Press the "+" button right to "OpenID Connect"
2. Set a connection name for example "ZITADEL"
3. The issuer url is `https://${CUSTOM_DOMAIN}/.well-known/openid-configuration`
4. Copy the callback URL (ending with `/login/callback`)
The settings should look like this:
Next we have to switch to the ZITADEL Management Console.
**ZITADEL**: Create the application [#zitadel-create-the-application]
First of all we create the application in your project.
**Auth0**: Connect ZITADEL [#auth-0-connect-zitadel]
1. Copy the client id from ZITADEL and paste it into the **Client ID** field
2. Copy the client secret from ZITADEL and paste it into the **Client Secret** field
3. click Create
4. To verify the connection go to the "Applications" tab and enable the Default App
5. Click "Back to OpenID Connect"
6. Click on the "..." button right to the newly created connection and click "Try"
7. ZITADEL should open on a new tab, and you can enter your login information
8. After you logged in you should see the following:
# Log in with ZITADEL on Auth0 through SAML 2.0
This guide shows how to enable login with ZITADEL on Auth0.
It covers how to:
* create and configure the application in your project
* create and configure the connection in your Auth0 tenant
Prerequisites:
* existing ZITADEL Instance, if not present follow [this guide](/guides/start/quickstart)
* existing ZITADEL Organization, if not present follow [this guide](/guides/manage/console/organizations-overview)
* existing ZITADEL project, if not present follow the first 3 steps [here](/guides/manage/console/projects-overview)
* existing Auth0 tenant as described [here](https://auth0.com/docs/get-started/auth0-overview/create-tenants)
> We have to switch between ZITADEL and Auth0. If the headings begin with "ZITADEL" switch to the ZITADEL Management Console and
> if the headings start with "Auth0" please switch to the Auth0 GUI.
**Auth0**: Create a new connection [#auth-0-create-a-new-connection]
In Authentication -> Enterprise
1. Press the "+" button right to "SAML"
2. Fill out the fields as follows in the SAML Connection:
This includes:
* a unique "Connection name"
* the "Sign In URL"
* the "Sign Out URL"
* used "User ID Attribute"
* the definition how the request should be signed
* which binding should be used to call ZITADEL
All the information is filled out as an example, and to connect with any other environment you only have to change the
used domain, for example "example.com" with "zitadel.cloud".
Lastly, upload the certificate used to sign the responses, provided for you under the
URL \{your\_instance\_domain}/saml/v2/certificate.
Then just press the button "Create" and the connection on Auth0 is configured.
**ZITADEL**: Create the application [#zitadel-create-the-application]
You need to upload the SAML metadata to ZITADEL for it to recognize this newly created connection.
[Under this link](https://auth0.com/docs/authenticate/protocols/saml/saml-identity-provider-configuration-settings) are
all necessary information to correctly fill out the metadata or download the metadata-file directly under the
URL https\://YOUR\_AUTH0\_DOMAIN/samlp/metadata?connection=YOUR\_CONNECTION\_NAME, which in this example would
be [https://example.auth0.com/samlp/metadata?connection=SAML-ZITADEL](https://example.auth0.com/samlp/metadata?connection=SAML-ZITADEL).
In your existing project:
1. Press the "+"-button to add an application
2. Fill in a name for the application and chose the SAML type, then click "Continue".
3. Either fill in the URL where ZITADEL can read the metadata from, or upload the metadata XML directly, then click "
Continue".
4. Check your application, if everything is correct, press "Create".
Everything on the side of ZITADEL is done if the application is correctly created.
**Auth0**: Try the connection [#auth-0-try-the-connection]
To then test the connection you only have to press "Try" on the created connection in the Authentication → Enterprise
screen.
To further customize the requests you can also customize the SAML communication as
described [here](https://auth0.com/docs/authenticate/protocols/saml/saml-configuration/customize-saml-assertions)
# Log in with ZITADEL on AWS through SAML 2.0
This guide shows how to enable login with ZITADEL on AWS SSO.
It covers how to:
* create and configure the application in your project
* create and configure the connection in your AWS SSO external IDP
Prerequisites:
* existing ZITADEL Instance, if not present follow [this guide](/guides/start/quickstart)
* existing ZITADEL Organization, if not present follow [this guide](/guides/manage/console/organizations-overview)
* existing ZITADEL project, if not present follow the first 3 steps [here](/guides/manage/console/projects-overview)
* prerequisites on AWS side [here](https://docs.aws.amazon.com/singlesignon/latest/userguide/prereqs.html).
* enabled AWS SSO [here](https://docs.aws.amazon.com/singlesignon/latest/userguide/step1.html?icmpid=docs_sso_console)
> We have to switch between ZITADEL and AWS. If the headings begin with "ZITADEL" switch to the ZITADEL Management Console and if
> the headings start with "AWS" please switch to the AWS GUI.
**AWS**: Change to external identity provider ZITADEL [#aws-change-to-external-identity-provider-zitadel]
As you have activated SSO you still have the possibility to use AWS itself to manage the users, but you can also use a
Microsoft AD or an external IDP.
Described [here](https://docs.aws.amazon.com/singlesignon/latest/userguide/manage-your-identity-source-idp.html) how you
can connect to ZITADEL as a SAML2 IDP.
1. Chose the External identity provider:
2. Download the metadata file, to provide ZITADEL with all the information it needs, and save the AWS SSO Sign-in URL,
which you use to log in afterward.
3. Fill out the fields as follows, to provide AWS with all the information it needs:
To connect to another environment, change the domains, for example if you would use ZITADEL under the url "
[https://example.com](https://example.com)" you would have the URLs "[https://example.com/saml/v2/SSO](https://example.com/saml/v2/SSO)"
and "[https://example.com/saml/v2/metadata](https://example.com/saml/v2/metadata)".
4. Download the ZITADEL-used certificate to sign the responses, so that AWS can validation the signature.
You can download the certificate from following
URL: `${CUSTOM_DOMAIN}/saml/v2/certificate`
5. Then upload the ".crt"-file to AWS and click "next".
6. Lastly, accept to confirm the change and ZITADEL is used as the external identity provider for AWS SSO to provide
connectivity to your AWS Accounts.
As for how the SSO users are then connected to the AWS accounts, you can find more information in the AWS documentation,
for example [here](https://docs.aws.amazon.com/singlesignon/latest/userguide/useraccess.html).
**ZITADEL**: Create the application [#zitadel-create-the-application]
The metadata used in this part is from "Change to external identity provider ZITADEL" step 2.
In your existing project:
1. Press the "+"-button to add an application
2. Fill in a name for the application and chose the SAML type, then click "Continue".
3. Either fill in the URL where ZITADEL can read the metadata from, or upload the metadata XML directly, then click "
Continue".
4. Check your application, if everything is correct, press "Create".
Everything on the side of ZITADEL is done if the application is correctly created.
**AWS**: Test the connection [#aws-test-the-connection]
The result, you can now log in to you AWS account through your ZITADEL-login with the AWS SSO Sign-in URL, which you
should have saved in "Change to external identity provider ZITADEL" step 2.
# Cloudflare Zero Trust OIDC with ZITADEL
This guide shows how to configure ZITADEL as OpenID Connect identity provider for Cloudflare Zero Trust.
Prerequisites:
* Existing ZITADEL instance, organization, and project. Follow our [get started](/guides/start/quickstart) guide to get started. If not present follow [this guide](/guides/start/quickstart)
* Existing Cloudflare account and [team domain](https://developers.cloudflare.com/cloudflare-one/glossary/#team-domain)
Create the application in ZITADEL [#create-the-application-in-zitadel]
Send user info in tokens [#send-user-info-in-tokens]
Make sure to enable "User Info inside ID Token" on your application settings.
Cloudflare will return an error "User email was not returned. API permissions are likely incorrect". Enable to send the user information inside the token on your application settings.
Configure Cloudflare Zero Trust Authentication [#configure-cloudflare-zero-trust-authentication]
1. On the Cloudflare dashboard go to Zero Trust, click settings, and then select "Authentication"
2. Add a new login method with the type "OpenID Connect"
3. Fill in the required information. Check the discovery endpoint of your instance `https://${CUSTOM_DOMAIN}/.well-known/openid-configuration` for the urls. As mentioned in the Cloudflare docs the Certificate Url is jwks\_uri.
4. Disable PKCE (Cloudflare requires a client secret for PKCE, which is currently not supported)
5. Add the following claims: "openid", "profile", "email"
6. Test the connection
Example settings [#example-settings]
```json
{
"config": {
"client_id": "",
"client_secret": "",
"auth_url": "https://${CUSTOM_DOMAIN}.zitadel.cloud/oauth/v2/authorize",
"token_url": "https://${CUSTOM_DOMAIN}.zitadel.cloud/oauth/v2/token",
"certs_url": "https://${CUSTOM_DOMAIN}.zitadel.cloud/oauth/v2/keys",
"scopes": ["openid", "email", "profile"],
"pkce_enabled": false,
},
"type": "oidc",
"name": "Generic Google"
}
```
# Log in with ZITADEL on Gitlab through SAML 2.0
This guide shows how to enable login with ZITADEL on Gitlab.
It covers how to:
* create and configure the application in your project
* create and configure the connection in Gitlab SaaS
Prerequisites:
* existing ZITADEL Instance, if not present follow [this guide](/guides/start/quickstart)
* existing ZITADEL Organization, if not present follow [this guide](/guides/manage/console/organizations-overview)
* existing ZITADEL project, if not present follow the first 3 steps [here](/guides/manage/console/projects-overview)
* existing Gitlab SaaS Setup in the premium tier
> We have to switch between ZITADEL and Gitlab. If the headings begin with "ZITADEL" switch to the ZITADEL
> Management Console and
> if the headings start with "Gitlab" please switch to the Gitlab GUI.
**Gitlab**: Create a new external identity provider [#gitlab-create-a-new-external-identity-provider]
Please follow the instructions on [Gitlab docs](https://docs.gitlab.com/ee/user/group/saml_sso/index.html) to configure a SAML identity provider for SSO.
The following instructions give you a quick overview of the most important steps.
[Open the group](https://gitlab.com/dashboard/groups) to which you want to add the SSO settings.
Select on the menu Settings and then SAML SSO.
Copy `GitLab metadata URL` for the next step.
**ZITADEL**: Create the application [#zitadel-create-the-application]
In your existing project:
Press the "+"-button to add an application
Fill in a name for the application and chose the SAML type, then click "Continue".
Enter the URL from before, then click "Continue".
Check your application, if everything is correct, press "Create".
**Gitlab**: Setup [#gitlab-setup]
Complete the setup as follows:
* `Identity provider single sign-on URL`: `${CUSTOM_DOMAIN}/saml/v2/SSO`
* `Certificate fingerprint`: You need to download the certificate from \{your\_instance\_domain}/saml/v2/certificate and create a SHA1 fingerprint
Save the changes.
**Gitlab**: Verify SAML setup [#gitlab-verify-saml-setup]
Once you saved the changes, click on the button "Verify SAML settings".
You should be redirected to ZITADEL.
Login with your user.
After that you should be redirected back to GitLab and you can inspect the Response Output.
# Log in with ZITADEL on Gitlab OmniAuth Provider
This guide shows how to enable login with ZITADEL on self-hosted Gitlab instances.
It covers how to:
* create and configure the application in your ZITADEL project
* create and configure the connection in a self-hosted Gitlab instance
Prerequisites:
* existing ZITADEL Instance, if not present follow [this guide](/guides/start/quickstart)
* existing ZITADEL Organization, if not present follow [this guide](/guides/manage/console/organizations-overview)
* existing ZITADEL project, if not present follow the first 3 steps [here](/guides/manage/console/projects-overview)
* running Gitlab instance see [installation guide](https://docs.gitlab.com/ee/install/)
Gitlab settings [#gitlab-settings]
Follow [this guide](https://docs.gitlab.com/ee/administration/auth/oidc.html) of gitlab to configure the omniauth provider. Here are some example settings with redacted secrets.
Replace the values of the following fields:
* `args.client_options.identifier` with the `ClientId` generated by ZITADEL
* `args.client_options.secret` with the `ClientSecret` generated by ZITADEL
* `args.client_options.redirect_uri` with the proper URL to your gitlab instance and callback
```ruby
gitlab_rails['omniauth_providers'] = [
{
name: "openid_connect",
label: "ZITADEL",
icon: "https://${CUSTOM_DOMAIN}/ui/console/assets/icons/favicon-32x32.png",
args: {
name: "openid_connect",
scope: ["openid","profile","email"],
response_type: "code",
issuer: "https://${CUSTOM_DOMAIN}",
discovery: true,
client_options: {
identifier: "",
secret: "",
redirect_uri: "https:///users/auth/openid_connect/callback"
}
}
}
]
```
# ZITADEL on Google Cloud Workforce Identity
This guide shows how to login users and assign roles with [Workforce Identity Federation to Google Cloud](https://cloud.google.com/iam/docs/workforce-identity-federation).
It covers how to:
* create and configure your application in ZITADEL
* configure an Action to transform claims
* create and configure the connection to Google Cloud with Workforce Identity Federation using OpenID Connect (OIDC)
Prerequisites:
* existing ZITADEL Instance, if not present follow [this guide](/guides/start/quickstart)
* existing ZITADEL Organization, if not present follow [this guide](/guides/manage/console/organizations-overview)
* existing ZITADEL project, if not present follow the first 3 steps [here](/guides/manage/console/projects-overview)
* prerequisites on Google Cloud side [in the settings guide](https://cloud.google.com/iam/docs/configuring-workforce-identity-federation).
> We have to switch between ZITADEL and a Google Cloud. If the headings begin with "ZITADEL" switch to the ZITADEL Management Console and if
> the headings start with "Google Cloud" please refer to the configuration guide on Google Cloud.
**Google Cloud**: Configure [#google-cloud-configure]
Follow the steps **Before you begin**, **Required roles**, and **create a workforce identity pool** (OIDC) in the [in the configuration guide](https://cloud.google.com/iam/docs/configuring-workforce-identity-federation).
Before you create the workforce identity pool provider you should create your application in ZITADEL.
**ZITADEL**: Create the application [#zitadel-create-the-application]
In your existing project:
First of all we create the application in your project.
Google Cloud requires just an ID Token as JWT including the [described required and optional scopes](https://cloud.google.com/iam/docs/workforce-identity-federation#attribute-mappings).
Create a new application and click on "I'm a pro. Skip this wizard."
* **Application Type**: Web
* **Grant Types**: Implicit
* **Response Type**: ID Token
* **Authentication Method**: None
You need to add the redirect URL and configure token settings after creating the application.
**ZITADEL**: Redirect url [#zitadel-redirect-url]
After creating, go to the application settings "Redirect settings" and add the redirect url from Googles configuration guide.
It looks something like `https://auth.cloud.google/signin-callback/locations/global/workforcePools/WORKFORCE_POOL_ID/providers/WORKFORCE_PROVIDER_ID`.
Save the settings.
Make sure to replace the `WORKFORCE_POOL_ID` and `WORKFORCE_PROVIDER_ID` with your values in the redirect url
**ZITADEL**: Token settings [#zitadel-token-settings]
After creating, go to the application settings "Token settings" and configure as follows:
* **Auth Token Type**: JWT
* **Add user roles to the access token**: disabled (optional)
* **User roles inside ID Token**: enabled
* **User Info inside ID Token**: enabled
Save the settings.
**ZITADEL**: Custom claims [#zitadel-custom-claims]
Go to your project and create roles according to the Groups in Google Cloud.
Authorize a test user by assigning roles in ZITADEL.
Google Cloud expects some claims, including groups, in a specific format as [described here](https://cloud.google.com/iam/docs/workforce-identity-federation#attribute-mappings).
Claims can be transformed in ZITADEL with [Actions](/guides/manage/console/actions-overview).
Create an Action with the following code to flatten the roles and include the claim for the users' display name.
If you want to configure a special attribute mapping in the workforce identity pool provider, then adjust the claims accordingly.
```javascript
function googleGroups(ctx, api) {
if (ctx.v1.user.grants == undefined || ctx.v1.user.grants.count == 0) {
return;
}
let grants = [];
ctx.v1.user.grants.grants.forEach(claim => {
claim.roles.forEach(role => {
grants.push(claim.projectId+':'+role)
})
})
api.v1.claims.setClaim('google.groups', grants)
api.v1.claims.setClaim('google.display_name', ctx.v1.getUser().human.displayName)
}
```
Make sure that the name of the action matches the name of the function.
And add the the Action Script to the following Flow and Trigger:
* **Flow Type**: Complement Token
* **Trigger Type**: Pre access token creation
* **Actions**: googleGroups
**Google Cloud**: Create a WIP provider [#google-cloud-create-a-wip-provider]
Complete the steps in the [in the configuration guide](https://cloud.google.com/iam/docs/configuring-workforce-identity-federation) with the `ISSUER_URI` and `CLIENT_ID` from ZITADEL.
# Google Workspace SSO with ZITADEL
This guide shows how to enable login with ZITADEL on Google Workspace.
You can configure two types of SAML SSO on Google Workspace:
* [SSO profile for your organization](#sso-profile-for-your-organization)
* [Third-party SSO SAML profile](#third-party-sso-saml-profile)
Both profiles need to be configured differently.
Please make sure to configure your application for the correct type.
Please refer to Google Help to [Set up SSO for your organization](https://support.google.com/a/answer/12032922) in case you need additional information on the Workspace setup.
At this time Google supports SSO with OpenID Connect only for few providers.
Prerequisites:
* You need to have a domain registered with your Google Workspace account to configure SSO profiles
* Make sure that you [verify the same domain also in your ZITADEL organization and set it as Organization Domain](/guides/manage/console/organizations-overview#organization-domain)
* A user in Google Workspace (eg, [road.runner@acme.com](mailto:road.runner@acme.com))
* A user in ZITADEL with the same username (eg, [road.runner@acme.com](mailto:road.runner@acme.com)); make sure you verify the domain to set the username. This is different than the user's email address
SSO profile for your organization [#sso-profile-for-your-organization]
Configure SSO profile on Google Workspace [#configure-sso-profile-on-google-workspace]
Open the Google settings for [SSO with third-party IdP](https://admin.google.com/u/1/ac/security/sso) and click on *ADD SSO PROFILE*.
Download the public certificate from your ZITADEL instance by requesting `${CUSTOM_DOMAIN}/saml/v2/certificate`
```bash
wget ${CUSTOM_DOMAIN}/saml/v2/certificate -O idp.crt
```
Always replace `${CUSTOM_DOMAIN}` with your Custom Domain.
Use the following settings
| Setting | Value |
| --------------------------------------------- | -------------------------------------------------- |
| Set up SSO with third-party identity provider | Enable (check) |
| Sign-in page URL | `${CUSTOM_DOMAIN}`/saml/v2/SSO |
| Sign-out page URL | `${CUSTOM_DOMAIN}`/saml/v2/SLO |
| Verification Certificate | Upload the certificate (idp.crt) |
| Use a domain-specific issuer | Enable (check) |
| Network masks | Leave blank |
| Change password URL | `${CUSTOM_DOMAIN}`/ui/console/users/me?id=security |
Create a SAML application in ZITADEL [#create-a-saml-application-in-zitadel]
Create a new .xml file with the following minimal SAML metadata contents:
```xml
```
Set or replace the variables with the values from the next screen as follows:
* `${ENTITYID}`: `google.com/a/`
* `${ACSURL}`: `https://www.google.com/a//acs`
`` is the domain you have verified in Google Workspace.
In your existing project:
Press the "+"-button to add an application
Fill in a name for the application and chose the SAML type, then click "Continue".
Either fill in the URL where ZITADEL can read the metadata from, or upload the metadata XML directly, then click "Continue".
Check your application, if everything is correct, press "Create".
Activate the SSO profile for your organization [#activate-the-sso-profile-for-your-organization]
Make sure to enable the SSO profile for your organization.
In the [domain-specific service URLs](https://admin.google.com/u/1/ac/security/sso/domain-specific-service-urls) settings select "Automatically redirect users to the third-party IdP in the following SSO profile" and select as SSO profile "SSO profile for your organization".
Save the settings.
Verify the SSO profile for your organization [#verify-the-sso-profile-for-your-organization]
Now you should be all set to verify your setup:
* Open Gmail in an incognito session with the following link: `https://mail.google.com/a/`
* Enter your username and credentials
* You should be redirected to Gmail and logged in
`` is the domain you have verified in Google Workspace.
Third-party SSO SAML profile [#third-party-sso-saml-profile]
Configure a third party SSO SAML profile and login users with ZITADEL to Google Workspace.
Add SAML profile on Google Workspace [#add-saml-profile-on-google-workspace]
Open the Google settings for [SSO with third-party IdP](https://admin.google.com/u/1/ac/security/sso) and click on *ADD SAML PROFILE*.
Download the public certificate from your ZITADEL instance by requesting `${CUSTOM_DOMAIN}/saml/v2/certificate`
```bash
wget ${CUSTOM_DOMAIN}/saml/v2/certificate -O idp.crt
```
Always replace `${CUSTOM_DOMAIN}` with your Custom Domain.
Use the following settings
| Setting | Value |
| ------------------------ | -------------------------------------------------- |
| SSO profile name | ZITADEL SSO |
| IDP entity ID | `${CUSTOM_DOMAIN}`/saml/v2/metadata |
| Sign-in page URL | `${CUSTOM_DOMAIN}`/saml/v2/SSO |
| Sign-out page URL | `${CUSTOM_DOMAIN}`/saml/v2/SLO |
| Change password URL | `${CUSTOM_DOMAIN}`/ui/console/users/me?id=security |
| Verification Certificate | Upload the certificate (idp.crt) |
Now go ahead and click *SAVE*
Entity ID and ACS URL [#entity-id-and-acs-url]
Open the Google settings for [SSO with third-party IdP](https://admin.google.com/u/1/ac/security/sso) and click on the SAML Profile *ZITADEL SSO*
You can copy the "Entity ID" and "ACS URL" from the "SP details" section.
Create a SAML application in ZITADEL [#create-a-saml-application-in-zitadel]
Create a new .xml file with the following minimal SAML metadata contents:
```xml
```
Set or replace the variables with the values from the next screen as follows:
* `${ENTITYID}`: `https://accounts.google.com/samlrp/metadata?rpid=`
* `${ACSURL}`: `https://accounts.google.com/samlrp/acs?rpid=`
Replace `` with the values from the [SSO profile](#entity-id-and-acs-url).
In your existing project:
Press the "+"-button to add an application
Fill in a name for the application and chose the SAML type, then click "Continue".
Either fill in the URL where ZITADEL can read the metadata from, or upload the metadata XML directly, then click "Continue".
Check your application, if everything is correct, press "Create".
Activate the SSO profile [#activate-the-sso-profile]
Make sure to enable the SSO profile.
In the [domain-specific service URLs](https://admin.google.com/u/1/ac/security/sso/domain-specific-service-urls) settings select "Automatically redirect users to the third-party IdP in the following SSO profile" and select as SSO profile "ZITADEL SSO".
Save the settings.
Verify the SAML SSO profile [#verify-the-saml-sso-profile]
Now you should be all set to verify your setup:
* Open Gmail in an incognito session with the following link: `https://mail.google.com/a/`
* Enter your username and credentials
* You should be redirected to Gmail and logged in
`` is the domain you have verified in Google Workspace.
Troubleshooting [#troubleshooting]
Make sure you don't use a super admin account in Google Workspace to test SSO. Super Admin users are not allowed to login with SSO and you might receive an status code 500.
# Integrate ZITADEL with your Favorite Services
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
# Log in with ZITADEL on Ping Identity through SAML 2.0
This guide shows how to enable login with ZITADEL on Ping Identity.
It covers how to:
* create and configure the application in your project
* create and configure the connection in your Ping Identity tenant
Prerequisites:
* existing ZITADEL Instance, if not present follow [this guide](/guides/start/quickstart)
* existing ZITADEL Organization, if not present follow [this guide](/guides/manage/console/organizations-overview)
* existing ZITADEL project, if not present follow the first 3 steps [here](/guides/manage/console/projects-overview)
* existing Pingidentity environment [here](https://docs.pingidentity.com/bundle/pingone/page/wqe1564020490538.html)
> We have to switch between ZITADEL and Ping Identity. If the headings begin with "ZITADEL" switch to the ZITADEL
> Management Console and
> if the headings start with "Ping" please switch to the PingIdentity GUI.
**Ping**: Create a new external identity provider [#ping-create-a-new-external-identity-provider]
To add an [external identity provider](https://docs.pingidentity.com/bundle/pingone/page/jvz1567784210191.html), you
can follow the instructions [here](https://docs.pingidentity.com/bundle/pingone/page/ovy1567784211297.html)
1. As described you have to create a new provider, with a unique identifier:
We recommend activating signing the auth request whenever possible:
2. Manually enter the necessary information:
* SSO Endpoint, for example [https://accounts.example.com/saml/SSO](https://accounts.example.com/saml/SSO)
* IDP EntityID, for example [https://accounts.example.com/saml/metadata](https://accounts.example.com/saml/metadata)
* Binding, which is a decision which you can take yourself, we recommend HTTP POST as it has fewer restrictions
* Import certificate, provided from the certificate endpoint
Everything you need to know about the attribute mapping you can find
in [Ping Identity's documentation](https://docs.pingidentity.com/bundle/pingone/page/pwv1567784207915.html)
3. With this you have defined to connection to ZITADEL as an external IDP, next is the policy to use ZITADEL as an IDP
to
connect to an application. The "How to" for that can be
found [here](https://docs.pingidentity.com/bundle/pingone/page/zqd1616600404402.html).
**ZITADEL**: Create the application [#zitadel-create-the-application]
To add the connection to ZITADEL you have to build the metadata, which should minimalistic look like this, the necessary
information can be found on the External IDPs page under "P1Connection" and "IDP Configuration" :
```xml
ENTITYID="PINGONE (SP) ENTITY ID"
ACSURL="ACS ENDPOINT"
```
In your existing project:
1. Press the "+"-button to add an application
2. Fill in a name for the application and chose the SAML type, then click "Continue".
3. Either fill in the URL where ZITADEL can read the metadata from, or upload the metadata XML directly, then click "
Continue".
4. Check your application, if everything is correct, press "Create".
Everything on the side of ZITADEL is done if the application is correctly created.
# Basic Authentication in ZITADEL
This is a guide on how to secure your API using [Basic Authentication](/apis/openidoauth/authn-methods#client-secret-basic).
Register the API in ZITADEL [#register-the-api-in-zitadel]
1. Go to your project and click on the **New** button as shown below.
2. Give a name to your application (Test API 2 is the name given below) and select type **API**.
3. Select **Basic** as the authentication method and click **Continue**.
4. Now review your settings and click **Create**.
5. You will now see the API’s **Client ID** and the **Client Secret**. Copy them and click **Close**.
6. When you click **URLs** on the left, you will see the relevant OIDC URLs. Note down the **issuer** URL, **token\_endpoint** and **introspection\_endpoint**.
7. Also note down the **Project ID** of your project.
Token introspection [#token-introspection]
With Basic Authentication, you will receive a Client ID and Client Secret for your API. Send your client\_id and client\_secret as a Basic Auth Header in the following format:
```
Authorization: "Basic " + base64( formUrlEncode(client_id) + ":" + formUrlEncode(client_secret) )
```
The request from the API to the introspection endpoint should be in the following format:
```bash
curl --request POST \
--url ${CUSTOM_DOMAIN}/oauth/v2/introspect \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic {your_basic_auth_header}' \
--data token=VjVxyCZmRmWYqd3_F5db9Pb9mHR5fqzhn...
```
Here's an example of how this is done in Python code:
```python
def introspect_token(self, token_string):
url = ZITADEL_INTROSPECTION_URL
data = {'token': token_string, 'token_type_hint': 'access_token', 'scope': 'openid'}
auth = HTTPBasicAuth(API_CLIENT_ID, API_CLIENT_SECRET)
resp = requests.post(url, data=data, auth=auth)
resp.raise_for_status()
return resp.json()
```
Introspection response [#introspection-response]
Follow this [tutorial](https://github.com/zitadel/examples-api-access-and-token-introspection/tree/main/api-basic-authentication) to learn how to register an API application using Basic Auth with ZITADEL and test it.
# Token introspection
Token introspection is the process of checking whether an access token is valid and can be used to access protected resources.
You have an API that acts as an OAuth resource server and can be accessed by user-facing applications.
To validate an access token by calling the ZITADEL introspection API, you can use the JSON Web Token (JWT) Profile (recommended) or Basic Authentication for token introspection.
It's crucial to understand that the API is entirely separate from the front end.
The API shouldn’t concern itself with the token type received.
Instead, it's about how the API chooses to call the introspection endpoint, either through JWT Profile or Basic Authentication.
Many APIs assume they might receive a JWT and attempt to verify it based on signature or expiration.
However, with ZITADEL, you can send either a JWT or an opaque Bearer token from the client end to the API.
This flexibility is one of ZITADEL's standout features.
API application [#api-application]
If you have an API that behaves as an OAuth resource server that can be accessed by user-facing applications and need to validate an access token by calling the ZITADEL introspection API, you can use the following methods to register these APIs in ZITADEL:
* [JSON Web Token (JWT) Profile (Recommended)](/guides/integrate/token-introspection/private-key-jwt)
* [Basic Authentication](/guides/integrate/token-introspection/basic-auth)
Service accounts [#service-accounts]
If there are client APIs or systems that need to access other protected APIs, these APIs or systems must be declared as [service accounts](/guides/manage/console/users-overview).
A service account is not considered an application type in ZITADEL.
Read the introduction on how to [authenticate service accounts](/guides/integrate/service-accounts/authenticate-service-accounts).
Further references [#further-references]
* [Introspection API reference](/apis/openidoauth/endpoints#token-endpoint)
* [JWT vs. opaque tokens](/concepts/knowledge/opaque-tokens)
* [Python examples for securing an API and invoking it as a service account](https://github.com/zitadel/examples-api-access-and-token-introspection)
# Private Key JWT
This guide explains how to use ZITADEL's [Private Key JWT authentication method](/apis/openidoauth/authn-methods#jwt-with-private-key) to authenticate your application and call the token introspection endpoint. This is not about securing an API, but about allowing your backend to introspect access tokens in a secure way.
If you're looking to set up Private Key JWT authentication specifically for Service Accounts, see our dedicated guide [here](/guides/integrate/service-accounts/private-key-jwt).
Register an Application & Generate a Key Pair [#register-an-application-generate-a-key-pair]
To use Private Key JWT for introspection, you must register your client application in ZITADEL and create a key pair. This process enables your application to obtain credentials needed to generate JWT assertions for authentication.
1. In your ZITADEL project, click **New** to add an application.
2. Enter a name (e.g., "Backend Client") and select **API** as the application type.
3. Choose **JWT** as the authentication method and click **Continue**.
4. Review your settings and click **Create**.
5. After creation, you’ll see the application’s **Client ID**. There is no client secret—instead, authentication relies on your private key and JWT.
6. To generate a key pair, click **New** under the application’s keys section.
7. Select **JSON** as the key type, set an expiration if desired, and click **Add**.
8. Download and save the generated key by clicking **Download**. Afterward, click **Close**.
9. The downloaded key file will look like this:
```
{
"type": "application",
"keyId": "",
"key": "-----BEGIN RSA PRIVATE KEY-----\n\n-----END RSA PRIVATE KEY-----\n",
"appId": "",
"clientId": ""
}
```
10. In the left menu, select **URLs** to view the application’s OIDC endpoints. Note the **issuer** URL, **token\_endpoint**, and **introspection\_endpoint**.
11. Optionally, note your **Project ID** for reference.
Calling the Introspection Endpoint [#calling-the-introspection-endpoint]
To introspect an access token in ZITADEL, your backend must authenticate by providing a JWT (`client_assertion`) signed with your application's private key. ZITADEL verifies this JWT to ensure the request is from a trusted application.
**Required request parameters:**
| **Parameter** | **Description** |
| ----------------------- | ---------------------------------------------------------------- |
| `client_assertion` | The JWT assertion created and signed as described below. |
| `client_assertion_type` | Must be `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
| `token` | The access token you want to introspect. |
Creating the Client Assertion (JWT) [#creating-the-client-assertion-jwt]
The `client_assertion` parameter is a signed JWT with the following structure:
**Header:**
```json
{
"alg": "RS256",
"kid": ""
}
```
**Payload:**
```json
{
"iss": "",
"sub": "",
"aud": "https://${YOUR_DOMAIN}", // Your ZITADEL issuer URL
"exp": 1605183582, // Expiry (Unix timestamp)
"iat": 1605179982 // Issued at (Unix timestamp, not older than 1 hour)
}
```
* `iss` and `sub`: Your application’s Client ID.
* `aud`: The issuer URL for your ZITADEL instance (e.g. `https://my-tenant.zitadel.cloud`).
* `exp`: JWT expiration time (seconds since epoch).
* `iat`: JWT issued at time (seconds since epoch), must not be older than 1 hour.
You can generate the signed JWT using libraries in your preferred language or helper tools like [zitadel-tools](https://github.com/zitadel/zitadel-tools) or [https://dinochiesa.github.io/jwt/](https://dinochiesa.github.io/jwt/).
Example Introspection HTTP Request [#example-introspection-http-request]
Make a POST request to the introspection endpoint, sending the above parameters:
```bash
curl --request POST \
--url ${YOUR_DOMAIN}/oauth/v2/introspect \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
--data client_assertion=eyJhbGciOiJSUzI1Ni... \
--data token=VjVxyCZmRmWYqd3_F5db9Pb9mHR
```
Python Example [#python-example]
Below is a Python function that generates the JWT assertion and performs token introspection:
```python
def introspect_token(token_string):
# Build JWT for client assertion
payload = {
"iss": API_KEY_FILE["clientId"],
"sub": API_KEY_FILE["clientId"],
"aud": ZITADEL_ISSUER_URL,
"exp": int(time.time()) + 60 * 60, # expires in 1 hour
"iat": int(time.time())
}
headers = {
"alg": "RS256",
"kid": API_KEY_FILE["keyId"]
}
jwt_token = jwt.encode(payload, API_KEY_FILE["key"], algorithm="RS256", headers=headers)
req_headers = {"Content-Type": "application/x-www-form-urlencoded"}
req_data = {
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": jwt_token,
"token": token_string
}
response = requests.post(ZITADEL_INTROSPECTION_ENDPOINT, headers=req_headers, data=req_data)
response.raise_for_status()
token_data = response.json()
print(f"Token data from introspection: {token_data}")
return token_data
```
Introspection Response [#introspection-response]
For more details and a complete working example, see this [tutorial](https://github.com/zitadel/examples-api-access-and-token-introspection/tree/main/api-jwt) on registering an application in ZITADEL and calling the introspection endpoint with a JWT assertion.
# Apache 2.0
This integration guide shows you the basic OpenID Connect integration with ZITADEL and an Apache 2.0 server.
Setup PKCE client in ZITADEL [#setup-pkce-client-in-zitadel]
* Go to your organization and set up a new application with the type PKCE
* When created go to the "Redirect Settings" and *enable Development Mode*
* Add the Redirect Uri, f.e. `http://localhost:8080/secure/callback`
* Add the Post Logout Uri, f.e. `http://localhost:8080/index.html`
You can find the url to your discovery endpoint under "URLs":
Configure Apache2 [#configure-apache-2]
Configure mod_auth_openidc [#configure-mod-auth-openidc]
We use the module `mod_auth_openidc` in this guide.
You can find a minimal configuration in the [official documentation](https://github.com/OpenIDC/mod_auth_openidc/wiki#16-what-does-a-minimal-apache-configuration-file-look-like).
The following parameters must be set with the values from ZITADEL.
```yaml
OIDCProviderMetadataURL https://${CUSTOM_DOMAIN}/.well-known/openid-configuration
OIDCClientID
# OIDCRedirectURI is a vanity URL that must point to a path protected by this module but must NOT point to any content
OIDCRedirectURI
OIDCCryptoPassphrase
OIDCScope "openid profile"
OIDCPKCEMethod S256
```
With the following parameters
| Parameter | Description | Example value |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| OIDCProviderMetadataURL | Is the url to the discovery endpoint | `https://${CUSTOM_DOMAIN}/.well-known/openid-configuration` |
| OIDCClientID | Is the ID of the zitadel application. You can find it on the settings page of the application. | 123456789123\@apache\_test |
| OIDCRedirectURI | Users will be redirected to this page after successful login. If you are using localhost or any other non-https endpoint, make sure to enable development mode in ZITADEL. | [https://mysecureapp.io/secure/callback](https://mysecureapp.io/secure/callback) |
| OIDCCryptoPassphrase | Create a secure passphrase. Consult the module's documentation for more details. | ... |
| OIDCScope | OpenID Connect scopes that should be included. You can find a list of [all scopes](/apis/openidoauth/scopes) in our documentation. | "openid profile" |
| OIDCPKCEMethod | The method should be set to `S256` | S256 |
Secure a route [#secure-a-route]
If you want to secure a route / path then add do so by adding the following Location functionality with the directives:
```text
AuthType openid-connect
Require valid-user
```
With the same functionality you can also specify if roles / permissions must be present on the user, or limit access to specific users.
Please consult the official documentation on more information.
Handling logout [#handling-logout]
Consult the official documentation on how to [logout users](https://github.com/OpenIDC/mod_auth_openidc/wiki#9-how-do-i-logout-users).
Or check out the [example code](#example-code) for a minimal version.
Example code [#example-code]
We provide a [minimum boilerplate example](https://github.com/zitadel/example-apache2) to test the integration of ZITADEL with an Apache server.
Follow the instructions in the readme.
# Access ZITADEL APIs
This guide explains what ZITADEL APIs are and how to access ZITADEL APIs using a service account to manage all types of resources and settings.
What are the ZITADEL APIs [#what-are-the-zitadel-ap-is]
ZITADEL exposes a variety of APIs that allow you to interact with its functionalities programmatically.
These APIs are offered through different protocols, including gRPC and REST.
Additionally, ZITADEL provides [SDKs for popular languages](/sdk-examples/introduction) and frameworks to simplify integration.
Here's a breakdown of some key points about ZITADEL APIs:
* **Auth API:** Used by authenticated users for tasks related to their accounts.
* **Management API:** Used by organization administrators for administrative tasks.
* **Admin API:** Used for administrative functions on the ZITADEL instance itself (may require separate user setup).
* **System API:** For ZITADEL self-hosted deployments only, providing superordinate control (requires specific settings).
ZITADEL is transitioning from a use-case based API structure to a resource-based one, aiming to simplify API usage.
For further details and in-depth exploration, you can refer to the [ZITADEL API documentation](/apis/introduction).
How to access ZITADEL APIs [#how-to-access-zitadel-ap-is]
Accessing ZITADEL APIs, except for the Auth API and the System API, requires these basic steps:
1. **Create a service account**: A service account is a special type of account used to grant programmatic access to ZITADEL's functionalities. Unlike regular users who log in with a username and password, [service accounts rely on a more secure mechanism involving digital keys and tokens](../service-accounts/authenticate-service-accounts).
2. **Give permission to access ZITADEL APIs**: Assign a Administrator role to the service account, giving it permission to make changes to certain resources in ZITADEL.
3. **Authenticate the service account**: Like users, service accounts must authenticate and request an OAuth token with the scope `urn:zitadel:iam:org:project:id:zitadel:aud` to access ZITADEL APIs. [Service accounts can be authenticated](../service-accounts/authenticate-service-accounts) using private key JWT, client credentials, or personal access tokens.
4. **Access ZITADEL APIs with the token**: The OAuth token must be included in the Authorization Header of calls to ZITADEL APIs.
Accessing Auth API [#accessing-auth-api]
The Auth API can be used for all operations on the requesting user, meaning the user id in the `sub` claim of the used token.
Using this API doesn't require a service account to be authenticated.
Instead, you call the Auth API with the token of the user.
[Reference documentation for authentication API](/apis/introduction#authentication)
Accessing System API [#accessing-system-api]
With the System API developers can manage different ZITADEL instances.
The System API can't be accessed by service accounts and requires special settings and authentication that can be found in our [guide to access ZITADEL's System API](./access-zitadel-system-api).
[Reference documentation for system API](/apis/introduction#system)
1\. Create a service account [#1-create-a-service-account]
First, you need to create a new service account through the management console or ZITADEL APIs.
Via Management Console:
1. In an organization, navigate to Users > Service Accounts
2. Click on **New**
3. Enter a username and a display name
4. Click on **Create**
Via APIs23: \* [Create User (Machine)](/reference/api/management/zitadel.management.v1.ManagementService.AddMachineUser)
2\. Grant an Administrator Role to the Service Account [#2-grant-an-administrator-role-to-the-service-account]
ZITADEL Administrators are users who have permission to manage ZITADEL itself.
There are different levels of administrators.
* **IAM Administrators**: This is the highest level. Users with IAM Administrator roles are able to manage the whole instance.
* **Organization Administrators**: Administrators at the Organization Level are able to manage everything within the granted Organization.
* **Project Administrators**: At this level, the user is able to manage a project.
* **Project Grant Administrators**: A project grant administrator manages projects which are granted by another organization.
There are different roles on each level. You can find more about the different roles here: [ZITADEL Administrator Roles](/guides/manage/console/administrators#roles)
To be able to access the ZITADEL APIs, your service account 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 account
4. Give the user the role you need — for example, we choose Org Owner (More about [ZITADEL Permissions](/guides/manage/console/administrators))
3\. Authenticate service account and request token [#3-authenticate-service-account-and-request-token]
Service accounts can be authenticated using private key JWT, client credentials, or personal access tokens.
The [service account authentication](../service-accounts/authenticate-service-accounts) can be used to make machine-to-machine requests to any Resource Server (eg, a backend service / API) by requesting a token from the Authorization Server (ZITADEL) and sending the short-lived token (access token) in the Header of requests.
This guide covers a specific case of service account authentication when requesting access to the [ZITADEL APIs](/apis/introduction).
While PAT can be used directly to access the ZITADEL APIS, the more secure authentication methods private key JWT and client credentials must include the [reserved scope](/apis/openidoauth/scopes) `urn:zitadel:iam:org:project:id:zitadel:aud` when requesting an access from the token endpoint.
This scope will add the ZITADEL APIs to the audience of the access token.
ZITADEL APIs will check if they are in the audience of the access token and reject the token in case they are not in the audience.
The following sections will explain the more specific authentication to access the ZITADEL APIs.
Authenticate with private key JWT [#authenticate-with-private-key-jwt]
Follow the steps in this guide to [generate a key file](../service-accounts/private-key-jwt#2-register-a-private-key-for-your-service-account) and [create a JWT and sign with private key](../service-accounts/private-key-jwt#3-create-a-jwt-and-sign-it-with-your-private-key).
With the encoded JWT (assertion) from the prior step, you will need to craft a POST request to ZITADEL's token endpoint.
**To access the ZITADEL APIs, you need the ZITADEL Project ID in the audience of your token.**
This is possible by sending a [reserved scope](/apis/openidoauth/scopes) for the audience.
Use the scope `urn:zitadel:iam:org:project:id:zitadel:aud` to include the ZITADEL project id in your audience
A sample request will look like this
```bash {5}
curl --request POST \
--url ${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 profile urn:zitadel:iam:org:project:id:zitadel:aud' \
--data assertion=eyJ0eXAiOiJKV1QiL...
```
where
* `grant_type` must be set to `urn:ietf:params:oauth:grant-type:jwt-bearer`
* `scope` should contain any [Scopes](/apis/openidoauth/scopes) you want to include, but must include `openid` and `urn:zitadel:iam:org:project:id:zitadel:aud` to access the ZITADEL APIs. For this example include `profile`.
* `assertion` is the encoded value of the JWT that was signed with your private key from the prior step
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
}
```
Use the access\_token in the Authorization header to make requests to the ZITADEL APIs.
In the following example, we read the organization of the service account.
```bash
curl --request GET \
--url ${CUSTOM_DOMAIN}/management/v1/orgs/me \
--header 'Authorization: Bearer ${TOKEN}'
```
Client credentials [#client-credentials]
Get the client id and client secret by
1. navigating to the service account, then
2. Open **Actions** in the top right corner and click on **Generate Client Secret**
3. Copy the **ClientID** and **ClientSecret** from the dialog
With the ClientId and ClientSecret from the prior step, you will need to craft a POST request to ZITADEL's token endpoint.
Audience scope [#audience-scope]
**To access the ZITADEL APIs you need the ZITADEL Project ID in the audience of your token.**
This is possible by sending a [reserved scope](/apis/openidoauth/scopes) for the audience.
Use the scope `urn:zitadel:iam:org:project:id:zitadel:aud` to include the ZITADEL project id in your audience
In this step we will authenticate a service account and receive an access\_token to use against the ZITADEL API.
Basic authentication [#basic-authentication]
When using `client_secret_basic` on the token or introspection endpoints, provide an `Authorization` header with a Basic auth value in the following form:
```markdown
Authorization: "Basic " + base64( formUrlEncode(client_id) + ":" + formUrlEncode(client_secret) )
```
For example, see the [client secret basic authentication method reference](/apis/openidoauth/authn-methods#client-secret-basic).
We recommend using an OpenID / OAuth library that handles the encoding for you.
Post request [#post-request]
You will need to craft a POST request to ZITADEL's token endpoint:
```bash {6}
curl --request POST \
--url https://${CUSTOM_DOMAIN}/oauth/v2/token \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic ${BASIC_AUTH}' \
--data grant_type=client_credentials \
--data scope='openid profile urn:zitadel:iam:org:project:id:zitadel:aud'
```
where
* `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`
and `urn:zitadel:iam:org:project:id:zitadel:aud`. The latter provides access to the ZITADEL API.
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
}
```
Because the received token includes the `urn:zitadel:iam:org:project:id:zitadel:aud` scope, we can send it in your requests to the ZITADEL API as the Authorization Header.
In this example, we read the organization of the service account.
```bash
curl --request GET \
--url ${CUSTOM_DOMAIN}/management/v1/orgs/me \
--header 'Authorization: Bearer ${TOKEN}'
```
Personal access token (PAT) [#personal-access-token-pat]
A Personal Access Token (PAT) is a ready-to-use token that can be used as an *Authorization* header.
Because the PAT is a ready-to-use token, you can add it as an Authorization Header and send it in your requests to the ZITADEL API.
In this example, we read the organization of the service account.
```bash
curl --request GET \
--url ${CUSTOM_DOMAIN}/management/v1/orgs/me \
--header 'Authorization: Bearer {PAT}'
```
Notes [#notes]
* [Example application in Go](./example-zitadel-api-with-go) to access ZITADEL APIs
* [Example application in .NET](./example-zitadel-api-with-dot-net) to access ZITADEL APIs
* Learn how to use the [Event API](./event-api) to retrieve your audit trail
* Read about the [different methods to authenticate service accounts](../service-accounts/authenticate-service-accounts)
# Access ZITADEL System API
This guide focuses on the ZITADEL System API. To access the other APIs (Admin, Auth, Management), please checkout [this guide](./access-zitadel-apis).
The ZITADEL System API is currently only available for ZITADEL Self-Hosted deployments.
System API User [#system-api-user]
The System API works superordinate over all instances. Therefore, you need to define a separate users to get access to this API.
You can do so by customizing the [runtime settings](/self-hosting/manage/configure/configure#runtime-configuration-file).
To authenticate the user a self-signed JWT will be created and utilized.
You can define any id for your user. This guide will assume it's `system-user-1`.
**NOTE:** system user id cannot contain capital letters
Generate an RSA keypair [#generate-an-rsa-keypair]
Generate an RSA private key with 2048 bit modulus:
```bash
openssl genrsa -traditional -out system-user-1.pem 2048
```
and export a public key from the newly created private key:
```bash
openssl rsa -in system-user-1.pem -outform PEM -pubout -out system-user-1.pub
```
Runtime Settings [#runtime-settings]
Provide the **public** key to the ZITADEL runtime settings.
Either with the path to the key:
```yaml
SystemAPIUsers:
- system-user-1:
Path: /system-user-1.pub
```
or with a base64 encoded value of the key:
```yaml
SystemAPIUsers:
- system-user-1:
KeyData:
```
You can either specify the public key directly in this way or wrap it in an X.509 certificate for better key management
in environments like kubernetes.
You can define memberships for the user as well:
```yaml
SystemAPIUsers:
- system-user-1:
Path: /system-user-1.pub
Memberships:
# MemberType System allows the user to access all APIs for all instances or organizations
- MemberType: System
Roles:
- "SYSTEM_OWNER"
- "IAM_OWNER"
- "ORG_OWNER"
# MemberType IAM and Organization let you restrict access to a specific instance or organization by specifying the AggregateID
- MemberType: IAM
Roles: "IAM_OWNER"
AggregateID: "123456789012345678"
- MemberType: Organization
Roles: "ORG_OWNER"
AggregateID: "123456789012345678"
- superuser2:
# If no memberships are specified, the user has a membership of type System with the role "SYSTEM_OWNER"
KeyData: # or you can directly embed it as base64 encoded value
```
If you don't specify any memberships, you are allowed to access the whole [ZITADEL System API](/reference/api/system).
Generate JWT [#generate-jwt]
Similar to the OAuth 2.0 JWT Profile, we will create and sign a JWT. For this API, the JWT will not be used to authenticate against ZITADEL Authorization Server, but rather directly to the API itself.
The JWT payload will need to contain the following claims:
```json
{
"iss": "",
"sub": "",
"aud": "",
"exp": ,
"iat":
}
```
So for your instance running on `custom-domain.com` the claims could look like this:
```json
{
"iss": "system-user-1",
"sub": "system-user-1",
"aud": "https://custom-domain.com",
"iat": 1659957184,
"exp": 1659960784
}
```
If your system is exposed without TLS or on a dedicated port, be sure to provide this in your audience, e.g. [http://localhost:8080](http://localhost:8080)
ZITADEL Tools [#zitadel-tools]
If you want to manually create a JWT for a test, you can also use our [ZITADEL Tools](https://github.com/zitadel/zitadel-tools). Download the latest release and run:
```bash
zitadel-tools key2jwt --audience=https://custom-domain.com --key=system-user-1.pem --issuer=system-user-1
```
Call the System API [#call-the-system-api]
Now that you configured ZITADEL and created a JWT, you can call the System API and authenticate using the token:
```bash
curl --request POST \
--url ${CUSTOM_DOMAIN}/system/v1/instances/_search \
--header 'Authorization: Bearer {token}' \
--header 'Content-Type: application/json'
```
You should get a successful response with a `totalResult` number of 1 and the details of your instance:
```json
{
"details": {
"totalResult": "1"
},
"result": [
{
"id": "172698969497928101",
"details": {
"sequence": "102",
"creationDate": "2022-08-02T09:30:10.781068Z",
"changeDate": "2022-08-02T09:30:10.781068Z",
"resourceOwner": "172698969497928101"
},
"state": "STATE_RUNNING",
"name": "ZITADEL",
"domains": [
{
"details": {
"sequence": "108",
"creationDate": "2022-08-02T09:30:10.781068Z",
"changeDate": "2022-08-02T09:30:10.781068Z",
"resourceOwner": "172698969497928101"
},
"domain": "custom-domain.com",
"primary": true
},
{
"details": {
"sequence": "108",
"creationDate": "2022-08-02T09:30:10.781068Z",
"changeDate": "2022-08-02T09:30:10.781068Z",
"resourceOwner": "172698969497928101"
},
"domain": "zitadel-gnft7o.custom-domain.com",
"generated": true
}
]
}
]
}
```
Summary [#summary]
* Create an RSA keypair
* Provide the public key with a userID to ZITADEL using the runtime settings
* Authorize the request with a JWT signed with your private key
Where to go from here:
* [ZITADEL API Documentation](/apis/introduction)
# Get Events from ZITADEL
ZITADEL leverages the power of eventsourcing, meaning every action and change within the system generates a corresponding event that is stored in the database.
To provide you with greater flexibility and access to these events, ZITADEL has introduced an Event API.
This API allows you to easily retrieve and utilize the events generated within the system, enabling you to integrate them into your own system and respond to specific events as they occur.
You need to assign a user the [administrator role](/guides/manage/console/administrators) IAM\_OWNER\_VIEWER or IAM\_OWNER to access the Event API.
If you like to know more about eventsourcing/eventstore and how this works in ZITADEL, head over to our [concepts](/concepts/eventstore/overview).
Request Events [#request-events]
Call the [ListEvents](/reference/api/admin/zitadel.admin.v1.AdminService.ListEvents) endpoint in the Administration API to get all the events you need.
To further restrict your result you can add the following filters:
* sequence
* editor user id
* event types
* aggregate id
* aggregate types
* resource owner
* creation date
```bash
curl --request POST \
--url ${CUSTOM_DOMAIN}/admin/v1/events/_search \
--header "Authorization: Bearer $TOKEN"
```
Get event types [#get-event-types]
To be able to filter for the different event types ZITADEL knows, you can request the [EventTypesList](/reference/api/admin/zitadel.admin.v1.AdminService.ListEventTypes)
```bash
curl --request POST \
--url ${CUSTOM_DOMAIN}/admin/v1/events/types/_search \
--header "Authorization: Bearer $TOKEN" \
--header 'Content-Type: application/json' \
'
```
The response will give you a list of event types. The type is what the event is called in ZITADEL itself (technical).
You can also find a translation for the event to better reflect it for an enduser perspective.
The following example shows you the event types for a password check (failed/succeeded).
```bash
...
{
"type": "user.human.password.check.failed",
"localized": {
"key": "EventTypes.user.human.password.check.failed",
"localizedMessage": "Password check failed"
}
},
{
"type": "user.human.password.check.succeeded",
"localized": {
"key": "EventTypes.user.human.password.check.succeeded",
"localizedMessage": "Password check succeeded"
}
},
...
```
Get aggregate types [#get-aggregate-types]
To be able to filter for the different aggregate types (resources) ZITADEL knows, you can request the [AggregateTypesList](/reference/api/admin/zitadel.admin.v1.AdminService.ListAggregateTypes)
```bash
curl --request POST \
--url ${CUSTOM_DOMAIN}/admin/v1/aggregates/types/_search \
--header "Authorization: Bearer $TOKEN" \
--header 'Content-Type: application/json'
```
The response will give you a list of aggregate types. The type is what the aggregate is called in ZITADEL itself (technical).
You can also find a translation for the aggregate to better reflect it for an enduser perspective.
The following example shows you the aggregate type for the user.
```bash
...
{
"type": "user",
"localized": {
"key": "AggregateTypes.user",
"localizedMessage": "User"
}
},
...
```
Example: Get user changes since a specific date [#example-get-user-changes-since-a-specific-date]
Assuming you use ZITADEL as your single source of truth for your user data.
Now you like to react to changes on the users to update data in other your other systems.
This example shows you how to get all events from users, filtered with the creation\_date (e.g since last day/hour, etc).
```bash
curl --request POST \
--url ${CUSTOM_DOMAIN}/admin/v1/events/_search \
--header "Authorization: Bearer $TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"asc": false,
"limit": 1000,
"creation_date": "2023-02-01T10:00:00.000000Z",
"aggregate_types": [
"user"
]
}'
```
Example: Find out which users have authenticated [#example-find-out-which-users-have-authenticated]
OIDC session [#oidc-session]
The following example shows you how you could use the events search to get all events where a user has authenticated using OIDC.
Also, we include the refresh tokens in this example to know when the user has received a new token.
Sessions without tokens events may by created during implicit flow with ID Token only, which does not create an access token.
```bash
curl --request POST \
--url ${CUSTOM_DOMAIN}/admin/v1/events/_search \
--header "Authorization: Bearer $TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"asc": true,
"limit": 1000,
"eventTypes": [
"oidc_session.added",
"oidc_session.access_token.added",
"oidc_session.refresh_token.added",
"oidc_session.refresh_token.renewed"
],
"aggregateTypes": [
"oidc_session"
]
}'
```
SAML session [#saml-session]
The following example shows you how you could use the events search to get all events where a user has authenticated using SAML.
```bash
curl --request POST \
--url ${CUSTOM_DOMAIN}/admin/v1/events/_search \
--header "Authorization: Bearer $TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"asc": true,
"limit": 1000,
"eventTypes": [
"saml_session.added",
"saml_session.saml_response.added"
],
"aggregateTypes": [
"saml_session"
]
}'
```
Example: Get failed login attempt [#example-get-failed-login-attempt]
The following example shows you how you could use the events search to find out the failed login attempts of your users.
You have to include all the event types that tell you that a login attempt has failed.
In this case these are the following events:
* Password verification failed
* OTP check failed (Authenticator Apps like Authy, Google Authenticator, etc.)
* Universal Second Factor (U2F) check failed (FaceID, WindowsHello, FingerPrint, etc.)
* Passkey check failed (FaceID, WindowsHello, FingerPrint, etc.)
```bash
curl --request POST \
--url ${CUSTOM_DOMAIN}/admin/v1/events/_search \
--header "Authorization: Bearer $TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"asc": true,
"limit": 1000,
"event_types": [
"user.human.password.check.failed",
"user.mfa.otp.check.failed",
"user.human.mfa.u2f.token.check.failed",
"user.human.passwordless.token.check.failed"
]
}'
```
# Integrate ZITADEL APIs into a .NET Application
This integration guide shows you how to integrate **ZITADEL** into your .NET application.
It demonstrates how to fetch some data from the ZITADEL management API.
At the end of the guide you should have an application able to read the details of your organization.
If you need any other information about the .NET SDK go to the [documentation](https://github.com/smartive/zitadel-net) of the SDK itself.
Prerequisites [#prerequisites]
The client [SDK](https://github.com/zitadel/zitadel-net) will handle all necessary OAuth 2.0 requests and send the required headers to the ZITADEL API.
All that is required, is a service account with an Org Owner (or another role, depending on the needed api requests) role assigned and its key JSON.
However, we recommend you read the guide on [how to access ZITADEL API](/guides/integrate/zitadel-apis/access-zitadel-apis) and the associated guides for a basic knowledge of :
* [Recommended Authorization Flows](/guides/integrate/login/oidc/oauth-recommended-flows)
* [Service Accounts](/guides/integrate/service-accounts/authenticate-service-accounts)
> Be sure to have a valid key JSON and that its service account is either ORG\_OWNER or at least ORG\_OWNER\_VIEWER before you continue with this guide.
.NET Setup [#net-setup]
Create a .NET application [#create-a-net-application]
Use the IDE of your choice or the command line to create a new application.
```bash
dotnet new web
```
Install the package [#install-the-package]
Install the package via nuget
```bash
dotnet add package Zitadel.Api
```
Create example application [#create-example-application]
Change the program.cs file to the content below. This will create a application for the management api and call its `GetMyUsers` function.
The SDK will make sure you will have access to the API by retrieving a Bearer Token using JWT Profile with the provided scopes (`openid` and `urn:zitadel:iam:org:project:id:zitadel:aud`).
Make sure to fill the const `apiUrl`, and `personalAccessToken` with your own instance data. The used vars below are from a test instance, to show you how it should look.
The apiURL is the domain of your instance you can find it on the instance detail in the Customer Portal or in the Management Console
```csharp
// This file contains two examples:
// 1. An example with a service account "personal access token" to access the ZITADEL API.
// 2. An example with a service account "jwt profile key" to access the ZITADEL API.
using Zitadel.Api;
using Zitadel.Credentials;
const string apiUrl = "https://zitadel-libraries-l8boqa.zitadel.cloud";
const string personalAccessToken = "ge85fvmgTX4XAhjpF0XGpelB2vn9LZanJaqmUQDuf7iTpKVowb44LFl-86pqY2mfJCEoIOk";
// or create the token provider directly:
// new StaticTokenProvider(token)
var client = Clients.AuthService(new(apiUrl, ITokenProvider.Static(personalAccessToken)));
var result = await client.GetMyUserAsync(new());
Console.WriteLine($"User: {result.User}");
// This adds the urn:zitadel:iam:org:project:id:zitadel:aud scope to the authorization request, enabling access to ZITADEL APIs.
const string apiProject = "zitadel";
var serviceAccount = ServiceAccount.LoadFromJsonString(
@"
{
""type"": ""serviceaccount"",
""keyId"": ""170084658355110145"",
""key"": ""-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAnQisbU4FuLmjLR9I2Q01Rm9Mx6WySat2mbxgmOzu04oXuESI\nyS+RkiimdN0khjqouBftYqtVes7yngMLq3E8hMCwv/kLE+YeXphZXnn8tps8M2gV\n7S//uCp9LooK9qeh0lSkOqIsh0atj/l7NAHFxnhuNhfmn8XIYJNLVNSj5yzTri5E\nSn92SAsUQLSONgr7IEmIjcuPtYeU0iLvVno52ljZHnPX2WJ0HEZv44nZpkR4qBfv\n3hJzNx7sd4TdPGHHugJD8jdG/X4bAxwL5XGHZu18cUVM5RerSMpFQHSuIGgpKmK4\nWlM1AJGeut6EX/SrCxUDvhyOnXAgqhunTUmi6QIDAQABAoIBAHn7y92Y1y743X3m\nqHMbJIBTYyRPXaCGljm0MKF6o8clpWlZq5wE3KLZ+vwa8Q1oMbnXtGqKR3t/mM4P\n9Ze2/djtyh9GOUm632qCFCIkxp+fFPOl7ipyt8V7FAT77KpP6490eqKlacunppmJ\nph/vJJAY6xwQEvGX9SC4KrN5/txLKXbVtR3V2RXy9sxbbL4cpnklmRBMeXQkpwEM\nTKELUr5Rmhg9KvS3yALgVv0dIRtOA8Z995R234hXfY0St48YEvZtsxeme47u2CVl\nHJcVH4aa9Sw6XlgAEQBxqbQHpcLvUIu3XempO7VfGklWE6OlGuEcnUWpJCD8jMZW\nPYtt9LUCgYEAwi8josS3Iyto+DMJjJKCw175N2cmFMxBGu9Rw4aHjTiN57z7AUkn\nbmT44WnSmc1bCLC+nMB34vhiEyBKXYrH7zgbeMO8QDG3aO6gXdod/IdsieZR8E3b\ngUA1wtZYyRbc7eo8U4Nqkv1NXVRuDJkz/Mfoy+m1BVKcW7YeZaaZN9MCgYEAzwYB\n/LAiJoyx5UPwuieizlT7kHI7uvZRo4oLx+cZipNCJ0NGKgX4l1NIYLaNDbCoT9N0\nylico+kn+nihzDmD6SjY2hHGSIHk7AnJOcW+Bk5TfsYb8clxfgX40udLMIS0F13R\nrJt0gD9x0O3AZv4MV9cSI0/Md0tbWePgrLI44NMCgYEAojj7TlmEnY8AbIlGqvci\n4tCO5qf3elyA712LMwtKZsIeWsDX+OUCWglkmfvsAq06JfJx60YnYagbVtsdBTSR\nftmiqarrs71U+gaQVpeHgZYpKLMPNO/2Nu5Le2/SUHwXKXML3sDk4dNXNGb6YPAE\nLGNdqiyeG8o98agdkNIzIh0CgYEAlTGhMPfGRL3UXoNN8vopjEUWXozUmvJ090S/\nJLtZXtKtNBp5cEOJWZT9biVhFeKgCZc8ba7ahA29b/aLs+AnPlrfnJh+qzZhQfHz\ngJ0PSwAbkBs5fFBOaCHppiRlvXuFRemo95m4pcwTPBx7Mj4Xqx4lxij2E2rNVMSy\n4AI4l10CgYBwefqXt8B+D+0EvmhyHk19Tk8/fPelclJUv/IVI59c0F9UMAA2rD1U\nNW6k9251OGU7mQkztluNvl13qtAW/DveOjkFeDJIMzhFjravpLQXhUK4ETnM44YL\nFbClVGJaHYSHgOkNpcN5lYVLoyEvzv9rEPwBqpZRVnwWj6L+/I2L5Q==\n-----END RSA PRIVATE KEY-----\n"",
""userId"": ""170079991923474689""
}");
client = Clients.AuthService(
new(
apiUrl,
ITokenProvider.ServiceAccount(
serviceAccount,
apiUrl,
apiProject)));
result = await client.GetMyUserAsync(new());
Console.WriteLine($"User: {result.User}");
```
Test application [#test-application]
After you have configured everything correctly, you can simply start the example by:
```bash
dotnet run
```
This will output something similar to:
```
User: {"FirstName": "MyName", "LastName": "MyLastName" ... }
```
Completion [#completion]
You have successfully used the ZITADEL .NET SDK to call the auth API!
To use the auth API you will not need a specific role, because only an authenticated user is needed.
For accessing the admin or management API the user will need some specific roles.
If you encountered an error (e.g. `code = PermissionDenied desc = No matching permissions found`),
ensure your service account has the required permissions by assigning the `ORG_OWNER` or `ORG_OWNER_VIEWER` role
and check the mentioned [guides](#prerequisites) at the beginning.
If you've run into any other problem, don't hesitate to contact us or raise an issue on [ZITADEL](https://github.com/zitadel/zitadel/issues) or in the [SDK](https://github.com/zitadel/zitadel-go/issues).
Whats next? [#whats-next]
Now you can proceed implementing our APIs by adding more calls.
Checkout more [examples from the SDK](https://github.com/zitadel/zitadel-go/blob/main/example) or refer to our [API Docs](/apis/introduction).
> This guide will be updated soon to show you how to use the SDK for your own API as well.
# Integrate ZITADEL APIs into a Go Application
This integration guide shows you how to integrate **ZITADEL** into your Go application.
It demonstrates how to fetch some data from the ZITADEL management API.
> ℹ️ These examples and guides are based on our official [Go SDK](https://github.com/zitadel/zitadel-go).
>
> The SDK is a convenient wrapper around our low-level [OIDC library](https://github.com/zitadel/oidc). For most use cases, using the helpers provided in our [Go SDK](https://github.com/zitadel/zitadel-go) is the recommended approach for implementing authentication.
At the end of the guide you should have an application able to read the details of your organization.
> This documentation references our [CLI example](https://github.com/zitadel/zitadel-go/blob/next/example/client/cli/cli.go).
Prerequisites [#prerequisites]
The client [SDK](https://github.com/zitadel/zitadel-go) will handle all necessary OAuth 2.0 requests and send the required headers to the ZITADEL API using our [Go SDK](https://github.com/zitadel/zitadel-go).
All that is required, is a service account with an Org Owner (or another role, depending on the needed api requests) role assigned and its key JSON.
However, we recommend you read the guide on [how to access ZITADEL API](/guides/integrate/zitadel-apis/access-zitadel-apis)) and the associated guides for a basic knowledge of :
* [Recommended Authorization Flows](/guides/integrate/login/oidc/oauth-recommended-flows)
* [Service Accounts](/guides/integrate/service-accounts/authenticate-service-accounts)
> Be sure to have a valid key JSON and that its service account is either ORG\_OWNER or at least ORG\_OWNER\_VIEWER before you continue with this guide.
Go Setup [#go-setup]
Add Go SDK to your project [#add-go-sdk-to-your-project]
You need to add the SDK into Go Modules by:
```bash
go get -u github.com/zitadel/zitadel-go/v3
```
Create example application [#create-example-application]
Create a new go file with the content below. This will create a application and call its `GetMyOrg` function on the ManagementService.
The SDK will make sure you will have access to the API by retrieving a Bearer Token using JWT Profile with the provided scopes (`openid` and `urn:zitadel:iam:org:project:id:zitadel:aud`).
```go reference
https://github.com/zitadel/zitadel-go/blob/next/example/client/cli/cli.go
```
Test [#test]
After you have configured everything correctly, you can simply start the example by:
```bash
go run cli.go --domain --key
```
This could look like:
```bash
go run cli.go --domain my-domain.zitadel.cloud --key ./api.json
```
This will output something similar to:
```
2023/12/20 08:48:23 INFO retrieved the organisation orgID=165467338479501569 name=DemoOrg
```
Completion [#completion]
You have successfully used the ZITADEL Go SDK to call the management API!
If you encountered an error (e.g. `code = PermissionDenied desc = No matching permissions found`),
ensure your service account has the required permissions by assigning the `ORG_OWNER` or `ORG_OWNER_VIEWER` role
and check the mentioned [guides](#prerequisites) at the beginning.
If you've run into any other problem, don't hesitate to contact us or raise an issue on [ZITADEL](https://github.com/zitadel/zitadel/issues) or in the [SDK](https://github.com/zitadel/zitadel-go/issues).
Whats next? [#whats-next]
Now you can proceed implementing our APIs by adding more calls or using a different service like the SessionService:
```go
api.SessionService().CreateSession(ctx, &session.CreateSessionRequest{})
```
Checkout more [examples from the SDK](https://github.com/zitadel/zitadel-go/blob/next/example),
like how you can integrate the [application in your own API](https://github.com/zitadel/zitadel-go/blob/next/example/api/client/main.go)
or refer to our [API Docs](/apis/introduction).
# Billing
Billing [#billing]
In the billing page shows your configured payment methods and the invoice
Payment Method [#payment-method]
If you click on the "+" Button a popup will be shown with the needed fields to add a new payment method.
At the moment we provide only "Credit Card" payment
Once a payment method is configured, it can be selected directly in the instance creation process.
Customer [#customer]
To be able to create correct billings we will need some customer information from you.
This includes the following fields:
* Name
* Country
* Email address
* Address line 1
* Address line 2
* Postal Code
* City
Tax ID [#tax-id]
If available, enter your tax ID type and VAT number.
The tax ID will be shown on the invoice.
When you enter the tax ID we will automatically calculate taxability.
Depending on your billing address we will mark the invoice as reverse charge.
Update Billing Information [#update-billing-information]
You will only need to add billing information if your want to get the pro tier. There are two options on how to add your billing info.
1. Go to the billing menu and add a new payment method.
2. Add the billing information directly during the upgrade process.
Invoices [#invoices]
We show all you invoices, and you are able to download them directly in the Customer Portal.
# ZITADEL Cloud Egress IP Addresses
When configuring your firewall or network security groups, you may need to allow traffic **from** ZITADEL Cloud to your internal infrastructure.
This page lists the static Egress (outgoing) IP addresses used by ZITADEL Cloud regions.
When do I need this? [#when-do-i-need-this]
You need to allowlist these IP addresses if you use features where ZITADEL initiates a connection to your systems. This is commonly required for the following scenarios:
[Identity Providers & Federation](/guides/integrate/identity-providers/introduction) [#identity-providers-federation]
If you are federating an external Identity Provider (IdP) that sits behind a firewall:
* **LDAP / Active Directory:** When ZITADEL connects to your LDAP server (typically port `636` for LDAPS).
* **OIDC / OAuth:** When ZITADEL connects to your IdP for:
* **Discovery:** Fetching configuration from `/.well-known/openid-configuration`.
* **Token Exchange:** Exchanging the authorization code at the `token_endpoint`.
* **User Info:** Retrieving user details from the `userinfo_endpoint`.
* **Keys:** Fetching signing keys from the `jwks_uri`.
* **SAML:** If ZITADEL needs to fetch the `metadata.xml` or artifact resolution services from an internal SAML IdP.
[Notification Providers](/guides/manage/customize/notification-providers) [#notification-providers]
* [**SMTP:**](/guides/manage/customize/notification-providers#smtp-providers) If you configured a custom SMTP sender pointing to your own mail server.
* [**Webhook / HTTP provider:**](/guides/manage/customize/notification-providers#webhook-http-provider) If you use a custom gateway for SMS or Emails.
Custom Logic [#custom-logic]
* **[Actions V1](/concepts/features/actions)**
* **[Actions V2](/concepts/features/actions_v2)**
IP Addresses by Region [#ip-addresses-by-region]
We recommend allowing the IP address corresponding to the region where your ZITADEL instance is hosted.
| Region | Egress IP Address |
| :---------------- | :---------------- |
| **Switzerland** | `34.65.158.196` |
| **Europe** | `34.107.19.72` |
| **United States** | `34.69.146.246` |
| **Australia** | `34.87.243.23` |
To find out which region your ZITADEL Cloud instance is running in, check the [ZITADEL Customer Portal](https://zitadel.com/admin/instances).
# ZITADEL Instances
The ZITADEL Customer Portal is used to manage all your different ZITADEL instances.
Instances are containers for your organizations, users and projects.
A recommended setup could look like the following:
1. Instance: "Dev Environment"
2. Instance: "Test Environment"
3. Instance: "Prod Environment"
In the free subscription model you have one instance included.
To be able to add more instances please upgrade to "ZITADEL Pro".
Overview [#overview]
The overview shows all the instances that are registered for your customer.
You can directly see the Custom Domain and data region.
With a click on an instance you get to the detail of the chosen instance.
New instance [#new-instance]
Click on the new button above the instance table to create a new instance.
1. Enter the name of your new instance
2. Add the credentials for your first administrator
* Username (prefilled)
* Password
3. Instance created! You can now see the details of your first instance.
Every new instance gets a generated domain of the form \[instancename]\[randomnumber].zitadel.cloud
Detail [#detail]
The detail shows you general information about your instance, the region and your usage.
Upgrade to Pro [#upgrade-to-pro]
Your first instance is included in the free subscription.
As soon as you want to create your second instance or use a "pro" feature like choosing the region, you will have to upgrade to the Pro subscription.
To upgrade you must enter your billing information.
If you hit a limit from the free tier you will automatically be asked to add your credit card information and to subscribe to the pro tier.
You can also upgrade manually at any time.
1. Click the "Upgrade to PRO" button in the menu or go to the billing menu
2. If you choose the billing menu, you can now see your Free plan, click "Upgrade to Pro"
3. Add the missing data
* Payment method: Credit Card Information
* Customer: At least you have to fill the country
4. Save the information
Add Custom Domain [#add-custom-domain]
We recommend register a Custom Domain to access your ZITADEL instance.
The primary Custom Domain of your ZITADEL instance will be the issuer of the instance. All other Custom Domains can be used to access the instance itself
1. Browse to the "Instances" Tab
2. Click on the instance box to open the instance detailed view
3. Scroll down to the **Custom Domains** section
4. Click on **+ Add Custom Domain**
5. Enter the domain you want
6. In the next screen you will get all the information you will have to add to your DNS provider to verify your domain
Be aware that it has some impacts if you change the primary domain of your instance.
1. The urls and issuer have to change in your app
2. Passkey authentication is based on the domain, if you change it, your users will not be able to login with the registered passkey authentication
Verify Custom Domain [#verify-custom-domain]
As soon as you have added your Custom Domain you will have to verify it, by adding a CNAME record to your DNS provider.
1. Go to your DNS provider
2. Add a new CNAME record (You can find the target on the detail page of your instance)
3. After adding the CNAME you need to wait till the domain is verified (this can take some time)
> ***Please note:*** Do not delete the verification code, as ZITADEL Customer Portal will re-check the ownership of your domain periodically.
You will now be able to use the added Custom Domain to access your ZITADEL instance
# Settings
In the settings you can change your team name, notification settings and delete your team.
Team name [#team-name]
Change your ZITADEL Cloud team name by entering a new name.
Confirm the changes by clicking the Change button.
Notifications and newsletters [#notifications-and-newsletters]
You can subscribe and unsubscribe to notifications and newsletters:
* Onboarding: Welcome information for new users
* Newsletter: Regular newsletter on ZITADEL
* Product News: Receive product updates
* Security: Receive notifications related to security issues
If you want to stay up to date on our technical advisories, we recommend [subscribing here to the mailing list](/support/technical_advisory#subscribe-to-our-mailing-list).
Technical advisories are notices that report major issues with ZITADEL Self-Hosted or the ZITADEL Cloud platform that could potentially impact security or stability in production environments.
You can also manage your subscriptions by clicking the unsubscribe link in the emails.
We are required to inform you about changes to the terms of service or based on regulatory requirements. You can't unsubscribe to these notifications.
Delete team [#delete-team]
Permanently delete your ZITADEL Cloud account.
This will delete your team and delete all associated information.
Click **Delete Account** and confirm the next step.
Update billing information [#update-billing-information]
Refer to the [billing guide](./billing).
# Getting Started with ZITADEL
If you are new to ZITADEL your first action is to create your first ZITADEL instance and an account to access the ZITADEL Customer Portal.
The ZITADEL customer Portal is used to manage all your different ZITADEL instances.
You can also manage your subscriptions, billing, newsletters and support requests.
Go to [ZITADEL Customer Portal](https://zitadel.com) and start by entering you email or use an existing account like Google.
In a second step fill out your user data like First-, Last- and Team-name.
If you did start with your email instead of a social login (e.g. Google) you have to fill a password for your authentication.
In that case you will get an initialization mail to verify your account.
You are now registered with a free account and ready to try all the features of ZITADEL.
Sign in to [ZITADEL Customer Portal](https://zitadel.com), to manage all you instances.
# ZITADEL Support
We describe our [support services](/legal/service-description/support-services) and information required in more detail in our legal section. Beware that not all features may be supported by your subscription and consult the [support states](https://help.zitadel.com/zitadel-support-states).
General [#general]
We always recommend first having a look at our [documentation](https://zitadel.com/docs), [discord chat](https://zitadel.com/chat) and [GitHub repository](https://github.com/zitadel/zitadel). You can also ask our AI assistant for help before contacting Support.
Support Request [#support-request]
Create a new support request with the following information:
* Subject
* Detailed description
* Attachments (Optional)
* Ticket Priority
* Ticket Category
* Hosting
* Affected Instance (Only for cloud instances)
* Database Version (Only for self-hosted instances)
After submitting the form, you will receive a confirmation email, and our team will be in touch with you shortly.
# Customer Portal Administrators
Manage all your administrators who are allowed to access the Customer Portal.
For the moment all users with access to the Customer Portal will have the role "Admin".
Add new administrator [#add-new-administrator]
1. Go to the Administrators tab in the ZITADEL Customer Portal
2. Click the button "Create"
3. Fill in the Firstname, Lastname, Email and the username
4. Click create
The user will receive a verification email. Clicking the button in the email will lead to the user activation screen where password creation is required.
Delete administrator [#delete-administrator]
1. Go to the Administrators tab in the ZITADEL Customer Portal
2. Click the bin icon in the users table for the user you like to delete
3. You will get a popup, where you have to enter the login name of the user to confirm that you like to delete the user
4. Click the "delete" button
The user will be deleted and has no access to the Customer Portal anymore
# Actions
Overview [#overview]
An Identity and Access Management system is a highly interactive environment. ZITADEL includes a powerful feature called **Actions**, which allows you to programmatically react to specific events within the system.
Actions allow you to define custom scripts (JavaScript) that are executed based on specific triggers (Flows). This enables advanced customization, such as modifying tokens, calling external APIs during login, or customizing authentication flows.
How Actions Work [#how-actions-work]
The Actions architecture consists of three main components:
1. **Action:** The JavaScript code containing your business logic.
2. **Trigger Type:** The specific event in ZITADEL (e.g., "Post Authentication") where code execution is allowed.
3. **Flow:** The settings that links an Action to a Trigger Type.
The JavaScript Runtime [#the-java-script-runtime]
ZITADEL interprets your Action scripts as JavaScript.
* **Compliance:** Scripts must be **ECMAScript 5.1(+)** compliant.
* **Engine:** The underlying engine is [goja](https://github.com/dop251/goja). Refer to their documentation for detailed references about the underlying library features and limitations.
Structure of an Action Script [#structure-of-an-action-script]
The script of an action must contain a function that matches the Action's name. ZITADEL calls this function at runtime.
The function receives two primary objects:
* `ctx` (Context): Provides **readable** information about the current request (User, Request Info, etc.).
* `api` (API): Provides **methods** to mutate state (Set Claims, Deny Access, etc.).
**Example:**
If your action is named **doSomething**, your script must look like this:
```js
function doSomething(ctx, api){
// read from ctx and manipulate with api
}
```
Available Modules [#available-modules]
You can use the following built-in modules inside your JavaScript code:
* [**HTTP module**](../../../apis/actions/modules#http): To call external APIs / Webhooks.
* [**Logging module**](../../../apis/actions/modules#log): To log information to stdout (useful for debugging).
* [**UUID module**](../../../apis/actions/modules#uuid): To generate UUIDs.
Stuck customizing ZITADEL actions? Find samples for setting OIDC claims, SAML attributes, extending JIT provisioning data, calling external APIs, and more in [this repository](https://github.com/zitadel/actions).
Managing Actions in Management Console [#managing-actions-in-management-console]
1\. Create an Action [#1-create-an-action]
To add an action, navigate to your Organization's top navigation and select **Actions**. Click the **New** button and provide:
* **Name:** Must match the function name in your script.
* **Script:** Your JavaScript code.
* **Timeout:** How long the script is allowed to run before being terminated.
* **Allowed to Fail:** If checked, the flow will continue even if the script throws an error.
2\. Create a Flow (Link Action to Trigger) [#2-create-a-flow-link-action-to-trigger]
Merely creating an Action does not run it. You must create a **Flow** to define *when* it runs.
1. Select the **Flow Type** (e.g., External Authentication).
2. Select the **Trigger** (e.g., Post Authentication).
3. Add your Action to the list of executed scripts.
**Example Scenario:**
You create an **External Authentication** Flow with a **Post Authentication** trigger. Now, whenever a user authenticates via an external IDP (like Google or Azure AD), your Action is triggered immediately after the authentication step but before the session is finalized.
Available Flow Types [#available-flow-types]
Trigger types define the point during the execution of a request. Each trigger defines which readable information (`ctx`) and mutable properties (`api`) are passed into the called function.
Currently, ZITADEL supports the following flows:
* [**Internal Authentication**](../../../apis/actions/internal-authentication): Triggers during login with ZITADEL (username/password).
* [**External Authentication**](../../../apis/actions/external-authentication): Triggers during login with an external Identity Provider.
* [**Complement Token**](../../../apis/actions/complement-token): Triggers when tokens (ID Token / Access Token) are created, allowing you to add custom claims.
* [**Customize SAML Response**](../../../apis/actions/customize-samlresponse): Triggers when a SAML response is generated.
References [#references]
* [Guide: Customize Behavior with Actions](/guides/manage/customize/behavior)
* [ZITADEL Roadmap](https://zitadel.com/roadmap)
# ZITADEL Administrators
To configure administrators in ZITADEL, go to the resource where you like to add it (e.g., Instance, Organization, Project, GrantedProject).
In the right part of the management console you can find **ADMINISTRATORS** in the details part. Here you have a list of the current administrators and can add a new one.
Roles [#roles]
| Name | Role | Description |
| ----------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| Instance Owner | IAM\_OWNER | Manage the Instance, manage all organizations with their content |
| Instance Owner Viewer | IAM\_OWNER\_VIEWER | View the Instance and view all organizations with their content |
| Instance Org Manager | IAM\_ORG\_MANAGER | Manage all organizations including their policies, projects and users |
| Instance User Manager | IAM\_USER\_MANAGER | Manage all users and their authorizations over all organizations |
| Instance Admin Impersonator | IAM\_ADMIN\_IMPERSONATOR | Allow impersonation of admin and end users from all organizations |
| Instance Impersonator | IAM\_END\_USER\_IMPERSONATOR | Allow impersonation of end users from all organizations |
| Instance Login Client | IAM\_LOGIN\_CLIENT | Get all permissions needed to implement your own Login UI. |
| Org Owner | ORG\_OWNER | Manage everything within an organization |
| Org Owner Viewer | ORG\_OWNER\_VIEWER | View everything within an organization |
| Org User Manager | ORG\_USER\_MANAGER | Manage users and their authorizations within an organization |
| Org User Permission Editor | ORG\_USER\_PERMISSION\_EDITOR | Manage user grants and view everything needed for this |
| Org Project Permission Editor | ORG\_PROJECT\_PERMISSION\_EDITOR | Grant Projects to other organizations and view everything needed for this |
| Org Project Creator | ORG\_PROJECT\_CREATOR | This role is used for users in the global organization. They are allowed to create projects and manage them. |
| Org Admin Impersonator | ORG\_ADMIN\_IMPERSONATOR | Allow impersonation of admin and end users from the organization |
| Org Impersonator | ORG\_END\_USER\_IMPERSONATOR | Allow impersonation of end users from the organization |
| Project Owner | PROJECT\_OWNER | Manage everything within a project. This includes to grant users for the project. |
| Project Owner Viewer | PROJECT\_OWNER\_VIEWER | View everything within a project. |
| Project Owner Global | PROJECT\_OWNER\_GLOBAL | Same as PROJECT\_OWNER, but in the global organization. |
| Project Owner Viewer Global | PROJECT\_OWNER\_VIEWER\_GLOBAL | Same as PROJECT\_OWNER\_VIEWER, but in the global organization. |
| Project Grant Owner | PROJECT\_GRANT\_OWNER | Same as PROJECT\_OWNER but for a granted project. |
Configure roles [#configure-roles]
If you run a self-hosted ZITADEL instance, you can define your custom roles by overwriting the defaults.yaml
In the InternalAuthZ section you will find all the roles and which permissions they have.
Example:
```bash
InternalAuthZ:
RolePermissionMappings:
- Role: "IAM_OWNER"
Permissions:
- "iam.read"
- "iam.write"
```
Administrator Permission Matrix [#administrator-permission-matrix]
This table is generated dynamically from our settings file.
# Applications
What is an application? [#what-is-an-application]
Applications are the entry point to your [Project Overview](/guides/manage/console/projects-overview)
. Users either login into one of your applications (Frontend) and interact with them directly, or use one of your APIs (Backend).
All applications within a project share the same **Roles** and **Role Assignments**. This means you define a role once (e.g., `admin`) on the Project level, and it applies across all your web, mobile, and API applications.
To access your applications, navigate to your Project in the Management Console and select your application.
Create an Application [#create-an-application]
To add an application to your project, click on the **New** button and select your application type.
Application Types [#application-types]
ZITADEL offers five application types to cover different architectural patterns:
The first three options (Web, Native, and User Agent) require user interaction. The fourth option (API) is for machine-to-machine communication without direct user interaction.
Depending on the app type, there are small differences in the possible settings. To get a good understanding about user profiles and recommended flows, read the [Recommended Flows Guide](../../integrate/login/oidc/oauth-recommended-flows#different-client-profiles).
Web [#web]
Web applications are **server-side rendered** applications that users interact with.
* **Examples:** Applications using Java (Spring/Thymeleaf), .NET (Razor), PHP, Python (Django/Flask), or enabling SSO in tools like GitLab.
* **NextJS:** A NextJS app is typically a Web Application because it can execute server-side code (SSR/API routes) to keep secrets safe.
* **Authentication:** Uses Authorization Code Flow (PKCE recommended) with a Client Secret.
Native [#native]
Native Applications are installed on a thin client, such as a smartphone or desktop computer.
* **Examples:** Android Apps, iOS Apps, Desktop Apps (Electron, Tauri).
* **Authentication:** Uses the Key file generated by ZITADEL to authenticate or Authorization Code Flow with PKCE (for user login).
User Agent [#user-agent]
User Agent Applications are executed entirely in the web browser (Client-Side).
* **Examples:** Single Page Applications (SPA) built with React, Angular, Vue, or Svelte that do not have a backend server.
* **Authentication:** Uses Authorization Code Flow with PKCE (implicit flow is supported but not recommended).
API [#api]
These are applications without human interaction, accessed by other applications or services (Machine-to-Machine).
* **Examples:** Background workers, CLIs, Daemons, Microservices.
* **Authentication:** Uses Client Credentials (JWT Profile or Basic Auth) or Private Key JWT.
SAML [#saml]
If your application doesn't support the OIDC standard but supports SAML 2.0, choose this type. SAML is an XML-based open standard for exchanging authentication and authorization data.
After creating a SAML app, you can either:
1. Add a link to a SAML metadata file.
2. Upload the metadata XML file directly.
You can find example settings for SAML 2.0 in our [integrate services](../../integrate/login/saml) guides.
Settings [#settings]
Redirect URIs [#redirect-ur-is]
App Types with user interaction (Web, Native, and User Agent) require Redirect URIs. These are the allowed URLs where ZITADEL sends the user after a successful login.
* **Security:** ZITADEL strictly checks these URIs during the login process. They must match exactly what is defined in your application code.
* **Protocols:** Native applications can use custom protocols (e.g., `myapp://`) instead of `http/https`.
Development Mode [#development-mode]
If you are developing locally or need to redirect users to a non-secure protocol (other than `https://`), you must enable **Development mode**.
When disabled, ZITADEL enforces strict security and only allows `https`.
**Glob Patterns:**
Development mode allows the use of glob patterns in Redirect URIs for flexibility:
| Special Terms | Meaning |
| :------------ | :-------------------------------------------------------------------------- |
| `*` | Matches any sequence of **non-path-separators**. |
| `/**/` | Matches zero or more directories. |
| `?` | Matches any single non-path-separator character. |
| `[class]` | Matches any single character against a class of characters (e.g., `[a-z]`). |
| `{alt1,...}` | Matches a sequence if one of the comma-separated alternatives matches. |
Double stars (`**`) should appear surrounded by path separators (e.g., `/**/`). A pattern like `path/to/**.txt` is invalid; use `path/to/**/*.txt`.
IPv6
When IPv6 URIs are used as redirects in development mode, square brackets must be escaped as they are special terms for globs. Example: `http://\[::1\]:80`
Review Settings [#review-settings]
Application Settings [#application-settings]
After creating an application, you can modify its settings.
* **Auth Method:** You can switch authentication methods (e.g., from Basic to Private Key JWT) via the toggle or dropdowns.
* **OIDC Compliance:** The Management Console checks if your settings are OIDC compliant and shows tasks for completion if anything is missing.
You **cannot** change the Application Type (e.g., from Web to Native) after creation. You must delete and recreate the application.
Token Settings [#token-settings]
In the **Token Settings** section, you can customize the tokens issued for this application:
* **Token Type:** Switch between **Bearer Token** (Opaque) and **JWT**.
* **Content:** Check options to include User Roles and User Information inside the ID Token.
* **ClockSkew:** Optionally set a time buffer added to the expiration time of the issued token to handle server time differences.
Additional Origins [#additional-origins]
If your application makes requests from domains other than the Redirect URI (e.g., a Javascript app fetching data from an API on a different domain), you can specify them here to configure CORS (Cross-Origin Resource Sharing).
Security Considerations [#security-considerations]
Ensure the management of application settings is limited to authorized users only.
* Use [Administrator roles](./administrators) to limit permissions for your users to make changes to your applications.
* When [granting projects](../../../concepts/structure/granted_projects) to other organizations, the receiving organization **cannot** see or change the application settings. They can only assign roles.
References [#references]
* [Recommended OIDC Flows](../../integrate/login/oidc/oauth-recommended-flows)
# Management Console
Overview [#overview]
The ZITADEL Management Console is the web-based Dashboard UI designed to facilitate the management and administration of ZITADEL resources and settings. It serves as a central hub where [Administrators](/concepts/structure/administrators) can perform tasks related to identity and access management, configure authentication methods, and manage the organization's infrastructure.
While the Management Console is primarily a tool for administrators, it can also be accessed by end-users to manage their own profiles (e.g., password reset, MFA setup), unless you [restrict access](/guides/solution-scenarios/restrict-console) to build your own custom UI.
Accessing the Management Console [#accessing-the-management-console]
The management console is available by navigating to the [Custom Domain](/concepts/features/custom-domain) of your instance and appending the path `/ui/console`.
Navigation and Context [#navigation-and-context]
When logged in, you are greeted by the home page, which allows you to set shortcuts to frequently used settings and projects.
Context Switcher [#context-switcher]
ZITADEL is a multi-tenant system. The Management Console features a **Context Switcher** in the **top-left** corner. This allows you to switch between the different **Organizations** you manage.
Depending on your use case:
* **B2C:** You might stick to your global organization.
* **B2B:** You will frequently switch between multiple organizations to manage specific customer settings.
To understand how to structure your organizations, read our [Solution Scenario](/guides/solution-scenarios/configurations) guides.
Key Capabilities [#key-capabilities]
The Management Console enables administrators to perform the following critical tasks:
1. **Default Settings:** Administrators can configure global system defaults. This includes authentication methods, security policies (like password complexity or lockout policies), and other system-wide parameters.
2. **Resource Management:** Create, update, and delete essential resources such as [Organizations](./organizations-overview), [Projects](./projects-overview), and [Applications](./applications-overview).
3. **User Management:** Manage the lifecycle of [User accounts](./users-overview). This includes creating new users, updating profiles, resetting passwords, and deactivating accounts.
4. **Access Control:** Define and manage permissions. Administrators can assign [Roles](/guides/manage/console/roles) to users and configure fine-grained access controls for specific resources.
5. **Administrator Assignment:** Delegate administrative tasks by assigning **Administrator** roles (e.g., Org Owner, Project Owner) to specific users, ensuring proper oversight and segregation of duties.
6. **Customization and Branding:** Customize the look and feel of ZITADEL. You can upload custom logos, select color schemes, and apply [branding](/guides/manage/customize/branding) to match your corporate identity.
7. **Audit Logging:** Access [Audit Logs](/concepts/features/audit-trail) to track user activity and setting changes. This is essential for monitoring security events and maintaining regulatory compliance.
Security and Restrictions [#security-and-restrictions]
Prevent Management Console Access [#prevent-management-console-access]
In many implementations, specifically white-label B2C scenarios, you may want to prevent end-users from accessing the generic ZITADEL Management Console entirely.
Administrators can restrict access to the management console, forcing users to interact only with your own applications or custom user interfaces.
Please follow the [Restrict Management Console Access guide](/guides/solution-scenarios/restrict-console) to achieve this.
References [#references]
* [Administrator Roles Concept](/concepts/structure/administrators)
# ZITADEL Default Settings
Default settings work as default or fallback settings for your organizational settings. Most of the time you only have to set default settings for the cases where you don't need specific behavior in the organizations themselves or you only have one organization.
To access default settings, use the settings page at `{instanceDomain}/ui/console/settings` or click at the default settings button on the **top-right** of the page and then navigate to settings in the navigation.
When you configure your default settings, you can set the following:
* **Organizations**: A list of your organizations
* [**Features**](#features): Feature Settings let you try out new features before they become generally available. You can also disable features you are not interested in.
* [**Notification settings**](#notification-settings): Setup Notification and Email Server settings for initialization-, verification- and other mails. Setup Twilio as SMS notification provider.
* [**Login Behavior and Security**](#login-behavior-and-security): Multifactor Authentication Options and Enforcement, Define whether passkeys are allowed or not, Set Login Lifetimes and advanced behavior for the login interface.
* [**Identity Providers**](#identity-providers): Define IDPs which are available for all organizations
* [**Password Complexity**](#password-complexity): Requirements for Passwords ex. Symbols, Numbers, min length and more.
* [**Password Expiry**](#password-expiry): Set an expiry for passwords. After the expiration, a user will be prompted to change their password during the next login.
* [**Lockout**](#lockout): Set the maximum attempts a user can try to enter the password or any (T)OTP method. When the number is exceeded, the user gets locked out and has to be unlocked.
* [**Domain settings**](#domain-settings): Whether users use their email or the generated username to login. Other Validation, SMTP settings
* [**Branding**](#branding): Appearance of the login interface.
* [**Message Texts**](#message-texts): Text and internationalization for emails
* [**Login Interface Texts**](#login-interface-texts): Text and internationalization for the login interface
* [**External Links**](#external-links): Links to your own Terms of Service and Privacy Policy regulations, Help Page, Support email, documentation link...
* [**OIDC Token Lifetimes and Expiration**](#oidc-token-lifetimes-and-expiration): Token lifetime and expiration settings.
* [**Secret Generator**](#secret-generator): Appearance and expiration of the generated codes and secrets used in mails for verification etc.
Features [#features]
Feature Settings let you try out new features before they become generally available. You can also disable features you are not interested in.
The Page lets you choose between the settings `Enabled`, `Disabled` or `Inherit`.
If a feature is set to `Inherit`, it becomes available once its enabled per default.
Features can range from UI changes in the management console, to new APIs or performance improvements.
Be careful on which features you enable as they can be in an experimental state.
Branding [#branding]
We recommend setting your Branding and SMTP settings initially as it will comfort your customers having a familiar UI for login and receiving notifications from your domain and mail addresses.
In the Branding settings, you can upload you Logo for the login interface, set your own colors for buttons, background, links, and choose between multiple behaviors. You don't need to be an expert as those settings can all be set without any knowledge of CSS.
| Setting | Description |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Logo | Upload your logo for the light and the dark design. This is used mainly in the login interface. |
| Icon | Upload your icon for the light and the dark design. Icons are used for smaller components. For example in management console on the top left as the home button. |
| Colors | You can set four different colors to design your login page and email. (Background-, Primary-, Warn- and Font Color) |
| Font | Upload your custom font |
| Advanced Behavior | **Hide Loginname suffix**: If enabled, your loginname suffix (Domain) will not be shown in the login page. **Disable Watermark**: If you disable the watermark you will not see the "Powered by ZITADEL" in the login page |
Make sure you click the "Apply settings" button after you finish your settings. This will ensure your design is visible for your customers.
Branding settings applied on you instance act as a default for all your organizations. If you need custom branding on a organization take a look at our guide under [organization setting](./organizations-overview#organization-settings).
Notification settings [#notification-settings]
In the notification settings you can configure when to notify users about certain events and you can customize your SMTP Server settings and your SMS Provider.
At the moment Twilio is available as SMS provider.
Notification [#notification]
You can configure on which changes the users will be notified. The text of the message can be changed in the [Message texts](#message-texts)
SMTP [#smtp]
On each instance we configure our default SMTP provider. To make sure, that you only send some E-Mails from domains you own, you need to add a Custom Domain on your instance.
You can configure many SMTP providers using templates for popular providers. The templates will add known settings like host, port or default user and will suggest values for user and/or password.
You have to activate your SMTP provider so Zitadel can use it to send your emails. Only one provider can be active.
The built-in SMTP provider is intended **only for testing and evaluation purposes**.
Email delivery through this service may be **delayed or temporarily unstable**.
For any **production setup**, you must configure your own SMTP provider to ensure reliable and consistent email delivery.
Go to the ZITADEL [customer portal](https://zitadel.cloud) to configure a Custom Domain.
To configure your custom SMTP please fill the following fields:
* Sender email address
* Sender name
* Enable or Disable Transport Layer Security (TLS)
* Host
* User
* SMTP Password
While you create/update a SMTP provider you have the chance to test your SMTP settings
In the SMTP providers table you can hover on a provider row to show buttons that allow you to activate/deactivate a provider, test your smtp settings and delete a provider
SMS [#sms]
No default provider is configured to send some SMS to your users. If you like to validate the phone numbers of your users make sure to add your twilio settings by adding your Sid, Token and either a Sender Number or a Verification Service Sid.
Setting a Verification Service Sid allows using the Twilio Verify Service instead of the Messages Service for verification.
Login Behavior and Security [#login-behavior-and-security]
The Login Policy defines how the login process should look like and which authentication options a user has to authenticate.
| Setting | Description |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Username Password allowed | Possibility to login with username and password. If this is disabled only login with external identity providers will be allowed |
| Register allowed | Enable self register possibility in the login ui, this enables username password registration as well as registration with configured external identity providers |
| External IDP allowed | Possibility to login with an external identity (e.g Google, Microsoft, Apple, etc), If you like to allow external Identity providers add them to the providers list |
| Hide password reset | Disable the self-service option for users to reset their password. |
| Domain discovery allowed | If this setting is enabled, the user doesn't have to exist when entering the username. It is required to have an Organization Domain on the organization. Example: ZITADEL is registered as organization with the domain zitadel.com and Entra ID as identity provider. A user enters [john@zitadel.com](mailto:john@zitadel.com) in the login but the user doesn't exist. The domain can be mapped to the organization and therefore the user can be redirected to the Entra ID. |
| Ignore unknown usernames | This setting can be enabled, if no error message should be shown if the user doesn't exist. Example: A user enters the login name [john@zitadel.com](mailto:john@zitadel.com), the user doesn't exist, but will be redirected to the password screen. After entering a password, the user will get an error that either username or password are wrong. |
| Disable login with email address | By default users can additionally [login with the email attribute](/guides/solution-scenarios/configurations#use-an-email-address-as-username) of their user. Check this option to disable. |
| Disable login with phone number | By default users can additionally [login with the phonenumber attribute](/guides/solution-scenarios/configurations#use-a-phone-number-as-username) of their user. Check this option to disable. |
Default Redirect URI [#default-redirect-uri]
The Default Redirect URI will be used, if a user calls the login page directly.
More specifically, typically an application will initiate login with an auth request.
The auth request contains a client-id and a redirect uri, that must match the settings in ZITADEL.
If there is no [auth request](https://zitadel.com/playgrounds/oidc), users will be redirected to the Default Redirect URI, which is by default `https://${CUSTOM_DOMAIN}/ui/console/`
Reasons why ZITADEL doesn't have a redirect URI:
* The login has not been called with an OIDC authorize request
* The user landed on the login through an email link, e.g. Password Reset, Initialize User
We recommend setting your own default redirect URI, if you do not want end users to access ZITADEL Management Console.
Change default redirect url of instance: `https://${CUSTOM_DOMAIN}/ui/console/instance?id=login`
Passkeys [#passkeys]
Passkey authentication means that the user doesn't need to enter a password to login. In our case the user has to enter his loginname and as the next step proof the identity through a registered device or token.
There are two different types one is depending on the device (e.g. Fingerprint, Face recognition, WindowsHello) and the other is independent (eg. Yubikey, Solokey).
Multifactor (MFA) [#multifactor-mfa]
In the multifactors section you can configure what kind of multifactors should be allowed. For passkeys to work, it's required to enable U2F (Universal Second Factor) with PIN. There is no other option at the moment.
Multifactors:
* U2F (Universal Second Factor) with PIN, e.g FaceID, WindowsHello, Fingerprint, Hardwaretokens like Yubikey
Second factors (2FA):
* Time-based One Time Password (TOTP), Authenticator Apps like Google/Microsoft Authenticator, Authy, etc.
* Universal Second Factor (U2F), e.g FaceID, WindowsHello, Fingerprint, Hardware tokens like Yubikey
* One Time Password with Email (OTP Email)
* One Time Password with SMS (OTP SMS)
Force a user to register and use a multifactor authentication, by checking the option "Force MFA".
Ensure that you have added the MFA methods you want to allow.
Or you can enable the "Force MFA for local authenticated users", which will enforce this rule only on local authentication, but not on users authenticated through an Identity Provider.
Login Lifetimes [#login-lifetimes]
Configure the different lifetimes checks for the login process:
* **Password Check Lifetime** specifies after which period a user has to reenter his password during the login process
* **External Login Check Lifetime** specifies after which period a user will be redirected to the IDP during the login process
* **Multi-factor Init Lifetime** specifies after which period a user will be prompted to setup a 2-Factor / Multi-factor during the login process (value 0 will deactivate the prompt)
* **Second Factor Check Lifetime** specifies after which period a user has to revalidate the 2-Factor during the login process
* **Multi-factor Login Check Lifetime** specifies after which period a user has to revalidate the Multi-factor during the login process
Identity Providers [#identity-providers]
You can set up all kinds of external identity providers for identity brokering, which support OIDC (OpenID Connect).
Create a new identity provider configuration and enable it in the list afterwards.
For a detailed guide about how to set up a new identity provider for identity brokering have a look at our [identity provider guides](/guides/integrate/identity-providers/introduction).
Password Complexity [#password-complexity]
With the password complexity policy you can define the requirements for a users password.
The following properties can be set:
* Minimum Length
* Has Uppercase
* Has Lowercase
* Has Number
* Has Symbol (Everything that is not a number or letter)
Password Expiry [#password-expiry]
With the password expiry policy you can set an expiration for user password.
After the expiration, a user will be prompted to change their password during the next authentication.
Note, that ZITADEL will not warn or notify the user about the expiry, yet. If you want your users to be notified, you can read this setting and send the notification yourself.
The following properties can be set:
* Maximum validity in days
* Expiration warning after days
Lockout [#lockout]
Define when an account should be locked.
The following settings are available:
* Maximum Password Attempts: When the user has reached the maximum password attempts the account will be locked, If this is set to 0 the lockout will not trigger.
* Maximum OTP Attempts: When the user has reached the maximum (T)OTP attempts the account will be locked, If this is set to 0 the lockout will not trigger.
If an account is locked, the administrator has to unlock it in the ZITADEL Management Console
Domain settings [#domain-settings]
Add Organization Domain as suffix to loginnames [#add-organization-domain-as-suffix-to-loginnames]
If you enable this setting, all loginnames will be suffixed with the Organization Domain. If this setting is disabled, you have to ensure that usernames are unique over all organizations.
Validate Organization Domains [#validate-organization-domains]
If this is enabled all created domains on an organization must be verified per dns/acme challenge.
More about how to verify a domain [here](/guides/manage/console/organizations-overview#domain-verification-guide).
If it is set to false, all registered domain will automatically be created as verified and the users will be able to use the domain for login.
SMTP Sender Address matches Custom Domain [#smtp-sender-address-matches-custom-domain]
If enabled, the SMTP server address must match the instance's primary Custom Domain.
With that you can ensure that users receive notifications from the same domain that is used for login.
Use email as username [#use-email-as-username]
To be able to use the email as username you have to disable the attribute "User Loginname must contain orgdomain" on your domain settings.
This means that all your users will not be suffixed with the domain of your organization and you can enter the email as username.
All usernames will then be globally unique within your instance.
You can either set this attribute on your whole ZITADEL instance or just on some specific organizations.
Please refer to the [settings guide](/guides/solution-scenarios/configurations#use-email-to-login) for more information.
External links [#external-links]
With this setting you are able to configure your privacy policy, terms of service, help links and help/support email address.
On register each user has to accept these policies.
This policy can be also be overridden by your organizations.
When focused on an input field you can see the language attribute, which can then be integrated into your link.
Example:
`https://demo.com/tos-{{.Lang}}`
Also you can set the link associated to the Documentation button in the console. Set an empty text if you don't want to show a Documentation button in your console. If you need a custom button to be shown in the management console you can set the button text and the link associated to the button (if the button text is button no text will be shown).
Message texts [#message-texts]
These are the texts for your notification mails. Available for change are:
| Message Text | Description |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------- |
| Domain Claim | The Mail after an organisation claimed a domain for itself. Users on other organisations with this domain will be notified |
| Initialization | The mail after a user has been created. A code is part of the message which then must be verified on first login |
| Passkey | The Mail to register an additional passkey device by a link |
| Password Reset | The Mail to reset the password by a link |
| Verify Email | The mail after the email has been changed. A code is part of the message which then must be verified on the next login |
| Password Changed | Notify the user, that the password has been changed. Can be configured in [Notification](#notification) |
You can set the locale of the translations on the right.
Login interface texts [#login-interface-texts]
These are the texts for the login. Just like for message texts, you can select the locale on the right.
Languages [#languages]
Drag allowed languages to the left column. Languages in the right column are not shown to your users.
Choose a default language which acts as a fallback, if no language header is set.
OIDC token lifetimes and expiration [#oidc-token-lifetimes-and-expiration]
Set up how long the different oidc tokens should live.
You can set the following times:
* Access Token Lifetime
* ID Token Lifetime
* Refresh Token Expiration
* Refresh Token Idle Expiration
Secret generator [#secret-generator]
ZITADEL has some different codes and secrets, that can be specified.
You can specify what kind of characters should be included, how long the secret should be and the expiration.
The following secrets can be specified:
* Initialization Mail Code
* Email verification code
* Phone verification code
* Password reset code
* Passkey initialization code
* Application secrets
* One Time Password (OTP) - SMS
* One Time Password (OTP) - Email
If your done with your default settings, you can proceed setting up your organizations. Again, make sure you get an understanding on how your project is structured and then continue.
# Organizations
Overview [#overview]
ZITADEL is organized around the idea of multi-tenancy. An **Organization** is the vessel where your projects and users live. It is comparable to a tenant in a SaaS system or an organizational unit (OU) in a directory-based system.
Key characteristics of Organizations in ZITADEL include:
* **Multi-tenancy:** Multiple organizations can be managed within one [instance](/concepts/structure/instance).
* **Isolation:** Users and data from one organization are separated from others.
* **Delegation:** Organizations can grant each other rights to self-manage certain aspects of the IAM (e.g., roles for access management).
In a B2B use case, an organization typically represents a business partner who requires their own branding, specific access settings, or distinct federated login providers.
Structure and Delegation [#structure-and-delegation]
Projects within Organizations [#projects-within-organizations]
You can use **Projects** within your organization to manage the security context of closely related components, such as roles, grants, and authorizations for multiple applications. You can set up multiple projects within a single organization.
Project Grants [#project-grants]
ZITADEL allows you to give *other* organizations permission to manage certain aspects of a project within *your* organization.
For example, you could set up a project with roles that define access to your software, but allow a partner organization to allocate those roles to their own users independently. As a service provider, this allows you to establish a self-service culture for your business customers.
Managing Organizations [#managing-organizations]
Create a new organization [#create-a-new-organization]
To create a new organization, click on the organizations dropdown in the Management Console and select **New organization**.
You have two options regarding the initial administrator:
1. **Current User:** Create the organization with your currently logged-in user as the organization manager. A membership for the new organization will be added to your account.
2. **New Account:** Directly create a new account to manage the organization.
Self-Service Registration [#self-service-registration]
If you want to enable your customers to create organizations themselves, ZITADEL provides a built-in registration form at:
`https://$CUSTOM-DOMAIN/ui/login/register/org`
The customer simply fills in the organization name and contact details to get started.
Default Organization [#default-organization]
On the **Default settings** page (`${CUSTOM_DOMAIN}`/ui/console/orgs), you can designate a specific organization as the "Default".
* Click the **...** on the right-hand side of the table and select **Set as default organization**.
* The current default is marked by a "Default" label.
When no specific organization is selected (e.g., during an auth request or via [Domain Discovery](/guides/solution-scenarios/domain-discovery)), users are directed to this default organization for login and self-registration.
Usernames and Domains [#usernames-and-domains]
Each organization has its own pool of users, covering both users and service accounts. How these users authenticate depends on your domain settings.
Login Name Formats [#login-name-formats]
By default, if the setting **User Loginname must contain orgdomain** is **disabled**, usernames are unique across the entire instance.
If you require usernames to be scoped to the organization (common in multi-tenant setups), you can enable **User Loginname must contain orgdomain** in your [Domain settings](./default-settings#domain-settings). Login names will then follow the format:
`{username}@{domainname}.{zitadeldomain}`
**Example:**
If a user has the username `john.doe` in an organization with the domain `acme`, the generated login name is:
`john.doe@acme.zitadel.cloud`
Custom Domain Names [#custom-domain-names]
You can verify your own domain names (e.g., `acme.ch`) to simplify the user experience. Once verified:
1. ZITADEL generates additional login names for users.
2. The user `john.doe` can log in via `john.doe@acme.ch` OR `john.doe@acme.zitadel.cloud`.
Only one user with the username `john.doe` can exist within the organization `ACME`, regardless of which domain suffix they use to log in.
Organization Domain [#organization-domain]
An organization can have multiple domain names, but only one is configured as the **Organization Domain**. The Organization Domain determines:
* Which login name is displayed to the user in the UI.
* What information is asserted in `access_tokens` (specifically the `preferred_username` claim).
Setting an Organization Domain affects users.
If a user `coyote` exists in the global organization with the login name `coyote@acme.ch`, and you subsequently verify `acme.ch` for a specific organization, the global user's login name will be replaced with `coyote@{randomvalue.tld}`. ZITADEL will notify users affected by this change.
Domain Verification Guide [#domain-verification-guide]
If the **validate Organization Domains** setting in [Domain Settings](./default-settings#domain-settings) is set to `true`, you must prove ownership of your domain via DNS or HTTP challenge.
You can disable domain verification requirements in the [default settings](/guides/manage/console/default-settings#domain-settings) if you are running a self-hosted instance and do not require strict validation.
1. Navigate to your **Organization Settings**.
2. Select the menu entry **Organization Domains**.
3. Click on the domain name you wish to verify. A dialog will appear offering DNS or HTTP challenge methods.
4. **DNS Challenge Example:** Create a TXT record with your DNS provider using the value provided by ZITADEL.
* [Cloudflare Guide](https://www.zoho.com/mail/help/adminconsole/cloudflare.html#alink1)
* [Squarespace Guide](https://support.squarespace.com/hc/en-us/articles/205812388-Domain-verification-with-a-TXT-Record-alternative-method-)
* [Name.com Guide](https://www.name.com/support/articles/115004972547-adding-a-txt-record)
* [EasyDNS Guide](https://kb.easydns.com/knowledge/how-to-make-a-dns-entry/)
5. Once verified, click **Verify**.
6. (Optional) Click **Set as primary** to make this the default domain for user login names.
Do not delete the DNS verification record after success. ZITADEL re-checks domain ownership periodically.
Organization Settings [#organization-settings]
Organizations can inherit settings from the instance defaults or override them with specific policies. Settings configured at the Organization level take priority over Default settings.
* [**Login Behavior and Security**](./default-settings#login-behavior-and-security): MFA options, passwordless authentication rules, and session lifetimes.
* [**Identity Providers**](./default-settings#identity-providers): Configure IDPs available specifically for this organization.
* [**Password Complexity**](./default-settings#password-complexity): Specific requirements for passwords (length, symbols, etc.).
* [**Lockout**](./default-settings#lockout): Max login attempts before an account is locked.
* [**Branding**](./default-settings#branding): Custom appearance for the login interface.
* [**Message Texts**](./default-settings#message-texts): Custom text and internationalization for emails.
* [**Login Interface Texts**](./default-settings#login-interface-texts): Custom text for the login UI.
* [**External Links**](./default-settings#external-links): Links to your specific Terms of Service, Privacy Policy, and Support.
Applying Custom Branding [#applying-custom-branding]
To apply a custom design (e.g., for a specific B2B customer):
1. Navigate to the Organization settings.
2. Configure the **Branding** section.
3. Ensure the **Login Behavior** on your **Project** is configured to allow organization-specific branding. Read more about project branding [here](./projects-overview#branding).
Triggering Organization Context [#triggering-organization-context]
When a user begins the login process, ZITADEL automatically determines the user's organization based on their entered login name and redirects them to the appropriate organization context. Users cannot select their organization manually.
By default, the ZITADEL Login uses the instance default settings. To trigger the specific settings (and branding) of an organization, you must provide the organization scope in your authentication request:
```bash
urn:zitadel:iam:org:id:{id}
```
This value is a [reserved scope](/apis/openidoauth/scopes#reserved-scopes) within ZITADEL.
# ZITADEL Projects: Manage Apps, Services & Roles
What is a project? [#what-is-a-project]
A Project is the vessel that holds your Applications and Roles. It defines the security context for a specific software solution.
Example Scenario [#example-scenario]
Imagine you are building a **Point of Sales (POS)** platform. In ZITADEL, you would create one Project (e.g., named `POS`).
* **Applications:** Your web portal for administration, your iOS app, and your Android app would all be defined as **Applications** within this single Project.
* **Roles:** You would create **Roles** (e.g., `admin`, `cashier`, `viewer`) inside this Project.
* **Role assignment:** All applications in this project share these roles, allowing you to create unified role assignments for your users across all platforms.
Core Components [#core-components]
Applications [#applications]
Applications define the different clients (web apps, native mobile apps, server-side APIs) that share the same roles and security context.
At the moment, we support OIDC and almost every OAuth2 client. We'll be expanding this with SAML shortly.
Go to [Applications](./applications-overview) for more details.
Roles [#roles]
A role consists of different attributes, but only the **Key** is relevant to the role assignment and must be unique within the project.
* **Key:** The identifier used in code (e.g., `role.admin`).
* **Display Name:** A human-readable name for the UI.
* **Group:** Enables better handling in the ZITADEL Management Console (e.g., giving a user all roles of a specific group).
Read more about managing roles [here](./roles).
The Default Project [#the-default-project]
When creating a new ZITADEL instance, you will find an automatically created project in the first organization.
This **Default Project** (named "ZITADEL") protects the ZITADEL Management Console and APIs.
We do not recommend changing any settings in the Default Project or using it for your own applications, as this could influence the behavior of ZITADEL itself.
Create a Project [#create-a-project]
To create a project:
1. Navigate to your Organization in the Console.
2. Go to **Projects** (or visit `https://${CUSTOM_DOMAIN}.zitadel.cloud/ui/console/projects`).
3. Click the **Create New Project** button.
4. Enter your project name and continue.
Granted Projects (B2B) [#granted-projects-b-2-b]
A powerful feature of ZITADEL is the ability to **Grant** a project to other organizations. This is essential for B2B scenarios where you sell your software to business partners.
* **The Concept:** You enable another organization to use your Project.
* **Delegation:** The granted organization can manage the role assignments of *their own users* for your project. They can assign the roles you defined to their employees without your intervention.
* **Isolation:** You can select specifically which roles are available to the granted organization. This allows you to restrict features (e.g., "Premium" roles) per customer.
Using the POS example above: You could grant the `POS` project to a partner organization. That partner can then log in with their own domain, use their own branding, and manage their own cashier users, all while using your underlying application structure.
How to Grant a Project [#how-to-grant-a-project]
1. Navigate to the Project you want to share (e.g., `POS`).
2. In the **Project Grants** section, click **New**.
3. Enter the **Domain** of the partner organization you want to grant access to.
* *Tip: If you don't know the domain, navigate to the Organization detail page to find it.*
4. Search and select the organization.
5. Select the **Roles** you want to grant to this organization and click **Save**.
Project Settings [#project-settings]
You can customize the behavior of your project, particularly regarding how login screens look and how tokens are issued.
Branding [#branding]
If you have different designs for your organizations (Private Labeling), you can define the login behavior on the project detail page.
| Setting | Description |
| :--------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Unspecified** | If nothing is specified, the system default settings will trigger. |
| **Enforce project's policy** | This forces the branding of the organization that *owns* the project throughout the entire login process. Useful if you want your users to always see *your* brand, even if they belong to a partner org. |
| **Allow login user policy** | The login starts with the project's branding. However, as soon as the user is identified (e.g., by entering their email), the branding switches to match the user's organization. |
In a B2B use case, you would typically use **Allow login user policy**. If you want to skip the detection step, you can preselect an organization using the [Organization Domain scope](/apis/openidoauth/scopes#reserved-scopes) (e.g., `urn:zitadel:iam:org:domain:primary:{domainname}`).
Role Settings [#role-settings]
You can configure strict security checks regarding roles during authentication.
* **Assert Roles on Authentication:** Role information is sent from the Userinfo endpoint (and in tokens, depending on app settings).
* **Check Role Assignment on Authentication:** Users are **only** allowed to log in if they have at least one role assigned for this project. If they have no roles, access is denied immediately.
* **Check for Project on Authentication:** Checks if the user's organization actually has a grant for this project. If not, the user cannot be authenticated.
Roles in Tokens [#roles-in-tokens]
If you want roles to appear directly inside your Access or ID Tokens (to avoid calling the Userinfo endpoint), you must enable this per application.
1. Navigate to your **Application**.
2. Open **Token Settings**.
3. Check **Assert Roles on Authentication**.
You can learn more about Application and Token settings [here](./applications-overview).
# ZITADEL Roles and Role Assignments
If you built out the [POS use case example](./projects-overview#example-scenario), you would probably need an application for administration.
In this application you would probably have somebody accessing as an accountant and somebody as an administrator, who is somebody with enhanced rights.
To achieve this, you would have to add this distinction as roles.
To add roles, jump to the section **Roles** and create those new roles with the following values
* Key: admin
* Display Name: Administrator
* Group: Administration
and
* Key: account
* Display Name: Accountant
* Group: Administration
The **Key** is used for coding (can then, for example, be requested in the ID Token).
The **Display Name** is just for you to remember its use case
The **Group** is to make it easier to assign multiple roles at once.
> The role client is for another application of the project `POS`, as all possible roles from your POS applications are defined in your project.
Role Assignments [#role-assignments]
Now, to make use of these roles, add a role assignment.
A role assignment combines a user of your organization with one or multiple roles.
> You can also add users of other organizations. Click on the hint below the username field to create an [external user role assignment](/concepts/features/external-user-grant).
If you want to test your application with your own user, navigate to the **Role Assignments** section under your project and click on **new**.
Type your username, hit "Continue," select the roles you want your user to have, and save. If you want to add all roles of the Administration group, you can click on the group to select all.
Now you can retrieve those roles in your application. ZITADEL has [multiple settings](./projects-overview#project-settings) for you to access them more easily. Navigate to the **General** section of your project for more settings.
> Note: We set up the role assignments from projects, but this can be achieved from multiple locations in the console. You can view and assign roles from the organization, the projects, or from the users page.
# Users
Overview [#overview]
ZITADEL supports authentication and authorization for different user types. We mainly differentiate between **users** (interactive) and **Service Accounts** (machine-to-machine).
Types of Users [#types-of-users]
Users (Human) [#users-human]
Users represent actual people who log in via an interactive interface (e.g., a login page).
* **Profile:** Has an email address, password, and optional profile data (phone, nickname, gender, language).
* **Authentication:** Can authenticate via password, multiple factors (MFA), or passwordless authentication (Passkeys).
* **Flow:** The application redirects the user to ZITADEL, which handles the credentials and issues a token to the application.
Read more on how to [login users with ZITADEL](/guides/integrate/login/login-users).
Service Accounts [#service-accounts]
Service accounts represent machines, backend services, or IoT devices requiring non-interactive access.
* **Profile:** Only has a name, description, and username.
* **Authentication:** Authenticates via **JWT Profile** or **Personal Access Tokens (PAT)**. Both methods support expiration settings.
* **Usage:** Primarily used to gain access to the ZITADEL Management API or to secure backend-to-backend communication.
To see how service accounts are utilized in practice, check out our [NextJS B2B Demo application](https://github.com/zitadel/zitadel-nextjs-b2b).
Federated Users [#federated-users]
Federated users are identities managed by a third-party Identity Provider (IdP) such as Google, Azure AD, or GitHub.
* **Identity Brokering:** Users log in via the external IdP ("Single Sign-On").
* **Account Linking:** Federated identities are [linked](../../../concepts/features/account-linking) to internal ZITADEL users to facilitate role assignment and audit trails.
External Users [#external-users]
In a multi-tenancy architecture, users are typically isolated within [Organizations](./organizations-overview).
However, using [External User Grants](../../../concepts/features/external-user-grant), an organization can invite users from *another* organization to access their projects. These invited users are referred to as **External Users** within the context of the inviting organization.
Administrators [#administrators]
Any user—whether User or Service Account—can be assigned an [Administrator Role](/concepts/structure/administrators).
A user with an administrator role is not just an end-user of your application but also has permissions to manage specific aspects of the ZITADEL instance, organization, or project (e.g., `ORG_OWNER`, `PROJECT_OWNER`).
Considerations [#considerations]
Uniqueness of Users [#uniqueness-of-users]
* **Scope:** Users exist strictly within **one** [Organization](./organizations-overview). It is currently not possible to move users between organizations.
* **Identifier:** User accounts are uniquely identified by their `id` or their `loginname` combined with the `Organization Domain` (e.g., `road.runner@acme.zitadel.local`).
* **Emails:** You can reuse the same email address for different user accounts across organizations.
Structuring User Pools [#structuring-user-pools]
How you organize users depends on your scenario:
* **B2B / Multi-Tenancy:** We recommend creating separate organizations for each business customer (e.g., based on their domain). Grant your projects to these organizations so they can manage their own users.
* **B2C / CIAM:** You might want to store all users in a single organization and enable a specific set of social logins for that organization.
You can only verify a domain (e.g., `acme.com`) on **one** organization. If multiple teams share the same email domain but need separate management, they might need to be consolidated into a single organization.
Hierarchy [#hierarchy]
ZITADEL does not enforce a native hierarchy or inheritance for users or organizations. We recommend structuring users along the smallest unit of groups (Organizations). You can use organization metadata or your own business logic to map complex hierarchies if needed.
Managing Users [#managing-users]
Create User [#create-user]
Metadata [#metadata]
ZITADEL provides a key-value storage system for users, which is essential for building complex applications.
**Example Use Case:**
In a Point of Sales application, you could add a `stripeCustomerId` as a metadata key to a user. Your client application can then read this metadata to fetch payment details directly from the Stripe API.
**Accessing Metadata:**
Metadata can be requested via the Auth and Management APIs, the Userinfo endpoint, or asserted directly into the ID Token.
1. **Userinfo Endpoint:** Add the scope `urn:zitadel:iam:user:metadata` to your authentication request.
2. **ID Token:** To include metadata in the token, navigate to your **Application Settings** and toggle **User Info inside ID Token**.
Role Assignments [#role-assignments]
The roles assigned to a user are displayed on user profile pages.
To access these roles in your application:
* **Userinfo Endpoint:** Check the **Assert roles on authentication** box in your [Project Settings](./roles#role-assignments).
* **ID Token:** Toggle **User roles inside ID Token** in your [Application Settings](./applications-overview).
References [#references]
* [Manage users in the Management Console](./users-overview#managing-users)
* [Migrate to ZITADEL](/guides/migrate/introduction)
* [User onboarding and registration](/guides/integrate/onboarding)
# Behavior Customization
In this guide, you will create a [ZITADEL action](../../../concepts/features/actions).
After users register using an external identity provider, the action assigns them a role.
Prerequisites [#prerequisites]
Before you start, make sure you have everything set up correctly.
* You need to be at least a ZITADEL *ORG\_OWNER*
* Your ZITADEL organization needs to have the actions feature enabled. - [Your ZITADEL organization needs to have at least one external identity provider enabled](../../integrate/identity-providers/introduction)
* [You need to have at least one role configured for a project](../console/projects)
Copy some information for the action [#copy-some-information-for-the-action]
1. Select the **Projects** navigation item.
2. Select a project that has a role configured.
3. Copy the Project ID on the screens top right.
4. Scroll to the **ROLES** section and note some roles key.
Create the action [#create-the-action]
1. Select the **Actions** navigation item.
2. In the **Actions ** section, select the **+ New** button.
3. Give the new action the name `addGrant`.
4. Paste this snippet into the multiline text-field.
5. Replace the snippets placeholders and select **Save**.
```js reference
https://github.com/zitadel/actions/blob/main/examples/add_user_grant.js
```
Run the action when a user registers [#run-the-action-when-a-user-registers]
Now, make the action hook into the [external authentication flow](/apis/actions/external-authentication).
1. In the **Flows ** section, select the **+ New** button.
2. Select the **Flow Type** *External Authentication*.
3. Select the **Trigger Type** *Post Creation*.
4. In the **Actions** dropdown, check *addGrant*.
5. Select the **Save** button.
New users automatically are assigned a role now if they register by authenticating with an external identity provider.
What's next? [#whats-next]
* [Read more about the concepts around actions](/concepts/features/actions)
* [Read more about all the options you have with actions](/guides/manage/console/actions-overview)
# Brand Customization
ZITADEL offers various customization options for your projects brand design. The branding can be configured on two different levels.
The settings on the instance level will set the default settings, which are triggered for all users if not overwritten on an organization specifically.
The second possibility is to configure it on each organization. This guide will describe the second possibility.
For this head over to the Branding Setting on your Organization Page.
How it works [#how-it-works]
You are able to customize the light and a dark mode separately.
All your changes will be shown in the preview window on the right side.
As soon as you are happy with your settings click the "Apply settings" button.
After this your settings will trigger in your system. The login and the emails will be sent with your branding.
Settings [#settings]
Logo [#logo]
Upload your logo for the chosen theme, as soon as it is uploaded the preview on the right side of the screen should show it.
Colors [#colors]
In the next part you can configure your colors.
Background color is self-explanatory, the primary color will be used for buttons, links and some highlights.
The warn color is used for all the error messages and warnings and the font color for texts.
Font [#font]
Last step to apply to your branding is the font upload.
The best way is to upload a ttf file after a successful upload you will see it in the font part, but not in the preview.
Advanced Settings [#advanced-settings]
In the advanced behavior you can choose if the loginname suffix (domain e.g. [road.runner@acme.caos.ch](mailto:road.runner@acme.caos.ch)) should be shown in the loginname screen or not and if the “ZITADEL watermark” should be hidden.
Trigger the private labeling for the login [#trigger-the-private-labeling-for-the-login]
If you like to trigger your settings for your applications you have different possibilities.
1\. Organization Domain Scope [#1-organization-domain-scope]
Send a [reserved scope](/apis/openidoauth/scopes) with your [authorization request](../../integrate/login/oidc/login-users) to trigger your organization.
The Organization Domain scope will restrict the login to your organization, so only users of your own organization will be able to log in.
You can use our [OpenID Authentication Request Playground](https://zitadel.com/playgrounds/oidc) to learn more about how to trigger an [organization's policies and branding](https://zitadel.com/playgrounds/oidc#organization-policies-and-branding).
2\. Setting on your Project [#2-setting-on-your-project]
Set the private labeling setting on your project to define which branding should trigger.
Reset to default [#reset-to-default]
If you don't like your customization anymore click the "reset policy" button.
All your settings will be removed and the default settings of the system will trigger.
# SMS, SMTP and HTTP Provider for Notifications
ZITADEL can send messages to users via different notification providers, such as SMS, SMTP, or Webhook (HTTP Provider).
While you can add multiple providers to different channels, messages will only be delivered via the active provider.
Message and notification texts can be [customized](./texts) for an instance or for each organization.
SMS providers [#sms-providers]
ZITADEL integrates with Twilio as SMS provider.
SMTP providers [#smtp-providers]
For easy setup, there are some integration templates available such as:
* Amazon SES
* Mailgun
* Mailjet
* SendGrid
* ...
Aside from those, it is also possible to configure a generic SMTP provider. With a generic SMTP provider you specify what
the SMTP server, username and what kind of auth method you want to use. Currently, the Console only supports plain auth.
However, with [the API's](https://zitadel.com/docs/apis/resources/admin/admin-service-add-email-provider-smtp) you can
configure XOAUTH2 as well.
A default SMTP provider is configured for ZITADEL Cloud customers.
This provider meant for development and testing purposes and you must replace this provider with your custom SMTP provider for production use cases to guarantee security and reliability of your service.
Webhook / HTTP provider [#webhook-http-provider]
Webhook (HTTP Provider) allows you to fully customize the messages and use integrate with any provider or custom solution to deliver the messages to users.
A provider with HTTP type will send the messages and the data to a pre-defined webhook as JSON.
Configuring a HTTP provider [#configuring-a-http-provider]
First [add a new SMS Provider of type HTTP](/reference/api/admin/zitadel.admin.v1.AdminService.AddSMSProviderHTTP) to create a new HTTP provider that can be used to send SMS messages:
```bash
curl -L 'https://${CUSTOM_DOMAIN}/admin/v1/sms/http' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer ${TOKEN}' \
-d '{
"endpoint": "http://relay.example.com/provider",
"description": "provider description"
}'
```
Where `endpoint` defines the Webhook endpoint to which the data should be sent to.
The result will contain an ID of the provider that we need in the next step.
You can configure multiple SMS providers at the same time.
To use the HTTP provider you need to [activate the SMS provider](/reference/api/admin/zitadel.admin.v1.AdminService.ActivateSMSProvider):
```bash
curl -L 'https://${CUSTOM_DOMAIN}/admin/v1/sms/:id/_activate' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer ${TOKEN}' \
-d '{}'
```
The `id` is the provider's ID from the previous step.
See full API reference for [SMS Providers](/reference/api/admin) for more details.
First [add a new Email Provider of type HTTP](/reference/api/admin/zitadel.admin.v1.AdminService.AddEmailProviderHTTP) to create a new HTTP provider that can be used to send Email messages:
```bash
curl -L 'https://${CUSTOM_DOMAIN}/admin/v1/email/http' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer ${TOKEN}' \
-d '{
"endpoint": "http://relay.example.com/provider",
"description": "provider description"
}'
```
Where `endpoint` defines the Webhook endpoint to which the data should be sent to.
The result will contain an ID of the provider that we need in the next step.
You can configure multiple Email providers at the same time.
To use the HTTP provider you need to [activate the Email provider](/reference/api/admin/zitadel.admin.v1.AdminService.ActivateEmailProvider):
```bash
curl -L 'https://${CUSTOM_DOMAIN}/admin/v1/email/:id/_activate' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer ${TOKEN}' \
-d '{}'
```
The `id` is the provider's ID from the previous step.
See full API reference for [Email Providers](/reference/api/admin/zitadel.admin.v1.AdminService.ListEmailProviders) for more details.
HTTP provider payload [#http-provider-payload]
In case of the Twilio and Email providers, the messages will be sent as before, in case of the HTTP providers the content of the messages is the same but as a HTTP call.
Here an example of the body of an payload sent via Email HTTP provider:
```json
{
"contextInfo": {
"eventType": "user.human.initialization.code.added",
"provider": {
"id": "285181292935381355",
"description": "test"
},
"recipientEmailAddress": "example@zitadel.com"
},
"templateData": {
"title": "Zitadel - Initialize User",
"preHeader": "Initialize User",
"subject": "Initialize User",
"greeting": "Hello GivenName FamilyName,",
"text": "This user was created in Zitadel. Use the username Username to login. Please click the button below to finish the initialization process. (Code 0M53RF) If you didn't ask for this mail, please ignore it.",
"url": "http://example.zitadel.com/ui/login/user/init?authRequestID=\u0026code=0M53RF\u0026loginname=Username\u0026orgID=275353657317327214\u0026passwordset=false\u0026userID=285181014567813483",
"buttonText": "Finish initialization",
"primaryColor": "#5469d4",
"backgroundColor": "#fafafa",
"fontColor": "#000000",
"fontFamily": "-apple-system, BlinkMacSystemFont, Segoe UI, Lato, Arial, Helvetica, sans-serif",
"footerText": "InitCode.Footer"
},
"args": {
"changeDate": "2024-09-16T10:58:50.73237+02:00",
"code": "0M53RF",
"creationDate": "2024-09-16T10:58:50.73237+02:00",
"displayName": "GivenName FamilyName",
"firstName": "GivenName",
"lastEmail": "example@zitadel.com",
"lastName": "FamilyName",
"lastPhone": "+41791234567",
"loginNames": [
"Username"
],
"nickName": "",
"preferredLoginName": "Username",
"userName": "Username",
"verifiedEmail": "example@zitadel.com",
"verifiedPhone": ""
}
}
```
There are 3 elements to this message:
* `contextInfo`, with information on why this message is sent like the Event, which Email or SMS provider is used and which recipient should receive this message
* `templateData`, with all texts and format information which can be used with a template to produce the desired message
* `args`, with the information provided to the user which can be used in the message to customize
# Feature Restrictions
New self-hosted and [ZITADEL Cloud instances](https://zitadel.com/admin) are unrestricted by default.
Self-hosters can change this default using the DefaultInstance.Restrictions settings section.
Users with the role IAM\_OWNER can change the restrictions of their instance using the [Feature Restrictions Admin API](/reference/api/admin).
Currently, the following restrictions are available:
* *Disallow public organization registrations* - If restricted, only users with the role IAM\_OWNERS can create new organizations. The endpoint */ui/login/register/org* returns HTTP status 404 on GET requests, and 409 on POST requests.
* *AllowedLanguages* - The following rules apply if languages are restricted:
* Only allowed languages are listed in the OIDC discovery endpoint */.well-known/openid-configuration*.
* Login UI texts are only rendered in allowed languages.
* Notification message texts are only rendered in allowed languages.
* Custom Texts can be created for disallowed languages as long as ZITADEL supports that language. Therefore, all texts can be customized before allowing a language.
Feature restrictions for an instance are intended to be configured by a user that is managed within that instance.
However, if you are self-hosting and need to control your virtual instances usage, [read about the APIs for limits and quotas](/self-hosting/manage/usage_control) that are intended to be used by system users.
# Customized Texts
You are able to customize the texts used from ZITADEL. This is possibly on the instance or organization level.
Message Texts [#message-texts]
Sometimes the users will get an email or phone message from ZITADEL (e.g. Password Reset Request).
ZITADEL already has some good standard texts, but maybe you would like to customize it for your organization.
Go to the message text policy on your organization, and you will find the different kinds of messages that are sent from ZITADEL.
Choose the template and the language you like to edit.
You can now change all the texts from a message.
As soon as you click into an input field you will see some attribute chips below the field.
These are the parameters you can include on this specific message.
Login Texts [#login-texts]
Like the message texts you are also able to change the texts on the login interface.
First choose the screen and the language you like to edit.
You will see the default texts in the input field, and you can overwrite them by typing into the box.
Reset to default [#reset-to-default]
If you don't like your customization anymore click the "reset policy" button.
All your settings will be removed and the default settings of the system will trigger.
Internationalization / i18n [#internationalization-i-18-n]
ZITADEL is available in the following languages
* German (de)
* English (en)
* Spanish (es)
* French (fr)
* Indonesian (id)
* Italian (it)
* 日本語 (ja)
* Polish(pl)
* 简体中文(zh)
* Bulgarian (bg)
* Portuguese (pt)
* Macedonian (mk)
* Czech (cs)
* Russian (ru)
* Dutch (nl)
* Swedish (sv)
* Hungarian (hu)
* 한국어 (ko)
* Romanian (ro)
* Turkish (tr)
* Ukrainian (uk)
* Arabic (ar)
A language is displayed based on your agent's language header.
If a users language header doesn't match any of the supported or [restricted](#restrict-languages) languages, the instances default language will be used.
If you need support for a specific language we highly encourage you to [contribute translation files](https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md) for the missing language.
Restrict Languages [#restrict-languages]
If you only want to enable a subset of the supported languages, you can configure the languages you'd like to allow using the [restrictions API](./restrictions).
The login UI and notification messages are only rendered in one of the allowed languages and fallback to the instances default language.
Also, the instances OIDC discovery endpoint will only list the allowed languages in the *ui\_locales\_supported* field.
All language settings are also configurable in the management consoles *Languages* default settings.
# User Metadata
This guide shows you how to request metadata from a user.
ZITADEL offers multiple methods to retrieve metadata.
Pick the one that works best for your solution.
Use cases for metadata [#use-cases-for-metadata]
Typical use cases for user metadata include:
* Link the user to an internal identifier (eg, userId, contract number, etc.)
* Save custom user data when registering a user
* Route upstream traffic based on user attributes
Before you start [#before-you-start]
Before you start you need to add some metadata to an existing user.
You can do so by using [Console](../console/users) or [setting user metadata](/reference/api/management/zitadel.management.v1.ManagementService.SetUserMetadata) through the management API.
Most of the methods below require you to login with the correct user while setting some scopes.
Make sure you pick the right user when logging into the test application.
Use the [OIDC authentication request playground](https://zitadel.com/playgrounds/oidc) or the settings of an [example application](/sdk-examples/introduction) to set the required scopes and receive a valid access token.
In case you want to test out different settings configure an application with code flow (PKCE).
Grab the code from the url parameter after a successful login and exchange the code for tokens by calling the [token endpoint](/apis/openidoauth/endpoints#token-endpoint).
You will find more information in our guides on how to [authenticate users](/guides/integrate/login/oidc/login-users).
Use tokens to get user metadata [#use-tokens-to-get-user-metadata]
Use one of these methods to get the metadata for the currently logged in user.
In case you want to manage metadata for other users than the currently logged in user, then you must use the [UserService API](#manage-user-metadata-through-the-api).
Request metadata from userinfo endpoint [#request-metadata-from-userinfo-endpoint]
With the access token we can make a request to the [userinfo endpoint](/apis/openidoauth/endpoints#introspection-endpoint) to get the user's metadata.
This method is the preferred method to retrieve a user's information in combination with opaque tokens, to insure that the token is valid.
You must pass the [reserved scope](/apis/openidoauth/scopes#reserved-scopes) `urn:zitadel:iam:user:metadata` in your authentication request.
If you don't include this scope the response will contain user data, but not the metadata object.
Request the user information by calling the [userinfo endpoint](/apis/openidoauth/endpoints#introspection-endpoint):
```bash
curl --request GET \
--url "https://${CUSTOM_DOMAIN}/oidc/v1/userinfo" \
--header "Authorization: Bearer $ACCESS_TOKEN"
```
Replace `$ACCESS_TOKEN` with your user's access token.
The response will look something like this
```json
{
"email": "road.runner@zitadel.com",
"email_verified": true,
"family_name": "Runner",
"given_name": "Road",
"locale": "en",
"name": "Road Runner",
"preferred_username": "road.runner@...asd.zitadel.cloud",
"sub": "166.....729",
"updated_at": 1655467738,
//highlight-start
"urn:zitadel:iam:user:metadata": {
"ContractNumber": "MTIzNA"
}
//highlight-end
}
```
You can grab the metadata from the reserved claim `"urn:zitadel:iam:user:metadata"` as key-value pairs.
Note that the values are base64 encoded.
So the value `MTIzNA` decodes to `1234`.
Send metadata inside the ID token [#send-metadata-inside-the-id-token]
You might want to include metadata directly into the ID Token.
For that you need to enable "User Info inside ID Token" in your application's settings.
Now request a new token from ZITADEL by logging in with the user that has metadata attached.
Make sure you log into the correct client/application where you enabled the settings.
The result will give you something like:
```json
{
"access_token": "jZuRixKQTVecEjKqw...kc3G4",
"token_type": "Bearer",
"expires_in": 43199,
"id_token": "ey...Ww"
}
```
When you decode the value of `id_token`, then the response will include the metadata claim:
```json
{
"amr": ["password", "pwd", "mfa", "otp"],
"at_hash": "lGIblkTr8faHz2zd0oTddA",
"aud": [
"170086824411201793@portal",
"209806276543185153@portal",
"170086774599581953"
],
"auth_time": 1687418556,
"azp": "170086824411201793@portal",
"c_hash": "dA3wre4ytCJCn11f7cIm0A",
"client_id": "1700...1793@portal",
"email": "road.runner@zitadel.com",
"email_verified": true,
"exp": 1687422272,
"family_name": "Runner",
"given_name": "Road",
"iat": 1687418672,
"iss": "https://...-abcd.zitadel.cloud",
"locale": null,
"name": "Road Runner",
"preferred_username": "road.runner@...-abcd.zitadel.cloud",
"sub": "170848145649959169",
"updated_at": 1658329554,
//highlight-start
"urn:zitadel:iam:user:metadata": {
"ContractNumber": "MTIzNA"
}
//highlight-end
}
```
Note that the values are base64 encoded.
So the value `MTIzNA` decodes to `1234`.
Use a website like [jwt.io](https://jwt.io/) to decode the token.
With jq installed you can also use `jq -R 'split(".") | .[1] | @base64d | fromjson' <<< $ID_TOKEN`
Request metadata from authentication API [#request-metadata-from-authentication-api]
You can use the authentication service to request and search for the user's metadata.
The introspection endpoint and the token endpoint in the examples above do not require a special scope to access.
Yet when accessing the authentication service, you need to pass the [reserved scope](/apis/openidoauth/scopes#reserved-scopes) `urn:zitadel:iam:org:project:id:zitadel:aud` along with the authentication request.
This scope allows the user to access ZITADEL's APIs, specifically the authentication API that we need for this method.
Use the [OIDC authentication request playground](https://zitadel.com/playgrounds/oidc) or the settings of an [example application](/sdk-examples/introduction) to set the required scopes and receive a valid access token.
If you get the error "invalid audience (APP-Zxfako)", then you need to add the reserved scope `urn:zitadel:iam:org:project:id:zitadel:aud` to your authentication request.
You can request the user's metadata with the [List My Metadata](/reference/api/auth/zitadel.auth.v1.AuthService.ListMyMetadata) method:
```bash
curl -L -X POST "https://${CUSTOM_DOMAIN}/auth/v1/users/me/metadata/_search" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
--data-raw '{
"query": {
"offset": "0",
"limit": 100,
"asc": true
},
"queries": [
{
"keyQuery": {
"key": "${METADATA_KEY}",
"method": "TEXT_QUERY_METHOD_EQUALS"
}
}
]
}'
```
Replace `${ACCESS_TOKEN}` with your user's access token.
Replace `${CUSTOM_DOMAIN}` with your ZITADEL instance's url.
Replace `${METADATA_KEY}` with they key you want to search for (f.e. "ContractNumber")
You can omit the queries array to retrieve all metadata key-value pairs.
An example response for your search looks like this:
```json
{
"details": {
"totalResult": "1",
"processedSequence": "2935",
"viewTimestamp": "2023-06-21T16:01:52.829838Z"
},
"result": [
{
"details": {
"sequence": "409",
"creationDate": "2022-08-04T09:09:06.259324Z",
"changeDate": "2022-08-04T09:09:06.259324Z",
"resourceOwner": "170086363054473473"
},
"key": "ContractNumber",
"value": "MTIzNA"
}
]
}
```
Register user with custom metadata [#register-user-with-custom-metadata]
When you build your own registration UI you have the possibility to have custom fields and add them to the metadata of your user.
Learn everything about how to build your own registration UI [here](/guides/integrate/onboarding/end-users#build-your-own-registration-form).
Manage user metadata through the API [#manage-user-metadata-through-the-api]
The previous methods allowed you to retrieve metadata only for the `sub` in the access token.
In case you want to manage metadata for another user, use the [User Service API](/reference/api/user).
The calling user must have the `user.write` permission to set or delete metadata.
Use [SetUserMetadata](/apis/resources/user_service_v2/user-service-set-user-metadata) to insert or update metadata entries.
Existing keys not included in the request are left unchanged.
To delete a metadata entry, pass the key with an empty value — if the key does not exist, it is ignored.
You can mix inserts, updates, and deletions in a single request.
Alternatively, use [DeleteUserMetadata](/apis/resources/user_service_v2/user-service-delete-user-metadata) to delete one or more keys explicitly.
Use [ListUserMetadata](/apis/resources/user_service_v2/user-service-list-user-metadata) to retrieve metadata for a user with optional filtering by key.
# Register and Create User
The ZITADEL API has different possibilities to create users.
This can be used, if you are building your own registration page.
Use the following API call to create your users:
[Create User (Human)](/reference/api/user/zitadel.user.v2.UserService.AddHumanUser)
With Username and Password [#with-username-and-password]
If you are collecting all the user information and a password, you can directly create the user with the password.
With the `password_change_required` flag you can choose if the user has to change the password on the first login or not.
This might make sense if an administrator created the user.
With passkey [#with-passkey]
You can directly ask for a link to create the passkey registration for the user.
Fill the user data and set the attribute `request_passwordless_registration` to true.
You will get a link for the registration and an expiration time in the response.
If you add `requestPlatformType` as query parameter to the link you can define what type the platform should be.
* **platform**: Device itself e.g. FaceID, Fingerprint etc.
* **crossPlatform** A hardware token e.g SoloKey
* **unspecified** The user is free to choose
If nothing is requested, the type will not be restricted and all possibilities of the device will be taken into account.
Add passkey to existing user [#add-passkey-to-existing-user]
If you already have a user in ZITADEL, it is possible to add passkey later.
[Add Passkey Registration ](/apis/resources/mgmt)
Send the user\_id in the request and you will get a link and an expiration as response.
You can then customize the link the same as described above in the creation process.
The second possibility is to send the link directly to the user per email.
Use the following request in that case:
[Send Passkey Registration ](/apis/resources/mgmt)
Verified Email Address [#verified-email-address]
When creating users you can define wether you want to verify their email address before activating the user.
In case you want to create the user with an verified email address set the `isEmailVerified` property to `true`.
No initialization email will be sent to the user.
# SCIM v2.0
Zitadel’s **[SCIM v2](https://scim.cloud/) service provider interface** enables standardized provisioning and lifecycle management of users and other instance resources.
By implementing the official *System for Cross-domain Identity Management (SCIM) v2.0* specification, Zitadel allows seamless and automated user creation, updates, deactivation, and deprovisioning across systems.
API [#api]
For full reference details of Zitadel’s SCIM API, consult the API documentation [here](/apis/scim2).
Provisioning domain [#provisioning-domain]
A **provisioning domain** represents an external administrative domain—separate from the service provider—typically introduced for legal, organizational, or technical reasons.
Refer to the SCIM specification definitions in **RFC7643**:
[https://datatracker.ietf.org/doc/html/rfc7643#section-1.2](https://datatracker.ietf.org/doc/html/rfc7643#section-1.2)
Zitadel uses provisioning domains to scope the handling of the SCIM **`externalId`**, ensuring that different customers or systems provisioning users do not conflict with each other.
Setting a provisioning domain [#setting-a-provisioning-domain]
A provisioning domain can be assigned to a **service account** by adding the following metadata entry:
* **Key:** `urn:zitadel:scim:provisioningDomain`
* **Value:** the name of the provisioning domain
How Zitadel stores `externalId` [#how-zitadel-stores-external-id]
If a service account has a provisioning domain set, any SCIM `externalId` provided during user provisioning or lookup is stored under:
`urn:zitadel:scim:{provisioningDomain}:externalId`
If *no* provisioning domain is configured, Zitadel falls back to a simplified metadata key:
`urn:zitadel:scim:externalId`
This ensures that different provisioning sources remain isolated, and the correct `externalId` is returned when queried through SCIM.
Mapping [#mapping]
The following table describes how Zitadel maps SCIM User attributes to Zitadel user fields.
Attributes without a direct Zitadel equivalent are stored in **user metadata**.
For more information about user metadata, see [here](../customize/user-metadata).
| SCIM | Zitadel | Remarks |
| ---------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id` | `userId` | |
| `username` | `username` | |
| `name.formatted` | `profile.displayName` | The SCIM attribute `displayName` takes precedence over `name.formatted` |
| `name.familyName` | `profile.familyName` | |
| `name.middleName` | `metadata[urn:zitadel:scim:name.middleName]` | |
| `name.honorificPrefix` | `metadata[urn:zitadel:scim:name.honorificPrefix]` | |
| `name.honorificSuffix` | `metadata[urn:zitadel:scim:name.honorificSuffix]` | |
| `displayName` | `profile.displayName` | The SCIM attribute `displayName` takes precedence over `name.formatted` |
| `nickName` | `profile.nickName` | |
| `profileUrl` | `metadata[urn:zitadel:scim:profileUrl]` | |
| `title` | `metadata[urn:zitadel:scim:title]` | |
| `preferredLanguage` | `profile.preferredLanguage` | |
| `locale` | `metadata[urn:zitadel:scim:locale]` | |
| `timezone` | `metadata[urn:zitadel:scim:timezone]` | |
| `active` | `state` | `Initial` and `Active` are mapped to `active = true`, all other states are mapped to `active = false`. The `active` value can only be updated if the user is in the state `Active` or `Inactive`. |
| `password` | `password` | |
| `emails` | `email` | Only the `primary` email is stored in Zitadel, if there is no `primary` email, the first one is stored. By default emails from SCIM are considered verified, this can be adjusted in the [settings](#settings). |
| `phoneNumbers` | `phone` | Only the `primary` phone number is stored in Zitadel, if there is no `primary` phone number, the first one is stored. By default phone numbers from SCIM are considered verified, this can be adjusted in the [settings](#settings). |
| `ims` | `metadata[urn:zitadel:scim:ims]` | Serialized as JSON. |
| `photos` | `metadata[urn:zitadel:scim:photos]` | Serialized as JSON. |
| `addresses` | `metadata[urn:zitadel:scim:addresses]` | Serialized as JSON. |
| `entitlements` | `metadata[urn:zitadel:scim:entitlements]` | Serialized as JSON. |
| `roles` | `metadata[urn:zitadel:scim:roles]` | Serialized as JSON. |
| `externalId` | `metadata[urn:zitadel:scim:externalId]` `metadata[urn:zitadel:scim:{provisioningDomain}:externalId]` | See [provisioning domain](#provisioning-domain). |
Settings [#settings]
This section provides details on the runtime settings of the SCIM interface of Zitadel.
Defaults [#defaults]
* Emails from SCIM are treated as **verified**
* Phone numbers from SCIM are treated as **verified**
* Bulk SCIM operations can include up to **100 operations**
* Maximum SCIM request body size is **1 MB**
This behavior can be adjusted through the Zitadel runtime settings:
```yaml
SCIM:
EmailVerified: true
PhoneVerified: true
MaxRequestBodySize: 1_000_000
Bulk:
MaxOperationsCount: 100
```
Limitations [#limitations]
Zitadel’s SCIM implementation follows the core SCIM specification but includes the following limitations.
Supported schemas [#supported-schemas]
Zitadel currently supports only the SCIM User schema: `urn:ietf:params:scim:schemas:core:2.0:User`
Group provisioning and other extended schemas are not supported.
Required attributes [#required-attributes]
In addition to SCIM-standard required fields, Zitadel requires:
* `name.familyName`
* `name.givenName`
* `emails`: at least one email is required
Duplicated attribute mapping [#duplicated-attribute-mapping]
The SCIM user attributes `name.formatted` and `displayName` are both mapped to the `profile.displayName` attribute in
Zitadel.
If both are provided, `displayName` always takes precedence and is the value that will be stored and returned in future SCIM queries.
Resources [#resources]
* **[Zitadel SCIM API Documentation](/apis/scim2)**: Documentation of Zitadel's SCIM API implementation.
* **[SCIM](https://scim.cloud/)**: The Webpage of SCIM.
* **[RFC7643](https://tools.ietf.org/html/rfc7643) Core Schema**:
The Core Schema provides a platform-neutral schema and extension model for representing users and groups.
* **[RFC7644](https://tools.ietf.org/html/rfc7644) Protocol**:
The SCIM Protocol is an application-level, REST protocol for provisioning and managing identity data on the web.
* **[RFC7642](https://tools.ietf.org/html/rfc7642) Definitions, Overview, Concepts, and Requirements**:
This document lists the user scenarios and use cases of System for Cross-domain Identity Management (SCIM).
# Migrate from Auth0 to ZITADEL with Password Hashes
1\. Introduction [#1-introduction]
This guide will walk you through the steps to migrate users from Auth0 to ZITADEL, including password hashes (which requires Auth0's support assistance), so users don't need to reset their passwords.
**What you'll learn with this guide**
* How to prepare your data from Auth0
* Use of the ZITADEL migration tooling
* Performing the user import via ZITADEL's API
* Troubleshooting and validating the migration
***
2\. Prerequisites [#2-prerequisites]
2.1. Install Go [#2-1-install-go]
The migration tool is written in Go. Download and install the latest version of Go from the [official Go website](https://go.dev/doc/install).
2.2. Create a ZITADEL Instance and Organization [#2-2-create-a-zitadel-instance-and-organization]
You'll need a target organization in ZITADEL to import your users. You can create a new organization or use an existing one.
If you don't have a ZITADEL instance, you can [sign up for free here](https://zitadel.com) to create a new one for you.
See: [Managing Organizations in ZITADEL](https://zitadel.com/docs/guides/manage/console/organizations-overview).
> **Note:** Copy your Organization ID since you will use the id in the later steps.
***
3\. Preparing Auth0 Data [#3-preparing-auth-0-data]
3.1. Export hashed passwords [#3-1-export-hashed-passwords]
Auth0 does not export hashed passwords as part of the bulk user export.
You must create a support ticket to download password hashes and password-related information.
Please also refer to the Auth0 guide on how to [Export Data](https://auth0.com/docs/troubleshoot/customer-support/manage-subscriptions/export-data#user-passwords).
You can also import users into ZITADEL with an verified email but without the passwords.
Users will be prompted to create a new password after they login for the first time after migration.
1. Go to [https://support.auth0.com/tickets](https://support.auth0.com/tickets) and click on **Open Ticket**
2. Issue Type: **I have a question regarding my Auth0 account**
3. What can we help you with?: **I would like to obtain an export of my tenant password hashes**
4. Fill out the form: Request password hashes as bcrypt
5. **Submit ticket**
You will receive a JSON file including the password hashes.
See this [community post](https://community.auth0.com/t/password-hashes-export-data-format/58730) for more information about the contents and format.
Reference: [Export hashed passwords from Auth0](https://zitadel.com/docs/guides/migrate/sources/auth0#export-hashed-passwords)
3.2. Export all user data [#3-2-export-all-user-data]
Create a [bulk user export](https://auth0.com/docs/manage-users/user-migration/bulk-user-exports) from the Auth0 Management API, or use the [User Import/Export extension](https://auth0.com/docs/manage-users/user-migration/user-import-export-extension).
You will receive a newline-delimited JSON with the requested user data.
This is an example request, we have included the user id, the email and the name of the user, but you can also export all the available user profile attributes. Make sure to export the users in a json format.
```bash
curl --request POST \
--url $AUTH0_DOMAIN/api/v2/jobs/users-exports \
--header 'authorization: Bearer $TOKEN' \
--header 'content-type: application/json' \
--data '{
"connection_id": "$CONNECTION_ID",
"format": "json",
"fields": [
{"name": "user_id"},
{"name": "email"},
{"name": "name"},
]
}'
```
***
4\. Running the ZITADEL Migration Tool [#4-running-the-zitadel-migration-tool]
We have developed a tool that combines your exported user data with their corresponding passwords to generate the import request body for ZITADEL.
4.1. Install the Migration Tool [#4-1-install-the-migration-tool]
Follow the installation instructions to set up the ZITADEL migration tool from [ZITADEL Tools](https://github.com/zitadel/zitadel-tools?tab=readme-ov-file#installation).
4.2. Generate Import JSON [#4-2-generate-import-json]
Use the migration tool to convert the Auth0 export file to a ZITADEL-compatible JSON.
Step-by-step instructions can be found here: [Migration Tool for Auth0](https://github.com/zitadel/zitadel-tools/blob/main/cmd/migration/auth0/readme.md)
Typical steps:
* Run the migration tool with your exported Auth0 files as input.
* The tool generates a JSON file ready for import into ZITADEL.
Example:
After obtaining the 2 required input files (passwords and profile) in JSON lines format, you can run the following command:
Sample `passwords.ndjson` content, as obtained from the Auth0 Support team:
```json
{"_id":{"$oid":"emxdpVxozXeFb1HeEn5ThAK8"},"email_verified":true,"email":"tommie_krajcik85@hotmail.com","passwordHash":"$2b$10$d.GvZhGwTllA7OdAmsA75uGGzqr/mhdQoU88M3zD.fX3Vb8Rcf33.","password_set_date":{"$date":"2025-06-30T00:00:00.000Z"},"tenant":"test","connection":"Username-Password-Authentication","_tmp_is_unique":true}
```
Sample `profiles.json` content, as obtained from the Auth0 Management API:
```json
{"user_id":"auth0|emxdpVxozXeFb1HeEn5ThAK8","email_verified":true,"name":"Tommie Krajcik","email":"tommie_krajcik85@hotmail.com"}
```
Run the following command in your terminal (replace ORG\_ID with your own organization ID):
```bash
zitadel-tools migrate auth0 --org= --users=./profiles.json --passwords=./passwords.ndjson --multiline --email-verified --output=./importBody.json --timeout=5m0s
```
The tool will merge both objects into a single one in the importBody.json output, this will be used in the next step to complete the import process.
***
5\. Importing Users into ZITADEL [#5-importing-users-into-zitadel]
5.1. Obtain Access Token (or PAT) for API Access [#5-1-obtain-access-token-or-pat-for-api-access]
To call the ZITADEL Admin API, you need to authenticate using a **Service Account** with the `IAM_OWNER` Manager permissions.
There are two recommended authentication methods:
* **Client Credentials Flow**
[Learn how to authenticate with client credentials.](https://zitadel.com/docs/guides/integrate/service-accounts/client-credentials)
* **Personal Access Token (PAT)**
[Learn how to create and use a PAT.](https://zitadel.com/docs/guides/integrate/service-accounts/authenticate-service-accounts#personal-access-token)
**Reference:** [Service Accounts & API Authentication](https://zitadel.com/docs/guides/integrate/service-accounts/authenticate-service-accounts#authentication-methods)
5.2. Import Data with the ZITADEL API [#5-2-import-data-with-the-zitadel-api]
* Use your **access token** or **PAT** to authorize the request.
* Call the [Admin API – Import Data](/reference/api/admin/zitadel.admin.v1.AdminService.ImportData) endpoint, passing your generated JSON file.
* Verify that the users were imported successfully in the ZITADEL console.
**Import Endpoint:**
* `POST /admin/v1/import`
* `Authorization: Bearer `
* **Body:** Generated in step 4.2
Example cURL request [#example-c-url-request]
```bash
curl --location 'https://${CUSTOM_DOMAIN}/admin/v1/import' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer ' \
--data-raw '{
"dataOrgs": {
"orgs": [
{
"orgId": "",
"humanUsers": [
{
"userId": "auth0|emxdpVxozXeFb1HeEn5ThAK8",
"user": {
"userName": "tommie_krajcik85@hotmail.com",
"profile": {
"firstName": "Tommie Krajcik",
"lastName": "Tommie Krajcik"
},
"email": {
"email": "tommie_krajcik85@hotmail.com",
"isEmailVerified": true
},
"hashedPassword": {
"value": "$2b$10$d.GvZhGwTllA7OdAmsA75uGGzqr/mhdQoU88M3zD.fX3Vb8Rcf33."
}
}
}
]
}
]
},
"timeout": "5m0s"
}'
```
***
6\. Testing the Migration [#6-testing-the-migration]
6.1. Test User Login [#6-1-test-user-login]
Use the **ZITADEL login page** or your integrated app to test logging in with one of the imported users.
> **Password for the sample user:** `Password1!`
Confirm that the migrated password works as expected.
6.2. Troubleshooting [#6-2-troubleshooting]
**Common issues:**
* Missing password hashes
* Malformed JSON
* Invalid or incomplete user data
The import endpoint returns an `errors` array which can help you identify any issues with the import.
Where to check logs and get help [#where-to-check-logs-and-get-help]
You can also verify that a user was imported by calling the **events endpoint** and checking for the following event type:
```json
"user.human.added"
```
***
7\. Q&A and Further Resources [#7-q-a-and-further-resources]
**Q:** What is the maximum number of users that can be imported in a single batch?
**A:** There is no hard limit on the number of users. However, there is a **timeout**.
For **ZITADEL Cloud deployments**, the timeout is **5 minutes**, which typically allows for importing around **5,000 users per batch**.
# Migrate from Keycloak
1\. Introduction [#1-introduction]
This guide will walk you through the steps to migrate users from **Keycloak** to **ZITADEL**, including password hashes, using the `zitadel-tools` CLI and the user import APIs.
**What you'll learn with this guide**
* How to export users from Keycloak
* Use of the ZITADEL migration tooling
* Performing the user import via ZITADEL's API
* Troubleshooting and validating the migration
***
2\. Prerequisites [#2-prerequisites]
2.1. Install Go [#2-1-install-go]
The migration tool is written in Go. Download and install the latest version of Go from the [official Go website](https://go.dev/doc/install).
2.2. Create a ZITADEL Instance and Organization [#2-2-create-a-zitadel-instance-and-organization]
You'll need a target organization in ZITADEL to import your users. You can create a new organization or use an existing one.
If you don't have a ZITADEL instance, you can [sign up for free here](https://zitadel.com) to create a new one for you.
See: [Managing Organizations in ZITADEL](https://zitadel.com/docs/guides/manage/console/organizations-overview).
> **Note:** Copy your Organization ID since you will use the id in the later steps.
***
3\. Exporting User Data from Keycloak [#3-exporting-user-data-from-keycloak]
3.1. Set up Keycloak Locally (Optional) [#3-1-set-up-keycloak-locally-optional]
To run a local development Keycloak instance, use the official Docker image:
```bash
docker run -d -p 8081:8080 \
-e KEYCLOAK_ADMIN=admin \
-e KEYCLOAK_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak:22.0.1 start-dev
```
3.2. Export Users from Keycloak [#3-2-export-users-from-keycloak]
Run the following command inside the Keycloak container to export your realm and users:
```bash
docker exec \
/opt/keycloak/bin/kc.sh export \
--dir /tmp/export \
--realm \
--users realm_file
```
Then copy the exported file to your host machine:
```bash
docker cp :/tmp/export/-realm.json .
```
This creates a file such as:
```
-realm.json
```
***
4\. Running the ZITADEL Migration Tool [#4-running-the-zitadel-migration-tool]
4.1. Install the Migration Tool [#4-1-install-the-migration-tool]
Follow the installation instructions to set up the ZITADEL migration tool from [ZITADEL Tools](https://github.com/zitadel/zitadel-tools?tab=readme-ov-file#installation).
4.2. Generate Import JSON [#4-2-generate-import-json]
Use the migration tool to convert the Keycloak realm export into a ZITADEL-compatible JSON file:
```bash
zitadel-tools migrate keycloak \
--org= \
--realm=-realm.json \
--output=./importBody.json \
--timeout=5m0s \
--multiline
```
The tool will generate `importBody.json`, which is ready for importing into ZITADEL.
***
5\. Importing Users into ZITADEL [#5-importing-users-into-zitadel]
5.1. Obtain Access Token (or PAT) for API Access [#5-1-obtain-access-token-or-pat-for-api-access]
To call the ZITADEL Management API, you need to authenticate using a **Service Account** with the `IAM_OWNER` Manager permissions.
There are two recommended authentication methods:
* **Client Credentials Flow**
[Learn how to authenticate with client credentials.](https://zitadel.com/docs/guides/integrate/service-accounts/client-credentials)
* **Personal Access Token (PAT)**
[Learn how to create and use a PAT.](https://zitadel.com/docs/guides/integrate/service-accounts/personal-access-token)
**Reference:** [Service Accounts & API Authentication](https://zitadel.com/docs/guides/integrate/service-accounts/authenticate-service-accounts#authentication-methods)
***
5.2. Import Data with the ZITADEL API [#5-2-import-data-with-the-zitadel-api]
Use your **access token** or **PAT** to authenticate, then call the [Management API – User Import](/reference/api/management/zitadel.management.v1.ManagementService.ImportHumanUser) endpoint.
**Import Endpoint:**
* `POST /admin/v1/import`
* `Authorization: Bearer `
* **Body:** Generated in step 4.2
Example cURL request [#example-c-url-request]
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/admin/v1/import \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ' \
--data @importBody.json
```
Successful Response:
```bash
{
"success": {
"orgs": [
{
"orgId": "318900732864567390",
"humanUserIds": [
"da72ac13-6994-4498-8b27-3ff9555661b2",
"4e987a01-34db-4393-b61c-1ce753baf69c",
"1041d710-8a89-48f8-85b5-1ab9656190f3",
"7b23b799-4f0f-4964-bc6d-95c534787d2c",
"6f2f1b2f-b292-4431-932b-620124e065ec",
"2c65045a-9de8-4d28-b686-b27bf3a70fc3",
"aca2dd3e-689c-4ab6-b446-0990127b1e0d",
"18a23e01-f0fe-443f-9f1c-2a8135cd22c2",
"c49af4bf-0dbb-4994-b453-b8dd0d5006ea"
]
}
]
},
"errors": [
{
"type": "org",
"id": "318900732864567390",
"message": "ID=ORG-lapo2m Message=Errors.Org.AlreadyExisting"
}
]
}
```
ℹ️ Note: The above response indicates that the organization already existed, and users were successfully added. This is not an error, and you can consider the import successful as long as the HTTP status code is **200**.
***
6\. Testing the Migration [#6-testing-the-migration]
6.1. Test User Login [#6-1-test-user-login]
Use the **ZITADEL login page** or your integrated app to test logging in with one of the imported users.
Confirm that the migrated password works as expected.
***
6.2. Troubleshooting [#6-2-troubleshooting]
**Common issues:**
* Invalid Keycloak export format
* Malformed JSON
* Missing `orgId` or access token
* Timeout exceeded during import
The import API returns a detailed response with any errors encountered during the process.
Where to check logs and get help [#where-to-check-logs-and-get-help]
You can verify that users were imported successfully by querying the **events API** and looking for the `user.human.added` event type.
Use the following request:
```bash
curl --location 'https://${CUSTOM_DOMAIN}/admin/v1/events/_search' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"asc": true,
"limit": 1000,
"event_types": [
"user.human.added"
]
}'
```
This will return a list of user creation events including details such as email, username, and hashed password to help you confirm the imported data.
Successful Response
```bash
{
"events": [
{
"type": {
"type": "user.human.added",
"localized": {
"key": "EventTypes.user.human.added",
"localizedMessage": "Person added"
}
},
"payload": {
"displayName": "test user",
"email": "testuser@gmail.com",
"userName": "testuser"
},
"aggregate": {
"id": "da72ac13-6994-4498-8b27-3ff9555661b2",
"resourceOwner": "318900732864567390"
},
"creationDate": "2025-07-22T15:16:06.364302Z"
}
]
}
```
ℹ️ Note: If you see entries with "type": "user.human.added" and correct payload data, the import was successful.
***
7\. Q&A and Further Resources [#7-q-a-and-further-resources]
Real-World Scenarios & Common Questions [#real-world-scenarios-common-questions]
**Q:** What is the maximum number of users that can be imported in a single batch?
**A:** There is no hard limit on the number of users. However, there is a **timeout**.
For **ZITADEL Cloud deployments**, the timeout is **5 minutes**, which typically allows for importing around **5,000 users per batch**.
***
# Migrate from ZITADEL
This guide explains how to migrate from ZITADEL, this includes
* ZITADEL Cloud to self-hosted
* ZITADEL self-hosted to ZITADEL Cloud
* ZITADEL v1 (deprecated) to ZITADEL v2.x
Considerations [#considerations]
The following scripts don't include:
* Global policies
* Instance members
* Global IDPs
* Global second factor / multi-factors
* Machine keys
* Personal Access Tokens
* Application keys
* Passkey authentication
Which results in that if you want to import, and you have no defined organization-specific custom policies, the experience for your users will not be exactly like in your old instance.
Note that the resources will be migrated without the event stream. This means that you will not have the audit trail for the imported objects.
Authorization [#authorization]
You need a PAT from a service account with instance Owner permissions in both the source and target system.
Source system [#source-system]
1. Go to your default organization
2. Create a service account "import\_user" with Access Token Type "Bearer"
3. Create a [personal access token](/guides/integrate/service-accounts/personal-access-token)
4. Go to the Default settings
5. Add the import\_user as [manager](/guides/manage/console/administrators) with the role "instance Owner"
Save the PAT to the environment variable `PAT_EXPORT_TOKEN` and the source domain as `ZITADEL_EXPORT_DOMAIN` to run the following scripts.
Target system [#target-system]
1. Go to your default organization
2. Create a service account "export\_user" with Access Token Type "Bearer"
3. Create a [personal access token](/guides/integrate/service-accounts/personal-access-token)
4. Go to the Default settings
5. Add the export\_user as [manager](/guides/manage/console/administrators) with the role "instance Owner"
Save the PAT to the environment variable `PAT_IMPORT_TOKEN` and the source domain as `ZITADEL_IMPORT_DOMAIN` to run the following scripts.
You should let the PAT expire as soon as possible.
Make sure to delete the created users after you are done with the migration.
Use file [#use-file]
Export to file [#export-to-file]
To export all necessary data, you only have to use one request, as an example:
```bash
curl --request POST \
--url $ZITADEL_EXPORT_DOMAIN/admin/v1/export \
--header "Authorization: Bearer $PAT_EXPORT_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"org_ids": [ ],
"excluded_org_ids": [ ],
"with_passwords": true,
"with_otp": true,
"timeout": "30s",
"response_output": true
}' -o export.json
```
| Field | Type | Description |
| ------------------ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| org\_ids | list of strings | provide a list of Organization IDs to select which organizations should be exported (eg, `[ "70669144072186707", "70671105999825752" ]`); leave empty to export all |
| excluded\_org\_ids | list of strings | to exclude several organization, if for example no organizations are selected |
| with\_passwords | bool | to include the hashed\_passwords of the users in the export |
| with\_otp | bool | to include the OTP-code of the users in the export |
| timeout | duration string | timeout of the call to export the data |
| response\_output | bool | to output the export as response to the call |
Import from file [#import-from-file]
To import the exported data into you new instance, you have to have an already existing instance on a ZITADEL, with all desired settings and global resources.
Then as an example you can use one request for the import:
```bash
curl --request POST \
--url $ZITADEL_IMPORT_DOMAIN/admin/v1/import \
--header "Authorization: Bearer $PAT_IMPORT_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"timeout": "10m",
"data_orgsv1": '"$(cat export.json)"'
}'
```
| Field | Type | Description |
| ------------ | --------------- | --------------------------------------- |
| timeout | duration string | timeout of the call to import the data |
| data\_orgsv1 | string | data which was exported from ZITADEL V1 |
Use Google Cloud Storage [#use-google-cloud-storage]
Export to GCS [#export-to-gcs]
To use this requests you have to have an access token with enough permissions to export and import.
The used serviceaccount has to have at least the role "Storage Object Creator" to create objects on GCS
To export all necessary data you only have to use one request which results in a file in your GCS, as an example:
```bash
curl --request POST \
--url $ZITADEL_EXPORT_DOMAIN/admin/v1/export \
--header "Authorization: Bearer $PAT_EXPORT_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"org_ids": [ ],
"excluded_org_ids": [ ],
"with_passwords": true,
"with_otp": true,
"timeout": "30s",
"gcs_output": {
"path": "export.json",
"bucket": "caos-zitadel-exports",
"serviceaccount_json": "XXXX"
}
}' -o export.json
```
| Field | Type | Description |
| ------------------ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| org\_ids | list of strings | provide a list of Organization IDs to select which organizations should be exported (eg, `[ "70669144072186707", "70671105999825752" ]`); leave empty to export all |
| excluded\_org\_ids | list of strings | to exclude several organization, if for example no organizations are selected |
| with\_passwords | bool | to include the hashed\_passwords of the users in the export |
| with\_otp | bool | to include the OTP-code of the users in the export |
| timeout | duration string | timeout of the call to export the data |
| gcs\_output | object(data\_orgsv1\_gcs) | to write a file into GCS as output to the call |
data\_orgsv1\_gcs object:
| Field | Type | Description |
| -------------------- | ------ | ----------------------------------------------------------------- |
| path | string | path to the output file on GCS |
| bucket | string | used bucket for output on GCS |
| serviceaccount\_json | string | base64-encoded serviceaccount.json used to output the file on GCS |
Import to GCS [#import-to-gcs]
To import the exported data into you new instance, you have to have an already existing instance on a ZITADEL, with all desired settings and global resources.
The used serviceaccount has to have at least the role "Storage Object Viewer" to read objects from GCS
Then as an example, you can use one request for the import:
```bash
curl --request POST \
--url $ZITADEL_IMPORT_DOMAIN/admin/v1/import \
--header "Authorization: Bearer $PAT_IMPORT_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"timeout": "10m",
"data_orgsv1_gcs": {
"path": "export.json",
"bucket": "caos-zitadel-exports",
"serviceaccount_json": "XXXX"
}
}'
```
| Field | Type | Description |
| ----------------- | ------------------------- | -------------------------------------- |
| timeout | duration string | timeout of the call to import the data |
| data\_orgsv1\_gcs | object(data\_orgsv1\_gcs) | to read the export from GCS directly |
data\_orgsv1\_gcs object:
| Field | Type | Description |
| -------------------- | ------ | ----------------------------------------------------------------- |
| path | string | path to the exported file on GCS |
| bucket | string | used bucket to read from GCS |
| serviceaccount\_json | string | base64-encoded serviceaccount.json used to read the file from GCS |
# Use Cases
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
# Application API
API Reference for Application
> **Terminology Update:** We have streamlined our naming conventions to improve clarity. The term **Application** has been replaced with **Application**. To avoid breaking changes the APIs still make use of the old term. Both terms refer to the same underlying functionality.
# Create Application
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create an application. The application can be OIDC, API or SAML type, based on the input.
Required permissions:
* project.app.write
# Create Application Key
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a new application key, which is used to authorize an API application.
Key details are returned in the response. They must be stored safely, as it will not
be possible to retrieve them again.
Required permissions:
* `project.app.write`
# Deactivate Application
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deactivates the application belonging to the input project and matching the provided
application ID.
Required permissions:
* project.app.write
# Delete Application
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deletes the application belonging to the input project and matching the provided
application ID.
Required permissions:
* project.app.delete
# Delete Application Key
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deletes an application key matching the provided ID.
Organization ID is not mandatory, but helps with filtering/performance.
The deletion time is returned in response message.
Required permissions:
* `project.app.write`
# Generate Client Secret
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Generates the client secret of an API or OIDC application that belongs to the input project.
Required permissions:
* project.app.write
# Get Application
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieves the application matching the provided ID.
Required permissions:
* project.app.read
# Get Application Key
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieves the application key matching the provided ID.
Specifying a project, organization and application ID is optional but help with filtering/performance.
Required permissions:
* project.app.read
# List Application Keys
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns a list of application keys matching the input parameters.
The result can be sorted by id, aggregate, creation date, expiration date, resource owner or type.
It can also be filtered by application, project or organization ID.
Required permissions:
* project.app.read
# List Applications
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns a list of applications matching the input parameters. The results can be filtered
by project, state, type and name. It can be sorted by id, name, creation date, change date or state.
Required permissions:
* project.app.read
# Reactivate Application
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Reactivates the application belonging to the input project and matching the provided
application ID.
Required permissions:
* project.app.write
# Update Application
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Changes the configuration of an OIDC, API or SAML type application, as well as
the application name, based on the input provided.
Required permissions:
* project.app.write
# Action API
API Reference for Action
> **Terminology Update:** We have streamlined our naming conventions to improve clarity. The term **Action** has been replaced with **Action**. To avoid breaking changes the APIs still make use of the old term. Both terms refer to the same underlying functionality.
# Activate Public Key
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Activates the public key for payload encryption.
The public key is used to encrypt the payload sent to the target when the payload type is set to `PAYLOAD_TYPE_JWE`.
Activating a new key will deactivate the current active key. Only one key can be active at a time.
The active key is indicated in the `kid` header in the JWE token sent to the target.
Activating a key that is already active is a no-op.
Required permission:
* `action.target.write`
# Add Public Key
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Adds a public key to the target for payload encryption.
The public key is used to encrypt the payload sent to the target when the payload type is set to `PAYLOAD_TYPE_JWE`.
The public key must be in PEM format and be either an RSA or an EC key.
On a successful addition, a key ID is returned which can not only be used to manage the key (activate, remove),
but also will be used as the `kid` header in the JWE token sent to the target to indicate which key was used for encryption.
Note that newly added keys are inactive by default. You must activate the key to use it for payload encryption.
Providing an optional expiration date allows you to set a validity period for the key.
After the expiration date, the key will be automatically deactivated and no longer used for payload encryption.
Be sure to activate a new key before the current active key expires to avoid interruptions in your target executions.
You can have multiple inactive keys for rotation purposes, but only one active key at a time.
Required permission:
* `action.target.write`
# Create Target
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a new target to your endpoint, which can be used in executions.
Required permission:
* `action.target.write`
# Deactivate Public Key
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deactivates the public key for payload encryption.
The public key will no longer be used to encrypt payloads sent to the target.
Be aware that deactivating the active key will leave the target without an active key.
Subsequent calls to the target with payload type `PAYLOAD_TYPE_JWE` will fail until a new key is activated.
This endpoint can be used in break glass scenarios to quickly disable a compromised key.
Deactivating a key that is already inactive is a no-op.
Required permission:
* `action.target.write`
# Delete Target
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Delete an existing target. This will remove it from any configured execution as well.
In case the target is not found, the request will return a successful response as
the desired state is already achieved.
Required permission:
* `action.target.delete`
# Get Target
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns the target identified by the requested ID.
Required permission:
* `action.target.read`
# List Execution Functions
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List all available functions which can be used as condition for executions.
# List Execution Methods
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List all available methods which can be used as condition for executions.
# List Execution Services
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List all available services which can be used as condition for executions.
# List Executions
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List all matching executions. By default all executions of the instance are returned that have at least one execution target.
Make sure to include a limit and sorting for pagination.
Required permission:
* `action.execution.read`
# List Public Keys
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists all public keys of a target.
The response includes which key is active and the key's expiration dates.
This allows you to manage key rotations and ensure that your target always has an active key for payload encryption.
Required permission:
* `action.target.read`
# List targets
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List all matching targets. By default all targets of the instance are returned.
Make sure to include a limit and sorting for pagination.
Required permission:
* `action.target.read`
# Remove Public Key
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Removes the public key from the target. This is a permanent action and can not be undone.
Note that you can only remove inactive keys. Attempting to remove an active key will result in an error.
For break glass scenarios, deactivate the key first and then remove it.
Removing a non-existing key is a no-op.
Required permission:
* `action.target.write`
# Set Execution
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Sets an execution to call a target or include the targets of another execution.
Setting an empty list of targets will remove all targets from the execution, making it a noop.
Required permission:
* `action.execution.write`
# Update Target
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Update an existing target.
To generate a new signing key set the optional expirationSigningKey.
Required permission:
* `action.target.write`
# Role Assignment API
API Reference for Role Assignment
> **Terminology Update:** We have streamlined our naming conventions to improve clarity. The term **Authorization** has been replaced with **Role Assignment**. To avoid breaking changes the APIs still make use of the old term. Both terms refer to the same underlying functionality.
# Activate Role Assignment
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Note: Authorization in this context refers to role assignments, not to OAuth authorization.
ActivateAuthorization activates an existing but inactive authorization.
In case the authorization is already active, the request will return a successful response as
the desired state is already achieved.
You can check the change date in the response to verify if the authorization was activated by the request.
Required permissions:
* "user.grant.write"
# Create Role Assignment
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Note: Authorization in this context refers to role assignments, not to OAuth authorization.
CreateAuthorization creates a new authorization for a user in an owned or granted project.
Required permissions:
* "user.grant.write"
# Deactivate Role Assignment
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
DeactivateAuthorization deactivates an existing and active authorization.
Note: Authorization in this context refers to role assignments, not to OAuth authorization.
In case the authorization is already inactive, the request will return a successful response as
the desired state is already achieved.
You can check the change date in the response to verify if the authorization was deactivated by the request.
Required permissions:
* "user.grant.write"
# Delete Role Assignment
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Note: Authorization in this context refers to role assignments, not to OAuth authorization.
DeleteAuthorization deletes the authorization.
In case the authorization is not found, the request will return a successful response as
the desired state is already achieved.
You can check the deletion date in the response to verify if the authorization was deleted by the request.
Required permissions:
* "user.grant.delete"
# List Role Assignment
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Note: Authorization in this context refers to role assignments, not to OAuth authorization.
ListAuthorizations returns all authorizations matching the request and necessary permissions.
Required permissions:
* "user.grant.read"
* no permissions required for listing own authorizations
# Update Role Assignments
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Note: Authorization in this context refers to role assignments, not to OAuth authorization.
UpdateAuthorization updates the authorization.
Note that any role keys previously granted to the user and not present in the request will be revoked.
Required permissions:
* "user.grant.write"
# Group API
API Reference for Group
> **Terminology Update:** We have streamlined our naming conventions to improve clarity. The term **Group** has been replaced with **Group**. To avoid breaking changes the APIs still make use of the old term. Both terms refer to the same underlying functionality.
# Add Users
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Adds one or more users to a group.
The users should be in the same organization as the group.
The request will not fail if some of the users could not be added to the group,
and a list of failed user IDs is available in the response.
Required permissions:
* group.user.write
# Create Group
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
CreateGroup creates a new user group in an organization.
Required permissions:
* group.create
# Delete Group
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
DeleteGroup deletes the group.
In case the group is not found, the request will return a successful response as
the desired state is already achieved.
You can check the deletion date in the response to verify if the group was deleted by the request.
Required permissions:
* group.delete
# Get Group
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieves a group based on its ID.
Required permission:
* group.read
# List Group Users
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Allows searching for groups based on user IDs, or retrieving users based on group IDs
Required permissions:
* group.user.read
# List Groups
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
ListGroups returns all groups matching the request and necessary permissions from an organization.
Required permissions:
* group.read
# Remove Users
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Removes one or more users from a group.
If some of the users are not in the group, the request will still return a successful response as
the desired state is already achieved.
Required permissions:
* group.user.delete
# Update Group
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
UpdateGroup updates the user group.
In case there aren't any changes, the request will return a successful response as
the desired state is already achieved.
You can check the change date in the response to verify if the group was updated by the request.
Required permissions:
* group.write
# Feature API
API Reference for Feature
> **Terminology Update:** We have streamlined our naming conventions to improve clarity. The term **Feature** has been replaced with **Feature**. To avoid breaking changes the APIs still make use of the old term. Both terms refer to the same underlying functionality.
# Get Instance Features
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns all configured features for an instance. Unset fields mean the feature is the current system default.
Required permissions:
* none
# Get Organization Features
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns all configured features for an organization. Unset fields mean the feature is the current instance default.
Required permissions:
* org.feature.read
* no permission required for the organization the user belongs to
# Get System Features
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns all configured features for the system. Unset fields mean the feature is the current system default.
Required permissions:
* none
# Get User Features
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns all configured features for a user. Unset fields mean the feature is the current organization default.
Required permissions:
* user.feature.read
* no permission required for the own user
# Reset Instance Features
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deletes ALL configured features for an instance, reverting the behaviors to system defaults.
Required permissions:
* iam.feature.delete
# Reset Organization Features
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deletes ALL configured features for an organization, reverting the behaviors to instance defaults.
Required permissions:
* org.feature.delete
# Reset System Features
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deletes ALL configured features for the system, reverting the behaviors to system defaults.
Required permissions:
* system.feature.delete
# Reset User Features
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deletes ALL configured features for a user, reverting the behaviors to organization defaults.
Required permissions:
* user.feature.delete
# Set Instance Features
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Configure and set features that apply to a complete instance. Only fields present in the request are set or unset.
Required permissions:
* iam.feature.write
# Set Organization Features
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Configure and set features that apply to a complete instance. Only fields present in the request are set or unset.
Required permissions:
* org.feature.write
# Set System Features
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Configure and set features that apply to the complete system. Only fields present in the request are set or unset.
Required permissions:
* system.feature.write
# Set User Features
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Configure and set features that apply to an user. Only fields present in the request are set or unset.
Required permissions:
* user.feature.write
# Idp API
API Reference for Idp
> **Terminology Update:** We have streamlined our naming conventions to improve clarity. The term **Idp** has been replaced with **Idp**. To avoid breaking changes the APIs still make use of the old term. Both terms refer to the same underlying functionality.
# Get identity provider (IdP) by ID
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns an identity provider (social/enterprise login) by its ID, which can be of the type Google, AzureAD, etc.
# Auth API
API Reference for Auth
> **Terminology Update:** We have streamlined our naming conventions to improve clarity. The term **Auth** has been replaced with **Auth**. To avoid breaking changes the APIs still make use of the old term. Both terms refer to the same underlying functionality.
# AddMyAuthFactorOTP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddMyAuthFactorOTPEmail
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddMyAuthFactorOTPSMS
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddMyAuthFactorU2F
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddMyPasswordless
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddMyPasswordlessLink
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetMyEmail
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetMyLabelPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetMyLoginPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetMyMetadata
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetMyPasswordComplexityPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetMyPhone
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetMyPrivacyPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetMyProfile
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetMyUser
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetSupportedLanguages
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Healthz
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListMyAuthFactors
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListMyLinkedIDPs
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListMyMemberships
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListMyMetadata
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListMyPasswordless
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListMyProjectOrgs
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# List My Project Roles
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: [List authorizations](/reference/api/authorization/zitadel.authorization.v2.AuthorizationService.ListAuthorizations) and pass the user ID filter with your users ID and the project ID filter to search for your authorizations on a granted and an owned project.
Returns a list of roles for the authenticated user and for the requesting project.
# ListMyRefreshTokens
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListMyUserChanges
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# List My Authorizations / Role Assignments
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: [List authorizations](/reference/api/authorization/zitadel.authorization.v2.AuthorizationService.ListAuthorizations) and pass the user ID filter with your users ID to search for your authorizations on granted and owned projects.
Returns a list of the authorizations/role assignments the authenticated user has. Role assignments consist of an organization, a project and 1-n roles.
Note: Authorization in this context refers to role assignments, not to OAuth authorization.
# ListMyUserSessions
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListMyZitadelPermissions
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RemoveMyAuthFactorOTP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RemoveMyAuthFactorOTPEmail
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RemoveMyAuthFactorOTPSMS
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RemoveMyAuthFactorU2F
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RemoveMyAvatar
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RemoveMyLinkedIDP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RemoveMyPasswordless
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RemoveMyPhone
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RemoveMyUser
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResendMyEmailVerification
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResendMyPhoneVerification
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Resends an sms to the last given phone number, to verify it
# RevokeAllMyRefreshTokens
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RevokeMyRefreshToken
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SendMyPasswordlessLink
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetMyEmail
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetMyPhone
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateMyPassword
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateMyProfile
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateMyUserName
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# VerifyMyAuthFactorOTP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# VerifyMyAuthFactorU2F
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# VerifyMyEmail
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# VerifyMyPasswordless
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# VerifyMyPhone
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Internal_permission API
API Reference for Internal\_permission
> **Terminology Update:** We have streamlined our naming conventions to improve clarity. The term **Internal\_permission** has been replaced with **Internal\_permission**. To avoid breaking changes the APIs still make use of the old term. Both terms refer to the same underlying functionality.
# Create Administrator
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
CreateAdministrator grants an administrator role to a user for a specific resource.
Note that the roles are specific to the resource type.
This means that if you want to grant a user the administrator role for an organization and a project,
you need to create two administrator roles.
Required permissions depend on the resource type:
* "iam.member.write" for instance administrators
* "org.member.write" for organization administrators
* "project.member.write" for project administrators
* "project.grant.member.write" for project grant administrators
# Delete Administrator
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
DeleteAdministrator revokes an administrator role from a user.
In case the administrator role is not found, the request will return a successful response as
the desired state is already achieved.
You can check the deletion date in the response to verify if the administrator role was deleted during the request.
Required permissions depend on the resource type:
* "iam.member.delete" for instance administrators
* "org.member.delete" for organization administrators
* "project.member.delete" for project administrators
* "project.grant.member.delete" for project grant administrators
# List Administrators
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
ListAdministrators returns all administrators and their roles matching the request and the caller's permissions to retrieve.
Required permissions depend on the resource type:
* "iam.member.read" for instance administrators
* "org.member.read" for organization administrators
* "project.member.read" for project administrators
* "project.grant.member.read" for project grant administrators
* no permissions required for listing own administrator roles
# Update Administrator
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
UpdateAdministrator updates the specific administrator role.
Note that any role previously granted to the user and not present in the request will be revoked.
Required permissions depend on the resource type:
* "iam.member.write" for instance administrators
* "org.member.write" for organization administrators
* "project.member.write" for project administrators
* "project.grant.member.write" for project grant administrators
# Instance API
API Reference for Instance
> **Terminology Update:** We have streamlined our naming conventions to improve clarity. The term **Instance** has been replaced with **Instance**. To avoid breaking changes the APIs still make use of the old term. Both terms refer to the same underlying functionality.
# Add Custom Domain
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Adds a Custom Domain to the instance.
The Custom Domain must be unique across all instances.
Once the domain is added, it will be used to route requests to this instance.
This method requires system level permissions and cannot be called from an instance context.
Required permissions:
* `system.domain.write`
# Add Trusted Domain
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Adds a Trusted Domain to the instance.
By default the instance will be determined by the context of the request,
e.g. the host header.
You can optionally pass an InstanceID to list the domains of a specific instance.
This requires additional permissions.
It must be a valid domain name.
Once the domain is added, it can be used in API responses like OIDC discovery,
email templates, and more.
This can be used in cases where the API is accessed through a different domain
than the Custom Domain, e.g. proxy setups and custom login UIs.
Unlike Custom Domains, Trusted Domains are not used to route requests to this instance
and therefore do not need to be uniquely assigned to an instance.
Required permissions:
* `iam.write`
* `system.instance.write` (if InstanceID is set)
# Delete Instance
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deletes an instance with the given ID.
This method requires system level permissions and cannot be called from an instance context.
Required permissions:
* `system.instance.delete`
# Get Instance
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns the instance in the current context or by its ID.
By default the instance will be determined by the context of the request,
e.g. the host header.
You can optionally pass an InstanceID to retrieve a specific instance.
This requires additional permissions.
Required permissions:
* `iam.read`
* `system.instance.read` (if InstanceID is set)
# List Custom Domains
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists Custom Domains of the instance.
By default the instance will be determined by the context of the request,
e.g. the host header.
You can optionally pass an InstanceID to list the domains of a specific instance.
This requires additional permissions.
Required permissions:
* `iam.read`
* `system.instance.read` (if InstanceID is set)
# List Instances
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists instances matching the given query.
The query can be used to filter either by instance ID or domain.
The request is paginated and returns 100 results by default.
This method requires system level permissions and cannot be called from an instance context.
Required permissions:
* `system.instance.read`
# List Trusted Domains
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists Trusted Domains of the instance.
By default the instance will be determined by the context of the request,
e.g. the host header.
You can optionally pass an InstanceID to list the domains of a specific instance.
This requires additional permissions.
Required permissions:
* `iam.read`
* `system.instance.read` (if InstanceID is set)
# Remove Custom Domain
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Removes a Custom Domain from the instance.
Be aware that this will stop routing requests from this domain to the instance and
might break existing setups or integrations.
This method requires system level permissions and cannot be called from an instance context.
Required permissions:
* `system.domain.write`
# Remove Trusted Domain
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Removes a Trusted Domain from the instance.
By default the instance will be determined by the context of the request,
e.g. the host header.
You can optionally pass an InstanceID to list the domains of a specific instance.
This requires additional permissions.
Required permissions:
* `iam.write`
* `system.instance.write` (if InstanceID is set)
# Update Instance
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Updates instance's name in the current context or by its ID.
By default the instance will be determined by the context of the request,
e.g. the host header.
You can optionally pass an InstanceID to update a specific instance.
This requires additional permissions.
Required permissions:
* `iam.write`
* `system.instance.write` (if InstanceID is set)
# Oidc API
API Reference for Oidc
> **Terminology Update:** We have streamlined our naming conventions to improve clarity. The term **Oidc** has been replaced with **Oidc**. To avoid breaking changes the APIs still make use of the old term. Both terms refer to the same underlying functionality.
# Authorize or Deny Device Authorization
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Authorize or deny the device authorization request based on the provided device authorization id.
Required permissions:
* `session.link`
# Create Callback
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Finalize an Auth Request and get the callback URL for success or failure.
The user must be redirected to the URL in order to inform the application about the success or failure.
On success, the URL contains details for the application to obtain the tokens.
This method can only be called once for an Auth request.
Required permissions:
* `session.link`
# Get Auth Request
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get OIDC Auth Request details by ID, obtained from the redirect URL.
Returns details that are parsed from the application's Auth Request.
Required permissions:
* `session.read`
# Get Device Authorization Request
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get the device authorization based on the provided "user code".
This will return the device authorization request, which contains the device authorization id
that is required to authorize the request once the user signed in or to deny it.
Required permissions:
* `session.read`
# Project API
API Reference for Project
> **Terminology Update:** We have streamlined our naming conventions to improve clarity. The term **Project** has been replaced with **Project**. To avoid breaking changes the APIs still make use of the old term. Both terms refer to the same underlying functionality.
# Activate Project
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Set the state of a project to active. Request returns no error if the project is already activated.
Required permission:
* `project.write`
# Activate Project Grant
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Set the state of the project grant to activated.
Required permission:
* `project.grant.write`
# Add Project Role
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new project role to a project. The key must be unique within the project.
Required permission:
* `project.role.write`
# Create Project
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a new project. A project is a vessel to group applications, roles and
authorizations. Every project belongs to exactly one organization, but
can be granted to other organizations for self-management of their authorizations.
Required permission:
* `project.create`
# Create Project Grant
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Grant a project to another organization.
The project grant will allow the granted organization to access the project and manage
the authorizations for its users.
Required permission:
* `project.grant.create`
# Deactivate Project
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Set the state of a project to deactivated. Request returns no error if the project is already deactivated.
Applications under deactivated projects are not able to login anymore.
Required permission:
* `project.write`
# Deactivate Project Grant
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Set the state of the project grant to deactivated.
Applications under deactivated projects grants are not able to login anymore.
Required permission:
* `project.grant.write`
# Delete Project
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Delete an existing project.
In case the project is not found, the request will return a successful response as
the desired state is already achieved.
Required permission:
* `project.delete`
# Delete Project Grant
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Delete a project grant. All user grants (role assignments) for this project grant will also be removed.
A user will not have access to the project afterward (if permissions are checked).
In case the project grant is not found, the request will return a successful response as
the desired state is already achieved.
Required permission:
* `project.grant.delete`
# Get Project
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns the project identified by the requested ID.
Required permission:
* `project.read`
# List Project Grants
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns a list of project grants. A project grant is when the organization grants its project
to another organization.
Required permission:
* `project.grant.read`
# List Project Roles
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns all roles of a project matching the search query.
Required permission:
* `project.role.read`
# List Projects
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List all matching projects. By default all projects of the instance that the caller
has permission to read are returned.
Make sure to include a limit and sorting for pagination.
Required permission:
* `project.read`
# Remove Project Role
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Removes the role from the project and on every resource it has a dependency.
This includes project grants and user grants (role assignments).
Required permission:
* `project.role.write`
# Update Project
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Update an existing project.
Required permission:
* `project.write`
# Update Project Grant
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change the roles of the project that is granted to another organization.
The project grant will allow the granted organization to access the project and manage
the authorizations for its users.
Required permission:
* `project.grant.write`
# Update Project Role
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change a project role. The key is not editable. If a key should change, remove the role and create a new one.
Required permission:
* `project.role.write`
# Org API
API Reference for Org
> **Terminology Update:** We have streamlined our naming conventions to improve clarity. The term **Org** has been replaced with **Org**. To avoid breaking changes the APIs still make use of the old term. Both terms refer to the same underlying functionality.
# Activate Organization
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Set the state of my organization to active. The state of the organization has to be deactivated to perform the request. Users of this organization will be able to log in again.
Required permission:
* `org.write`
# Add Organization
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a new organization with an administrative user. If no specific roles are sent for the users, they will be granted the role ORG\_OWNER.
Required permission:
* `org.create`
# Add Organization Domain
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new domain to an organization. The domains are used to identify to which organization a user belongs.
Required permission:
* `org.write`
# Deactivate Organization
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Sets the state of my organization to deactivated. Users of this organization will not be able to log in.
Required permission:
* `org.write`
# Delete Organization
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deletes the organization and all its resources (Users, Projects, Grants to and from the org). Users of this organization will not be able to log in.
Required permission:
* `org.delete`
# Delete Organization Domain
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Delete a new domain from an organization. The domains are used to identify to which organization a user belongs. If the uses use the domain for login, this will not be possible afterwards. They have to use another domain instead.
Required permission:
* `org.write`
# Delete Organization Metadata
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Delete metadata objects from an organization with a specific key.
Required permission:
* `org.write`
# Generate Organization Domain Validation
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Generate a new file to be able to verify your domain with DNS or HTTP challenge.
Required permission:
* `org.write`
# List Organization Domains
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns the list of registered domains of an organization. The domains are used to identify to which organization a user belongs.
Required permission:
* `org.read`
# List Organization Metadata
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List metadata of an organization filtered by query.
Required permission:
* `org.read`
# List Organizations
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Search for Organizations. By default, we will return all organization of the instance that you have permission to read.
Make sure to include a limit and sorting for pagination.
Required permission:
* `org.read`
# Set Organization Metadata
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Sets a list of key value pairs. Existing metadata entries with matching keys are overwritten. Existing metadata entries without matching keys are untouched. To remove existing metadata entries, pass an empty metadata value or use [DeleteOrganizationMetadata](/reference/api/org/zitadel.org.v2.OrganizationService.DeleteOrganizationMetadata). If no metadata entry exists for a given key, passing an empty value for that key has no effect (no-op). For HTTP requests, make sure the bytes array value is base64 encoded.
Required permission:
* `org.write`
# Update Organization
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change the name of the organization.
Required permission:
* `org.write`
# Verify Organization Domain
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Make sure you have added the required verification to your domain, depending on the method you have chosen (HTTP or DNS challenge). Zitadel will check it and set the domain as verified if it was successful. A verify domain has to be unique.
Required permission:
* `org.write`
# Saml API
API Reference for Saml
> **Terminology Update:** We have streamlined our naming conventions to improve clarity. The term **Saml** has been replaced with **Saml**. To avoid breaking changes the APIs still make use of the old term. Both terms refer to the same underlying functionality.
# Create Response
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Finalize a SAML Request and get the response definition for success or failure.
The response must be handled as per the SAML definition to inform the application about the success or failure.
On success, the response contains details for the application to obtain the SAMLResponse.
This method can only be called once for an SAML request.
Required permissions:
* `session.link`
# Get SAML Request
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get SAML Request details by ID. Returns details that are parsed from the application's SAML Request.
Required permissions:
* `session.read`
# Session API
API Reference for Session
> **Terminology Update:** We have streamlined our naming conventions to improve clarity. The term **Session** has been replaced with **Session**. To avoid breaking changes the APIs still make use of the old term. Both terms refer to the same underlying functionality.
# Create Session
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a new session with initial checks, metadata and challenges for further verification.
A token will be returned, which is required for using the session as authentication, e.g.
when authenticating an OIDC auth request or SAML request.
Additionally, the session token can be used as OAuth2 access token to authenticate against
the Zitadel APIs.
Required permissions:
* `session.write`
# DeleteSession
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Terminate an existing session. This invalidates the session and its token.
The session can no longer be used for the authentication of other resources
or to authenticate against the Zitadel APIs.
You can only terminate your own session, unless you are granted the `session.delete` permission.
Required permissions:
* `session.delete`
* no permission required for own sessions or when providing the current session token
# Get Session
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve a session by its ID. Returns all information about the session, including
the factors that were verified, the metadata, user agent information and possible expiration date.
The session token is required unless either of the following conditions is met:
* the caller created the session
* the authenticated user requests their own session (checked user)
* the security token provided in the authorization header has the same user agent as the session
* the caller is granted the permission session.read permission on either the instance or on the checked user's organization
Required permissions:
* `session.read`
* no permission required to get own sessions (see above) or when providing the current session token
# List sessions
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Searches for sessions matching the given query. You can search by session ID, user ID,
creation date, creator, user agent or expiration date.
Required permissions:
* `session.read`
* no permission required to search for own sessions
# Set Session
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Update an existing session with new information like additional checks or metadata
or request additional challenges.
A new session token will be returned. Note that the previous token will be invalidated.
Required permissions:
* `session.write`
# Settings API
API Reference for Settings
> **Terminology Update:** We have streamlined our naming conventions to improve clarity. The term **Settings** has been replaced with **Settings**. To avoid breaking changes the APIs still make use of the old term. Both terms refer to the same underlying functionality.
# Delete Organization Settings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Delete the settings specific to an organization.
Required permissions:
* `iam.policy.delete`
# Get Active Identity Providers
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get the current active identity providers for the requested context.
This can be the instance or an organization. In case of an organization,
the returned identity providers will fall back to the active instance identity providers
if not explicitly set on the organization.
Optionally, filter the identity providers by their allowed actions:
* creation\_allowed: only return identity providers that are allowed for user creation
* linking\_allowed: only return identity providers that are allowed for linking to existing users
* auto\_creation: only return identity providers that are allowed for automatic user creation
* auto\_linking: only return identity providers that are allowed for automatic linking to existing users
Required permissions:
* `policy.read`
# Get Branding Settings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get the current active branding settings for the requested context.
This can be the instance or an organization. In case of an organization,
the returned settings will fall back to the instance settings if not explicitly set on the organization.
Required permissions:
* `policy.read`
# Get Domain Settings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get the domain settings for the requested context.
This can be the instance or an organization. In case of an organization,
the returned settings will fall back to the instance settings if not explicitly set on the organization.
Required permissions:
* `policy.read`
# Get effective settings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets all settings which are requested in the request.
The settings values are resolved using the hierarchical model.
Only the resolved settings are returned, without the source
which describes from which hierarchical level they came.
# Get General Settings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get basic information of the instance like the default organization, default language and supported languages.
Required permissions:
* `policy.read`
# Get Hosted Login Translation
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns the translations in the requested locale for the hosted login.
The translations returned are based on the input level specified (system, instance or organization).
If the requested level doesn't contain all translations, and ignore\_inheritance is set to false,
a merging process fallbacks onto the higher levels ensuring all keys in the file have a translation,
which could be in the default language if the one of the locale is missing on all levels.
The etag returned in the response represents the hash of the translations as they are stored on DB
and its reliable only if ignore\_inheritance = true.
Required permissions:
* `iam.policy.read`
# Get Legal and Support Settings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get the legal and support settings for the requested context.
This can be the instance or an organization. In case of an organization,
the returned settings will fall back to the instance settings if not explicitly set on the organization.
Required permissions:
* `policy.read`
Deprecated: use GetLinkSettings instead
# Get Link settings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns the effective links for the requested scope.
Source is tracked for the list as a whole — the entire list
is either inherited or overridden, never partially merged.
# Get Lockout Settings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get the lockout settings for the requested context.
This can be the instance or an organization. In case of an organization,
the returned settings will fall back to the instance settings if not explicitly set on the organization.
Lockout settings define how many failed attempts are allowed before a user is locked out.
Required permissions:
* `policy.read`
# Get Login Settings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get the login settings for the requested context.
This can be the instance or an organization. In case of an organization,
the returned settings will fall back to the instance settings if not explicitly set on the organization.
Required permissions:
* `policy.read`
# Get Password Complexity Settings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get the password complexity settings for the requested context.
This can be the instance or an organization. In case of an organization,
the returned settings will fall back to the instance settings if not explicitly set on the organization.
Required permissions:
* `policy.read`
# Get Password Expiry Settings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get the password expiry settings for the requested context.
This can be the instance or an organization. In case of an organization,
the returned settings will fall back to the instance settings if not explicitly set on the organization.
Required permissions:
* `policy.read`
# Get Security Settings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get the security settings of the Zitadel instance.
Security settings include settings like enabling impersonation and embedded iframe settings.
Required permissions:
* `iam.policy.read`
# List Organization Settings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns a list of organization settings.
Required permission:
* `iam.policy.read`
* `org.policy.read`
# Reset link settings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Removes all link overrides at the requested scope.
The scope falls back to its parent's values.
Unlike other settings, there is no per-field reset — links are
managed as a list, so reset always clears the entire list.
# Set Hosted Login Translation
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Sets the input translations at the specified level (instance or organization) for the input language.
Required permissions:
* `iam.policy.write`
# Set Link settings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Replaces the entire link list at the requested scope.
This is a full replacement, not a merge — omitted links are removed.
To add a link, include the full list with the new link appended.
To remove a link, include the full list without it.
# Set Organization Settings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Sets the settings specific to an organization.
Organization scopes usernames defines that the usernames have to be unique in the organization scope,
can only be changed if the usernames of the users are unique in the scope.
Required permissions:
* `iam.policy.write`
# Set Security Settings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Set the security settings of the instance.
Required permissions:
* `iam.policy.write`
# System API
API Reference for System
> **Terminology Update:** We have streamlined our naming conventions to improve clarity. The term **System** has been replaced with **System**. To avoid breaking changes the APIs still make use of the old term. Both terms refer to the same underlying functionality.
# Adds a domain to an instance
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [AddCustomDomain](/reference/api/instance/zitadel.instance.v2.InstanceService.AddCustomDomain) instead to add a Custom Domain to the instance in context
# AddInstance
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use CreateInstance instead
Creates a new instance with all needed setup data
This might take some time
# AddQuota
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates a new quota
Returns an error if the quota already exists for the specified unit
Deprecated: use SetQuota instead
# BulkSetLimits
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Sets many instance level limits
# ClearView
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Truncates the delta of the change stream
be carefull with this function because Zitadel has to
recompute the deltas after they got cleared.
Search requests will return wrong results until all deltas are recomputed
# CreateInstance
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates a new instance with all needed setup data
This might take some time
# Checks if a domain exists
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [ListCustomDomains](/reference/api/instance/zitadel.instance.v2.InstanceService.ListCustomDomains) instead to check existence of an instance
# Returns the detail of an instance
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [GetInstance](/reference/api/system/zitadel.system.v1.SystemService.GetInstance) instead to get the details of the instance in context
# Healthz
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Indicates if Zitadel is running.
It respondes as soon as Zitadel started
# List Domains
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [instance service v2 ListCustomDomains](/reference/api/instance/zitadel.instance.v2.InstanceService.ListCustomDomains) instead.
Returns the Custom Domains of an instance.
# ListFailedEvents
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns event descriptions which cannot be processed.
It's possible that some events need some retries.
For example if the SMTP-API wasn't able to send an email at the first time
# ListIAMMembers
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns all instance members matching the request
all queries need to match (ANDed)
Deprecated: Use the Admin APIs ListIAMMembers instead
# Returns a list of Zitadel instances
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [ListInstances](/reference/api/system/zitadel.system.v1.SystemService.ListInstances) instead to list instances
# ListViews
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns all stored read models of Zitadel
views are used for search optimisation and optimise request latencies
they represent the delta of the event happend on the objects
# Removes the domain of an instance
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [RemoveDomain](/reference/api/instance/zitadel.instance.v2.InstanceService.RemoveCustomDomain) instead to remove a Custom Domain from the instance in context
# RemoveFailedEvent
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deletes the event from failed events view.
the event is not removed from the change stream
This call is usefull if the system was able to process the event later.
e.g. if the second try of sending an email was successful. the first try produced a
failed event. You can find out if it worked on the `failure_count`
# Removes an instance
This might take some time
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [DeleteInstance](/reference/api/instance/zitadel.instance.v2.InstanceService.DeleteInstance) instead to delete an instance
# RemoveQuota
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Removes a quota
# ResetLimits
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Resets instance level limits
# SetInstanceFeature
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Set a feature flag on an instance
# SetLimits
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Sets instance level limits
# SetPrimaryDomain
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Sets the primary domain of an instance
# SetQuota
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Sets quota configuration properties
Creates a new quota if it doesn't exist for the specified unit
# Updates name of an existing instance
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [UpdateInstance](/reference/api/system/zitadel.system.v1.SystemService.UpdateInstance) instead to update the name of the instance in context
# Webkey API
API Reference for Webkey
> **Terminology Update:** We have streamlined our naming conventions to improve clarity. The term **Webkey** has been replaced with **Webkey**. To avoid breaking changes the APIs still make use of the old term. Both terms refer to the same underlying functionality.
# Activate Web Key
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Switch the active signing web key. The previously active key will be deactivated.
Note that the JWKs OIDC endpoint returns a cacheable response.
Therefore it is not advised to activate a key that has been created within the cache duration (default is 5min),
as the public key may not have been propagated to caches and clients yet.
Required permission:
* `iam.web_key.write`
# Create Web Key
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Generate a private and public key pair. The private key can be used to sign OIDC tokens after activation.
The public key can be used to validate OIDC tokens.
The newly created key will have the state `STATE_INITIAL` and is published to the public key endpoint.
Note that the JWKs OIDC endpoint returns a cacheable response.
If no key type is provided, a RSA key pair with 2048 bits and SHA256 hashing will be created.
Required permission:
* `iam.web_key.write`
# Delete Web Key
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Delete a web key pair. Only inactive keys can be deleted. Once a key is deleted,
any tokens signed by this key will be invalid.
Note that the JWKs OIDC endpoint returns a cacheable response.
In case the web key is not found, the request will return a successful response as
the desired state is already achieved.
You can check the change date in the response to verify if the web key was deleted during the request.
Required permission:
* `iam.web_key.delete`
# List Web Keys
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List all web keys and their states.
Required permission:
* `iam.web_key.read`
# Admin API
API Reference for Admin
> **Terminology Update:** We have streamlined our naming conventions to improve clarity. The term **Admin** has been replaced with **Admin**. To avoid breaking changes the APIs still make use of the old term. Both terms refer to the same underlying functionality.
# ActivateEmailProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ActivateFeatureLoginDefaultOrg
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Activates the "LoginDefaultOrg" feature by setting the flag to "true"
This is irreversible!
Once activated, the login UI will use the settings of the default org (and not from the instance) if not organization context is set
# ActivateLabelPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ActivateSMSProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Deprecated: Activate SMTP Provider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Activate an SMTP provider.
Deprecated: please move to the new endpoint ActivateEmailProvider.
# AddAppleProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new Apple identity provider on the instance
# AddAzureADProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new Azure AD identity provider on the instance
# AddCustomDomainPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddCustomOrgIAMPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddEmailProviderHTTP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddEmailProviderSMTP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddGenericOAuthProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new OAuth2 identity provider on the instance
# AddGenericOIDCProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new OIDC identity provider on the instance
# AddGitHubEnterpriseServerProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new GitHub Enterprise Server identity provider on the instance
# AddGitHubProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new GitHub identity provider on the instance
# AddGitLabProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new GitLab identity provider on the instance
# AddGitLabSelfHostedProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new self hosted GitLab identity provider on the instance
# AddGoogleProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new Google identity provider on the instance
# Add instance Member
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [CreateAdministrator](/reference/api/internal_permission/zitadel.internal_permission.v2.InternalPermissionService.CreateAdministrator) instead.
Members are users with permission to administrate Zitadel on different levels. This request adds a new user to the members list with one or multiple roles.
# AddIDPToLoginPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Add an Instance Trusted Domain
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [instance service v2 ListTrustedDomains](/reference/api/instance/zitadel.instance.v2.InstanceService.AddTrustedDomain) instead.
Add a domain to the list configured for this Zitadel instance. These domains are trusted to be used as public hosts.
# AddJWTIDP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddJWTProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new JWT identity provider on the instance
# AddLDAPProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new LDAP identity provider on the instance
# AddMultiFactorToLoginPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddNotificationPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddOIDCIDP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddOIDCSettings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddSAMLProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new SAML identity provider on the instance
# AddSMSProviderHTTP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddSMSProviderTwilio
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Deprecated: Add SMTP Configuration
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new SMTP configuration if nothing is set yet.
Deprecated: please move to the new endpoint AddEmailProviderSMTP.
# AddSecondFactorToLoginPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddZitadelProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new Zitadel identity provider in the instance
# DeactivateEmailProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# DeactivateIDP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# DeactivateSMSProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Deprecated: Deactivate SMTP Provider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deactivate an SMTP provider. After deactivating the provider, the users will not be able to receive SMTP notifications from that provider anymore.
Deprecated: please move to the new endpoint DeactivateEmailProvider.
# DeleteProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Remove an identity provider
Will remove all linked providers of this configuration on the users
# ExportData
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetAllowedLanguages
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomDomainClaimedMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomDomainPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomInitMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomInviteUserMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomLoginTexts
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomOrgIAMPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomPasswordChangeMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomPasswordResetMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomPasswordlessRegistrationMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomVerifyEmailMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomVerifyEmailOTPMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomVerifyPhoneMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomVerifySMSOTPMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultDomainClaimedMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultInitMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultInviteUserMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultLanguage
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultLoginTexts
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Get Default Organization
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 ListOrganizations](/reference/api/org/zitadel.org.v2.OrganizationService.ListOrganizations) instead.
Get the default organization of the Zitadel instance. If no specific organization is given on the register form, a user will be registered to the default organization.
# GetDefaultPasswordChangeMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultPasswordResetMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultPasswordResetMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultPasswordlessRegistrationMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultVerifyEmailMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultVerifyEmailOTPMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultVerifyPhoneMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultVerifySMSOTPMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDomainPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetEmailProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetEmailProviderById
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetFileSystemNotificationProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetIDPByID
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetLabelPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetLockoutPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetLogNotificationProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetLoginPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Get My Instance
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [instance service v2 GetInstance](/reference/api/system/zitadel.system.v1.SystemService.GetInstance) instead.
Returns the details about the current instance such as the name, version, domains, etc.
# GetNotificationPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetOIDCSettings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Get Organization By ID
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 ListOrganizations](/reference/api/org/zitadel.org.v2.OrganizationService.ListOrganizations) instead.
Returns an organization by its ID. Make sure the user has the permissions to access the organization.
# GetOrgIAMPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetPasswordAgePolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetPasswordComplexityPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetPreviewLabelPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetPrivacyPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetProviderByID
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns an identity provider of the instance
# GetRestrictions
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets restrictions
# GetSMSProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Deprecated: Get active SMTP Configuration
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns the active SMTP configuration from the system. This is used to send E-Mails to the users.
Deprecated: please move to the new endpoint GetEmailProvider.
# Deprecated: Get SMTP provider configuration by its id
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get a specific SMTP provider configuration by its ID.
Deprecated: please move to the new endpoint GetEmailProviderById.
# GetSecretGenerator
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetSecurityPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetSupportedLanguages
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Healthz
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ImportData
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Imports data into an instance and creates different objects
# Is Organization Unique
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 ListOrganizations](/reference/api/org/zitadel.org.v2.OrganizationService.ListOrganizations) instead.
Checks if an organization with the searched parameters already exists or not.
# ListAggregateTypes
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListEmailProviders
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListEventTypes
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListEvents
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListFailedEvents
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListIAMMemberRoles
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# List instance Members
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [ListAdministrators](/reference/api/internal_permission/zitadel.internal_permission.v2.InternalPermissionService.ListAdministrators) instead.
Members are users with permission to administrate Zitadel on different levels. This request returns all users with memberships on the instance level, matching the search queries. The search queries will be AND linked.
# ListIDPs
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# List Instance Domains
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [instance service v2 GetInstance](/reference/api/system/zitadel.system.v1.SystemService.GetInstance) instead.
Returns a list of domains that are configured for this Zitadel instance. These domains are the URLs where Zitadel is running.
# List Instance Trusted Domains
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [instance service v2 ListTrustedDomains](/reference/api/instance/zitadel.instance.v2.InstanceService.ListTrustedDomains) instead.
Returns a list of domains that are configured for this Zitadel instance. These domains are trusted to be used as public hosts.
# ListLoginPolicyIDPs
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListLoginPolicyMultiFactors
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListLoginPolicySecondFactors
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListMilestones
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Search Organizations
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 ListOrganizations](/reference/api/org/zitadel.org.v2.OrganizationService.ListOrganizations) instead.
Returns a list of organizations that match the requesting filters. All filters are applied with an AND condition.
# ListProviders
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns all identity providers, which match the query
Limit should always be set, there is a default limit set by the service
# ListSMSProviders
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Deprecated: List SMTP Configs
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns a list of SMTP configurations.
Deprecated: please move to the new endpoint ListEmailProviders.
# ListSecretGenerators
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListViews
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# MigrateGenericOIDCProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Migrate an existing OIDC identity provider on the instance
# ReactivateIDP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RegenerateSAMLProviderCertificate
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Regenerate certificate for an existing SAML identity provider in the organization
# RemoveEmailProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RemoveFailedEvent
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Remove IAM Member
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [DeleteAdministrator](/reference/api/internal_permission/zitadel.internal_permission.v2.InternalPermissionService.DeleteAdministrator) instead.
Members are users with permission to administrate Zitadel on different levels. This request removes a user from the members list on an instance level. The user can still have roles on another level (organization, project).
# RemoveIDP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RemoveIDPFromLoginPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Remove an Instance Trusted Domain
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [instance service v2 ListTrustedDomains](/reference/api/instance/zitadel.instance.v2.InstanceService.RemoveTrustedDomain) instead.
Removes a domain from the list configured for this Zitadel instance. These domains are trusted to be used as public hosts.
# RemoveLabelPolicyFont
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RemoveLabelPolicyIcon
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RemoveLabelPolicyIconDark
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RemoveLabelPolicyLogo
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RemoveLabelPolicyLogoDark
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RemoveMultiFactorFromLoginPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Remove Organization
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 DeleteOrganization](/reference/api/org/zitadel.org.v2.OrganizationService.DeleteOrganization) instead.
Deletes the organization and all its resources (Users, Projects, Grants to and from the org). Users of this organization will not be able to log in.
# RemoveSMSProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Deprecated: Remove SMTP Configuration
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Remove the SMTP configuration, be aware that the users will not get an E-Mail if no SMTP is set.
Deprecated: please move to the new endpoint RemoveEmailProvider.
# RemoveSecondFactorFromLoginPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomDomainClaimedMessageTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomDomainPolicyToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomInitMessageTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomInviteUserMessageTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomLoginTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomOrgIAMPolicyToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomPasswordChangeMessageTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomPasswordResetMessageTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomPasswordlessRegistrationMessageTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomVerifyEmailMessageTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomVerifyEmailOTPMessageTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomVerifyPhoneMessageTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomVerifySMSOTPMessageTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetCustomLoginText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetDefaultDomainClaimedMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetDefaultInitMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetDefaultInviteUserMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetDefaultLanguage
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetDefaultOrg
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetDefaultPasswordChangeMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetDefaultPasswordResetMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetDefaultPasswordlessRegistrationMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetDefaultVerifyEmailMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetDefaultVerifyEmailOTPMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetDefaultVerifyPhoneMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetDefaultVerifySMSOTPMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetRestrictions
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Sets restrictions
# SetSecurityPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Setup Organization
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 AddOrganization](/reference/api/org/zitadel.org.v2.OrganizationService.AddOrganization) instead.
Create a new organization with an administrative user. If no specific roles are sent for the first user, the user will get the role ORG\_OWNER.
# TestEmailProviderSMTP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# TestEmailProviderSMTPById
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Deprecated: Test SMTP Provider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Test an SMTP provider. After testing the provider, the users will receive information about the test results.
Deprecated: please move to the new endpoint TestEmailProviderSMTP.
# Deprecated: Test SMTP Provider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Test an SMTP provider identified by its ID. After testing the provider, the users will receive information about the test results.
Deprecated: please move to the new endpoint TestEmailProviderSMTPById.
# UpdateAppleProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing Apple identity provider on the instance
# UpdateAzureADProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing Azure AD identity provider on the instance
# UpdateCustomDomainPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateCustomOrgIAMPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateDomainPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateEmailProviderHTTP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateEmailProviderSMTP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateEmailProviderSMTPPassword
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateGenericOAuthProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing OAuth2 identity provider on the instance
# UpdateGenericOIDCProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing OIDC identity provider on the instance
# UpdateGitHubEnterpriseServerProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing GitHub Enterprise Server identity provider on the instance
# UpdateGitHubProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing GitHub identity provider on the instance
# UpdateGitLabProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing GitLab identity provider on the instance
# UpdateGitLabSelfHostedProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing self hosted GitLab identity provider on the instance
# UpdateGoogleProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing Google identity provider on the instance
# Update instance Member
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [UpdateAdministrator](/reference/api/internal_permission/zitadel.internal_permission.v2.InternalPermissionService.UpdateAdministrator) instead.
Members are users with permission to administrate Zitadel on different levels. This request changes the roles of an existing member. The whole roles list will be updated. Make sure to include roles that you don't want to change (remove).
# UpdateIDP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateIDPJWTConfig
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateIDPOIDCConfig
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateJWTProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing JWT identity provider on the instance
# UpdateLDAPProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing LDAP identity provider on the instance
# UpdateLabelPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateLockoutPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateLoginPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateNotificationPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateOIDCSettings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateOrgIAMPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdatePasswordAgePolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdatePasswordComplexityPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdatePrivacyPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateSAMLProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing SAML identity provider on the instance
# UpdateSMSProviderHTTP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateSMSProviderTwilio
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateSMSProviderTwilioToken
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Deprecated: Update SMTP Configuration
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Update the SMTP configuration, be aware that this will be activated as soon as it is saved. So the users will get notifications from the newly configured SMTP.
Deprecated: please move to the new endpoint UpdateEmailProviderSMTP.
# Deprecated: Update SMTP Password
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Update the SMTP password that is used for the host, be aware that this will be activated as soon as it is saved. So the users will get notifications from the newly configured SMTP.
Deprecated: please move to the new endpoint UpdateEmailProviderSMTPPassword.
# UpdateSecretGenerator
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# User API
API Reference for User
> **Terminology Update:** We have streamlined our naming conventions to improve clarity. The term **User** has been replaced with **User**. To avoid breaking changes the APIs still make use of the old term. Both terms refer to the same underlying functionality.
# Create a new User (Human)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [CreateUser](/reference/api/user/zitadel.user.v2.UserService.CreateUser) to create a new user.
Create/import a new user. The newly created user will get a verification email if either the email address is not marked as verified and you did not request the verification to be returned.
# Add link to an identity provider to an user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add link to an identity provider to an user..
# Add a Key
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a keys that can be used to securely authenticate at the Zitadel APIs using JWT profile authentication using short-lived tokens.
Make sure you store the returned key safely, as you won't be able to read it from the Zitadel API anymore.
Only users of type machine can have keys.
Required permission:
* user.write
# Add OTP Email for a user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new OTP Email factor to the authenticated user. OTP Email will enable the user to verify an OTP with the latest verified email. The email has to be verified to add the second factor..
# Add OTP SMS for a user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new OTP SMS factor to the authenticated user. OTP SMS will enable the user to verify an OTP with the latest verified phone number. The phone number has to be verified to add the second factor..
# Add a Personal Access Token
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Personal access tokens (PAT) are the easiest way to authenticate to the Zitadel APIs.
Make sure you store the returned PAT safely, as you won't be able to read it from the Zitadel API anymore.
Only users of type machine can have personal access tokens.
Required permission:
* user.write
# Add a Users Secret
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Generates a client secret for the user.
The client id is the users username.
If the user already has a secret, it is overwritten.
Only users of type machine can have a secret.
Required permission:
* user.write
# Create an invite code for a user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create an invite code for a user to initialize their first authentication method (password, passkeys, IdP) depending on the organization's available methods.
If an invite code has been created previously, it's url template and application name will be used as defaults for the new code.
The new code will overwrite the previous one and make it invalid.
Note: It is possible to reissue a new code only when the previous code has expired, or when the user provides a wrong code three or more times during verification.
# Create a passkey registration link for a user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a passkey registration link which includes a code and either return it or send it to the user..
# Create a User
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a new user or service account in the specified organization.
Required permission:
* user.write
# Deactivate user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
The state of the user will be changed to 'deactivated'. The user will not be able to log in anymore. The endpoint returns an error if the user is already in the state 'deactivated'. Use deactivate user when the user should not be able to use the account anymore, but you still need access to the user data..
# Delete user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
The state of the user will be changed to 'deleted'. The user will not be able to log in anymore. Endpoints requesting this user will return an error 'User not found..
# Delete User Metadata
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Delete metadata objects from an user with a specific key.
Required permission:
* `user.write`
# Generate single-use recovery codes for a user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Generate new single-use recovery codes for the authenticated user. Recovery codes can be used to recover access to the account if other second factors are not available.
# User by ID
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns the full user or Service Account including the profile, email, etc..
# MFA Init Skipped
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Update the last time the user has skipped MFA initialization. The server timestamp is used.
# ListAuthenticationFactors
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# List all possible authentication methods of a user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List all possible authentication methods of a user like password, passkey, (T)OTP and more..
# List links to an identity provider of an user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List links to an identity provider of an user.
# Search Keys
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List all matching keys. By default all keys of the instance on which the caller has permission to read the owning users are returned.
Make sure to include a limit and sorting for pagination.
Required permission:
* user.read
# List passkeys of an user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List passkeys of an user
# Search Personal Access Tokens
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List all personal access tokens. By default all personal access tokens of the instance on which the caller has permission to read the owning users are returned.
Make sure to include a limit and sorting for pagination.
Required permission:
* user.read
# List User Metadata
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List metadata of an user filtered by query.
Required permission:
* `user.read`
# Search Users
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Search for users. By default, we will return all users of your instance that you have permission to read. Make sure to include a limit and sorting for pagination.
# Lock user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
The state of the user will be changed to 'locked'. The user will not be able to log in anymore. The endpoint returns an error if the user is already in the state 'locked'. Use this endpoint if the user should not be able to log in temporarily because of an event that happened (wrong password, etc.)..
# Request a code to reset a password
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Request a code to reset a password..
# Reactivate user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Reactivate a user with the state 'deactivated'. The user will be able to log in again afterward. The endpoint returns an error if the user is not in the state 'deactivated'..
# Start the registration of passkey for a user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Start the registration of a passkey for a user, as a response the public key credential creation options are returned, which are used to verify the passkey..
# Start the registration of a TOTP generator for a user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Start the registration of a TOTP generator for a user, as a response a secret returned, which is used to initialize a TOTP app or device..
# Start the registration of a u2f token for a user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Start the registration of a u2f token for a user, as a response the public key credential creation options are returned, which are used to verify the u2f token..
# Remove link of an identity provider to an user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Remove link of an identity provider to an user.
# Remove a Key
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Remove a machine users key by the given key ID and an optionally given user ID.
Required permission:
* user.write
# Remove OTP Email from a user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Remove the configured OTP Email factor of a user. As only one OTP Email per user is allowed, the user will not have OTP Email as a second factor afterward.
# Remove OTP SMS from a user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Remove the configured OTP SMS factor of a user. As only one OTP SMS per user is allowed, the user will not have OTP SMS as a second factor afterward.
# Remove passkey from a user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Remove passkey from a user.
# Remove a Personal Access Token
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Removes a machine users personal access token by the given token ID and an optionally given user ID.
Required permission:
* user.write
# Delete the user phone
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: [Update the users phone field](/reference/api/user/zitadel.user.v2.UserService.UpdateUser) to remove the phone number.
Delete the phone number of a user.
# Remove recovery codes from a user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Remove all recovery codes from the authenticated user. This will disable the recovery code second factor.
# Remove a Users Secret
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Remove the current client ID and client secret from a service account.
Required permission:
* user.write
# Remove TOTP generator from a user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Remove the configured TOTP generator of a user. As only one TOTP generator per user is allowed, the user will not have TOTP as a second factor afterward.
# Remove u2f token from a user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Remove u2f token from a user.
# ResendEmailCode
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Resend code to verify user email
# Resend an invite code for a user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [CreateInviteCode](/reference/api/user/zitadel.user.v2.UserService.CreateInviteCode) instead.
Resend an invite code for a user to initialize their first authentication method (password, passkeys, IdP) depending on the organization's available methods.
A resend is only possible if a code has been created previously and sent to the user. If there is no code or it was directly returned, an error will be returned.
# Resend code to verify user phone number
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Resend code to verify user phone number.
# Retrieve the information returned by the identity provider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve the information returned by the identity provider for registration or updating an existing user with new information..
# SendEmailCode
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Send code to verify user email
# Change the user email
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: [Update the users email field](/reference/api/user/zitadel.user.v2.UserService.UpdateUser).
Change the email address of a user. If the state is set to not verified, a verification code will be generated, which can be either returned or sent to the user by email..
# Change password
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: [Update the users password](/reference/api/user/zitadel.user.v2.UserService.UpdateUser) instead.
Change the password of a user with either a verification code or the current password..
# Set the user phone
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: [Update the users phone field](/reference/api/user/zitadel.user.v2.UserService.UpdateUser).
Set the phone number of a user. If the state is set to not verified, a verification code will be generated, which can be either returned or sent to the user by sms..
# Set User Metadata
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Sets a list of key value pairs. Existing metadata entries with matching keys are overwritten. Existing metadata entries without matching keys are untouched. To remove existing metadata entries, pass an empty metadata value or use [DeleteUserMetadata](/reference/api/user/zitadel.user.v2.UserService.DeleteUserMetadata). If no metadata entry exists for a given key, passing an empty value for that key has no effect (no-op). For HTTP requests, make sure the bytes array value is base64 encoded.
Required permission:
* `user.write`
# Start flow with an identity provider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Start a flow with an identity provider, for external login, registration or linking..
# Unlock user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
The state of the user will be changed to 'active'. The user will be able to log in again. The endpoint returns an error if the user is not in the state 'locked'.
# Update User (Human)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [UpdateUser](/reference/api/user/zitadel.user.v2.UserService.UpdateUser) to update a user.
Update all information from a user.
# Update a User
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Partially update an existing user.
If you change the users email or phone, you can specify how the ownership should be verified.
If you change the users password, you can specify if the password should be changed again on the users next login.
Required permission:
* user.write
# Verify the email
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Verify the email with the generated code.
# Verify an invite code for a user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Verify the invite code of a user previously issued. This will set their email to a verified state and
allow the user to set up their first authentication method (password, passkeys, IdP) depending on the organization's available methods.
# Verify a passkey for a user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Verify the passkey registration with the public key credential..
# Verify the phone number
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Verify the phone number with the generated code.
# Verify a TOTP generator for a user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Verify the TOTP registration with a generated code..
# Verify a u2f token for a user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Verify the u2f token registration with the public key credential..
# Caching
This guide explains how to connect Zitadel caches to Redis or Valkey on Kubernetes.
Caching is an experimental beta feature. See the [Caches](/self-hosting/manage/cache) page for behavior, objects, and tuning guidance.
Zitadel supports Redis or any Redis-compatible store (for example, Valkey) in standalone mode. Sentinel and Redis Cluster are not supported because the cache connector expects a single endpoint and does not implement cluster routing or sentinel failover logic.
Zitadel supports server-auth TLS (`rediss://` scheme) but does not support client certificates/mTLS for cache connections.
Each cache uses its own Redis database index. Set `DBOffset` so Zitadel-owned indexes do not overlap with other applications. Zitadel issues `FLUSHDB` on its cache databases, so sharing an index with other apps is unsafe.
Connect without authentication (development only) [#connect-without-authentication-development-only]
Pass the Redis URL as an environment variable:
```yaml
zitadel:
env:
- name: ZITADEL_CACHES_CONNECTORS_REDIS_URL
value: "redis://redis-master.caching.svc.cluster.local:6379"
- name: ZITADEL_CACHES_CONNECTORS_REDIS_ENABLED
value: "true"
```
Use this only on isolated clusters where the Redis endpoint does not require authentication.
Connect with password from a Kubernetes Secret [#connect-with-password-from-a-kubernetes-secret]
Store the Redis URL (with credentials) in a secret and pass it as an environment variable:
```bash
kubectl create secret generic zitadel-cache-credentials \
--from-literal=url="redis://cache-user:your-redis-password@redis.database.svc.cluster.local:6379"
```
```yaml
zitadel:
env:
- name: ZITADEL_CACHES_CONNECTORS_REDIS_URL
valueFrom:
secretKeyRef:
name: zitadel-cache-credentials
key: url
- name: ZITADEL_CACHES_CONNECTORS_REDIS_ENABLED
value: "true"
```
Connect with TLS (Redis or Valkey) [#connect-with-tls-redis-or-valkey]
Use the `rediss://` scheme (note the double `s`) when your endpoint requires encryption. This uses server-auth TLS with the container's trust store.
```bash
kubectl create secret generic zitadel-cache-credentials \
--from-literal=url="rediss://cache-user:your-redis-password@redis.example.com:6380"
```
```yaml
zitadel:
env:
- name: ZITADEL_CACHES_CONNECTORS_REDIS_URL
valueFrom:
secretKeyRef:
name: zitadel-cache-credentials
key: url
- name: ZITADEL_CACHES_CONNECTORS_REDIS_ENABLED
value: "true"
```
Custom certificate authorities must be added to the container's trust bundle (for example, by mounting a CA file into the pod); client certificate authentication is not supported.
# Configuration
This guide covers the major configuration options for the Zitadel Helm chart. For a complete list of options, see the [values.yaml](https://github.com/zitadel/zitadel-charts/blob/main/charts/zitadel/values.yaml) in the chart repository.
Global Settings [#global-settings]
Global settings affect multiple components of the deployment.
Replica Count [#replica-count]
Set the number of Zitadel replicas:
```yaml
replicaCount: 2
```
Container Images [#container-images]
Zitadel Image [#zitadel-image]
Configure the Zitadel container image:
```yaml
image:
repository: ghcr.io/zitadel/zitadel
tag: "v4.9.1"
pullPolicy: IfNotPresent
```
Login Image [#login-image]
Configure the Login container image:
```yaml
login:
image:
repository: ghcr.io/zitadel/login
tag: "v4.9.1"
pullPolicy: IfNotPresent
```
Pod Security Context [#pod-security-context]
Configure security settings for the pods. These settings apply globally to all pods in the deployment:
```yaml
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
securityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
privileged: false
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
```
Zitadel Settings [#zitadel-settings]
ExternalDomain [#external-domain]
The domain where Zitadel is accessible. This is used for generating URLs, cookies, and OIDC endpoints.
```yaml
zitadel:
configmapConfig:
ExternalDomain: "zitadel.example.com"
ExternalPort: 443
ExternalSecure: true
```
| Setting | Description |
| ---------------- | -------------------------------------------- |
| `ExternalDomain` | The public domain name (no protocol or port) |
| `ExternalPort` | The public port (443 for HTTPS, 80 for HTTP) |
| `ExternalSecure` | Whether the external connection uses HTTPS |
TLS [#tls]
TLS is terminated at the ingress controller. The Zitadel containers do not handle TLS termination:
```yaml
zitadel:
configmapConfig:
TLS:
Enabled: false
```
FirstInstance / Bootstrapping [#first-instance-bootstrapping]
Configure the initial Zitadel instance created during setup.
```yaml
zitadel:
configmapConfig:
FirstInstance:
Org:
Human:
UserName: "admin"
Password: "SecurePassword123!"
FirstName: "Zitadel"
LastName: "Admin"
Email: "admin@zitadel.example.com"
PasswordChangeRequired: false
```
Service Account for System API [#service-account-for-system-api]
For programmatic access, configure a system user with RSA keys:
```yaml
zitadel:
configmapConfig:
SystemAPIUsers:
systemuser:
KeyData: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
```
Generate the RSA private key:
```bash
openssl genrsa -out system-user-private.pem 2048
```
Extract the public key:
```bash
openssl rsa -in system-user-private.pem -pubout -out system-user-public.pem
```
Secrets [#secrets]
Referenced Secrets [#referenced-secrets]
Reference Kubernetes Secrets for sensitive values:
```yaml
zitadel:
masterkeySecretName: zitadel-masterkey
configSecretName: zitadel-config-secret
```
Create the masterkey secret:
```bash
kubectl create secret generic zitadel-masterkey \
--from-literal=masterkey="$(tr -dc A-Za-z0-9
No cluster yet? [k3d](https://k3d.io/) is a quick way to spin one up locally — it runs k3s in Docker, with Traefik as the built-in ingress controller:
```bash
k3d cluster create zitadel --port "80:80@loadbalancer"
```
Install the full stack [#install-the-full-stack]
```bash
mkdir zitadel-helm && cd zitadel-helm &&
curl -fsSLO https://raw.githubusercontent.com/zitadel/zitadel-charts/main/examples/0-quickstart/quickstart-values.yaml
```
Edit `quickstart-values.yaml` before installing. For a local k3d or k3s cluster, these values usually work as-is:
```yaml
zitadel:
configmapConfig:
ExternalDomain: localhost
ExternalPort: 80
ingress:
className: traefik
login:
ingress:
className: traefik
```
If you are using a different ingress controller, replace both `className` values with that controller's IngressClass name.
```bash
helm repo add zitadel https://charts.zitadel.com &&
helm repo update &&
helm upgrade --install zitadel zitadel/zitadel --values quickstart-values.yaml --wait
```
That's it. Visit [http://localhost/ui/console?login\_hint=zitadel-admin@zitadel.localhost](http://localhost/ui/console?login_hint=zitadel-admin@zitadel.localhost) and log in with password `Password1!`.
The masterkey [encrypts sensitive data at rest](/concepts/architecture/secrets). The `quickstart-values.yaml` file contains a **dev placeholder** masterkey — generate a real one before any non-local deployment:
```bash
tr -dc A-Za-z0-9
The stack runs: your ingress controller → **ZITADEL API** (Go) + **ZITADEL Login** (Next.js) → **PostgreSQL** (bundled).
See [HTTP/2](/self-hosting/manage/http2) for requirements on how the ingress controller must forward traffic to ZITADEL pods.
Swap out components [#swap-out-components]
The bundled PostgreSQL subchart is for quickstart use only. You can replace it independently:
| Component | How to replace |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Database** | Set `postgresql.enabled: false` and configure `ZITADEL_DATABASE_POSTGRES_DSN` pointing to your own PostgreSQL. Use the `postgres` maintenance database so ZITADEL can create its own database during initialization |
| **Ingress controller** | Update `ingress.className` (and `login.ingress.className`) to match your controller's IngressClass |
| **Caching** | Add Redis by configuring `zitadel.configmapConfig.Caches` (see [Caching](/self-hosting/deploy/kubernetes/caching)) |
Stage 2 — Production Cluster [#stage-2-production-cluster]
For production you need:
* A Kubernetes cluster (1.30+)
* An ingress controller (Traefik, NGINX, or cloud-native)
* A domain with DNS configured
* TLS certificates (via cert-manager, ACME, or manually)
* PostgreSQL 14+ (managed service or in-cluster)
* Helm 3.x+
**PostgreSQL compatibility:** ZITADEL supports PostgreSQL **14–18**. When using **PostgreSQL 18**, ensure you run **ZITADEL v4.11.0 or newer**. For the most up-to-date compatibility matrix and configuration details, see [Database requirements](/self-hosting/manage/database).
The ZITADEL management console [requires end-to-end HTTP/2 support](/self-hosting/manage/http2). Ensure your ingress controller is configured to forward HTTP/2 (h2c) traffic to ZITADEL pods.
Create secrets [#create-secrets]
```bash
# Masterkey — generate once, store safely, cannot be changed after first run
kubectl create secret generic zitadel-masterkey \
--from-literal=masterkey="$(tr -dc A-Za-z0-9
```
5. Monitor the upgrade. Watch the pods:
```bash
kubectl get pods --watch
```
Check the Helm release status:
```bash
helm status my-zitadel
```
Scaling [#scaling]
Manual Scaling [#manual-scaling]
Adjust the replica count in your values:
```yaml
replicaCount: 3
```
Or scale directly:
```bash
kubectl scale deployment my-zitadel --replicas=3
```
Horizontal Pod Autoscaler [#horizontal-pod-autoscaler]
Enable HPA for automatic scaling based on resource utilization:
```yaml
zitadel:
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
```
This creates an HPA that:
* Maintains at least 2 replicas
* Scales up to 10 replicas
* Targets 80% CPU and memory utilization
View HPA status:
```bash
kubectl get hpa
```
Get detailed HPA information:
```bash
kubectl describe hpa my-zitadel
```
Resource Requests and Limits [#resource-requests-and-limits]
Configure appropriate resource allocations:
```yaml
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 1000m
memory: 1Gi
```
**Recommendations by deployment size:**
| Size | CPU Request | CPU Limit | Memory Request | Memory Limit |
| ----------- | ----------- | --------- | -------------- | ------------ |
| Small (dev) | 100m | 500m | 256Mi | 512Mi |
| Medium | 250m | 1000m | 512Mi | 1Gi |
| Large | 500m | 2000m | 1Gi | 2Gi |
Pod Disruption Budget [#pod-disruption-budget]
Ensure availability during voluntary disruptions by using `minAvailable`:
```yaml
podDisruptionBudget:
enabled: true
minAvailable: 1
```
Alternatively, use `maxUnavailable`:
```yaml
podDisruptionBudget:
enabled: true
maxUnavailable: 1
```
This ensures at least one pod remains available during node drains, upgrades, or other voluntary disruptions.
Database Scaling Considerations [#database-scaling-considerations]
When scaling Zitadel horizontally, ensure your PostgreSQL database can handle the increased connection load:
* Each Zitadel pod opens multiple connections
* Consider using PgBouncer for connection pooling
* Monitor database connection usage
Example connection pooling setup with PgBouncer:
```bash
kubectl create secret generic zitadel-db-credentials \
--from-literal=dsn="postgresql://zitadel:password@pgbouncer.database.svc.cluster.local:6432/zitadel?sslmode=disable"
```
```yaml
zitadel:
env:
- name: ZITADEL_DATABASE_POSTGRES_DSN
valueFrom:
secretKeyRef:
name: zitadel-db-credentials
key: dsn
configmapConfig:
Database:
Postgres:
MaxOpenConns: 20
MaxIdleConns: 10
```
Next Steps [#next-steps]
* [Configuration](./configuration) — Review all configuration options
* [Uninstalling](./uninstalling) — Remove Zitadel from your cluster
# Uninstalling
This guide covers how to remove Zitadel from your Kubernetes cluster.
Uninstall the Helm Release [#uninstall-the-helm-release]
```bash
helm uninstall my-zitadel
```
Clean Up Helm Hooks [#clean-up-helm-hooks]
> ⚠️ **Important:** The Zitadel chart uses Helm hooks, which are [not garbage collected by helm uninstall](https://helm.sh/docs/topics/charts_hooks/#hook-resources-are-not-managed-with-corresponding-releases).
Remove hook resources manually:
```bash
for k8sresourcetype in job configmap secret rolebinding role serviceaccount; do
kubectl delete $k8sresourcetype \
--selector app.kubernetes.io/name=zitadel,app.kubernetes.io/managed-by=Helm
done
```
Complete Namespace Teardown [#complete-namespace-teardown]
To remove everything including the namespace and any Persistent Volume Claims:
```bash
kubectl delete namespace zitadel
```
> ⚠️ **Warning:** This permanently deletes all data in the namespace, including any PVCs. Ensure you have backups if needed.
Verify Removal [#verify-removal]
Confirm all resources have been removed.
Check for remaining Zitadel resources:
```bash
kubectl get all --selector app.kubernetes.io/name=zitadel
```
Check for remaining secrets:
```bash
kubectl get secrets --selector app.kubernetes.io/name=zitadel
```
If you did not delete the namespace, check for remaining PVCs:
```bash
kubectl get pvc --selector app.kubernetes.io/name=zitadel
```
# Mirror data from cockroach to postgres
The `mirror` command allows you to do database to database migrations. This functionality is useful to copy data from one database to another.
The data can be mirrored to multiple database without influencing each other.
Use cases [#use-cases]
Migrate from cockroachdb to postgres.
Replicate data to a secondary environment for testing.
Prerequisites [#prerequisites]
You need an existing source database, most probably the database Zitadel currently serves traffic from.
To mirror the data, the destination database needs to be initialized and set up without an instance. You can find the commands to start an empty Zitadel deployment in [the example section](#prepare-the-destination-database).
Start the destination database [#start-the-destination-database]
Follow one of the following guides to start the database:
* [Linux](/self-hosting/deploy/linux#run-postgre-sql)
* [MacOS](/self-hosting/deploy/macos#install-postgre-sql)
Or use the following commands for [Docker Compose](/self-hosting/deploy/compose)
```bash
# Download the docker compose example configuration.
wget https://raw.githubusercontent.com/zitadel/zitadel/main/docs/self-hosting/deploy/docker-compose.yaml
# Run the database and application containers.
docker compose up db --detach
```
Example [#example]
Prepare the destination database [#prepare-the-destination-database]
The following commands setup the database without an instance.
```bash
zitadel init --config /path/to/your/new/config.yaml
zitadel setup --for-mirror --config /path/to/your/new/config.yaml # make sure to set --tlsMode and masterkey analog to your current deployment
```
Mirror the data [#mirror-the-data]
The next step is to copy the data from the source to the destination database. For detailed configuration options, please refer to the [configuration section](#configuration).
```bash
zitadel mirror --system --config /path/to/your/mirror/config.yaml # make sure to set --tlsMode and masterkey analog to your current deployment
```
Initialize the data and verify [#initialize-the-data-and-verify]
The last step is to setup the permissions and verify the data, there might be differences between source and destination, refer to [`zitadel mirror verify`](#zitadel-mirror-verify) to get an overview of possible diffs.
```bash
zitadel setup --for-mirror --config /path/to/your/new/config.yaml # make sure to set --tlsMode and masterkey analog to your current deployment
zitadel mirror verify --system --config /path/to/your/mirror/config.yaml # make sure to set --tlsMode and masterkey analog to your current deployment
```
Usage [#usage]
The general syntax for the mirror command is:
```bash
zitadel mirror [flags]
Flags:
-h, --help help for mirror
--config stringArray path to config file to overwrite system defaults
--ignore-previous ignores previous migrations of the events table. This flag should be used if you manually dropped previously mirrored events.
--replace replaces all data of the following tables for the provided instances or all if the `--system`-flag is set:
* system.assets
* auth.auth_requests
* eventstore.unique_constraints
The should be provided if you want to execute the mirror command multiple times so that the static data are also mirrored to prevent inconsistent states.
--instance strings id or comma separated ids of the instance(s) to migrate. Either this or the `--system`-flag must be set. Make sure to always use the same flag if you execute the command multiple times.
--system migrates the whole system. Either this or the `--instance`-flag must be set. Make sure to always use the same flag if you execute the command multiple times.
# For the flags below use the same configuration you also use in the current deployment
--masterkey string masterkey as argument for en/decryption keys
-m, --masterkeyFile string path to the masterkey for en/decryption keys
--masterkeyFromEnv read masterkey for en/decryption keys from environment variable (ZITADEL_MASTERKEY)
--tlsMode externalSecure start Zitadel with (enabled), without (disabled) TLS or external component e.g. reverse proxy (external) terminating TLS, this flag will overwrite externalSecure and `tls.enabled` in configs files
```
Settings [#settings]
```yaml
# The source database the data are copied from. Use either cockroach or postgres, by default cockroach is used
Source:
cockroach:
Host: localhost # ZITADEL_SOURCE_COCKROACH_HOST
Port: 26257 # ZITADEL_SOURCE_COCKROACH_PORT
Database: zitadel # ZITADEL_SOURCE_COCKROACH_DATABASE
MaxOpenConns: 6 # ZITADEL_SOURCE_COCKROACH_MAXOPENCONNS
MaxIdleConns: 6 # ZITADEL_SOURCE_COCKROACH_MAXIDLECONNS
MaxConnLifetime: 30m # ZITADEL_SOURCE_COCKROACH_MAXCONNLIFETIME
MaxConnIdleTime: 5m # ZITADEL_SOURCE_COCKROACH_MAXCONNIDLETIME
Options: "" # ZITADEL_SOURCE_COCKROACH_OPTIONS
User:
Username: zitadel # ZITADEL_SOURCE_COCKROACH_USER_USERNAME
Password: "" # ZITADEL_SOURCE_COCKROACH_USER_PASSWORD
SSL:
Mode: disable # ZITADEL_SOURCE_COCKROACH_USER_SSL_MODE
RootCert: "" # ZITADEL_SOURCE_COCKROACH_USER_SSL_ROOTCERT
Cert: "" # ZITADEL_SOURCE_COCKROACH_USER_SSL_CERT
Key: "" # ZITADEL_SOURCE_COCKROACH_USER_SSL_KEY
# Postgres is used as soon as a value is set
# The values describe the possible fields to set values
postgres:
Host: # ZITADEL_SOURCE_POSTGRES_HOST
Port: # ZITADEL_SOURCE_POSTGRES_PORT
Database: # ZITADEL_SOURCE_POSTGRES_DATABASE
MaxOpenConns: # ZITADEL_SOURCE_POSTGRES_MAXOPENCONNS
MaxIdleConns: # ZITADEL_SOURCE_POSTGRES_MAXIDLECONNS
MaxConnLifetime: # ZITADEL_SOURCE_POSTGRES_MAXCONNLIFETIME
MaxConnIdleTime: # ZITADEL_SOURCE_POSTGRES_MAXCONNIDLETIME
Options: # ZITADEL_SOURCE_POSTGRES_OPTIONS
User:
Username: # ZITADEL_SOURCE_POSTGRES_USER_USERNAME
Password: # ZITADEL_SOURCE_POSTGRES_USER_PASSWORD
SSL:
Mode: # ZITADEL_SOURCE_POSTGRES_USER_SSL_MODE
RootCert: # ZITADEL_SOURCE_POSTGRES_USER_SSL_ROOTCERT
Cert: # ZITADEL_SOURCE_POSTGRES_USER_SSL_CERT
Key: # ZITADEL_SOURCE_POSTGRES_USER_SSL_KEY
# The destination database the data are copied to. Use either cockroach or postgres, by default cockroach is used
Destination:
postgres:
Host: localhost # ZITADEL_DATABASE_POSTGRES_HOST
Port: 5432 # ZITADEL_DATABASE_POSTGRES_PORT
Database: zitadel # ZITADEL_DATABASE_POSTGRES_DATABASE
MaxOpenConns: 5 # ZITADEL_DATABASE_POSTGRES_MAXOPENCONNS
MaxIdleConns: 2 # ZITADEL_DATABASE_POSTGRES_MAXIDLECONNS
MaxConnLifetime: 30m # ZITADEL_DATABASE_POSTGRES_MAXCONNLIFETIME
MaxConnIdleTime: 5m # ZITADEL_DATABASE_POSTGRES_MAXCONNIDLETIME
Options: "" # ZITADEL_DATABASE_POSTGRES_OPTIONS
User:
Username: zitadel # ZITADEL_DATABASE_POSTGRES_USER_USERNAME
Password: "" # ZITADEL_DATABASE_POSTGRES_USER_PASSWORD
SSL:
Mode: disable # ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE
RootCert: "" # ZITADEL_DATABASE_POSTGRES_USER_SSL_ROOTCERT
Cert: "" # ZITADEL_DATABASE_POSTGRES_USER_SSL_CERT
Key: "" # ZITADEL_DATABASE_POSTGRES_USER_SSL_KEY
# As cockroachdb first copies the data into memory this parameter is used to iterate through the events table and fetch only the given amount of events per iteration
EventBulkSize: 10000 # ZITADEL_EVENTBULKSIZE
# The maximum duration an auth request was last updated before it gets ignored.
# Default is 30 days
MaxAuthRequestAge: 720h # ZITADEL_MAXAUTHREQUESTAGE
Projections:
# Defines how many projections are allowed to run in parallel
ConcurrentInstances: 7 # ZITADEL_PROJECTIONS_CONCURRENTINSTANCES
# Limits the amount of events projected by each iteration
EventBulkLimit: 1000 # ZITADEL_PROJECTIONS_EVENTBULKLIMIT
Auth:
Spooler:
# Limits the amount of events projected by each iteration
BulkLimit: 1000 #ZITADEL_AUTH_SPOOLER_BULKLIMIT
Admin:
Spooler:
# Limits the amount of events projected by each iteration
BulkLimit: 10 #ZITADEL_ADMIN_SPOOLER_BULKLIMIT
Log:
Level: info
```
Sub commands [#sub-commands]
The provided sub commands allow more fine grained execution of copying the data.
The following commands are safe to execute multiple times by adding the `--replace`-flag which replaces the data not provided by the events in the destination database.
`zitadel mirror auth` [#zitadel-mirror-auth]
Copies the auth requests to the destination database.
`zitadel mirror eventstore` [#zitadel-mirror-eventstore]
Copies the events since the last migration and unique constraints to the destination database.
`zitadel mirror projections` [#zitadel-mirror-projections]
Executes all projections in the destination database.
It is NOOP if the projections are already up-to-date.
`zitadel mirror system` [#zitadel-mirror-system]
Copies encryption keys and assets to the destination database.
`zitadel mirror verify` [#zitadel-mirror-verify]
Prints the amount of rows of the source and destination database and the diff. Positive numbers indicate more rows in the destination table that in the source, negative numbers the opposite.
The following tables will likely have an unequal count:
* **projections.current\_states**: If your deployment was upgraded several times, the number of entries in the destination will be lower
* **projections.locks**: If your deployment was upgraded several times, the number of entries in the destination will be lower
* **projections.keys4\***: Only not expired keys are inserted, the number of entries in the destination will be lower
* **projections.failed\_events**: Should be lower or equal.
* **auth.users2**: Was replaced with auth.users3, the number of entries in the destination will be 0
* **auth.users3**: Is the replacement of auth.users2, the number of entries in the destination will be equal or higher
Limitations [#limitations]
It is not possible to use files as source or destination. See github issue [here](https://github.com/zitadel/zitadel/issues/7966)
Currently the encryption keys of the source database must be copied to the destination database. See github issue [here](https://github.com/zitadel/zitadel/issues/7964)
It is not possible to change the domain of the Zitadel deployment.
Once you mirrored an instance using the `--instance` flag, you have to make sure you don't mirror other preexisting instances. This means for example, you cannot mirror a few instances and then pass the `--system` flag. You have to pass all remaining instances explicitly, once you used the `--instance` flag
# ZITADEL Command Line Interface
This documentation serves as your guide to interacting with Zitadel through the command line interface (CLI). The Zitadel CLI empowers you to manage various aspects of your Zitadel system efficiently from your terminal.
This introductory section provides a brief overview of what the Zitadel CLI offers and who can benefit from using it.
Let's dive in!
Download the CLI [#download-the-cli]
Download the CLI for [Linux](/self-hosting/deploy/linux#install-zitadel) or [MacOS](/self-hosting/deploy/macos#install-zitadel).
Quick start [#quick-start]
The easiest way to start ZITADEL is by following the [docker compose example](/self-hosting/deploy/compose) which executes the commands for you.
Initialize the database [#initialize-the-database]
The `zitadel init`-command sets up the zitadel database. The statements executed need a user with `ADMIN`-privilege. See [init phase](/self-hosting/manage/updating_scaling#the-init-phase) for more information.
Setup ZITADEL [#setup-zitadel]
The `zitadel setup`-command further sets up the database created using `zitadel init`. This command only requires the user created in the previous step. See [setup phase](/self-hosting/manage/updating_scaling#the-setup-phase) for more information.
Start ZITADEL [#start-zitadel]
The `zitadel start`-command runs the ZITADEL server. See [runtime phase](/self-hosting/manage/updating_scaling#the-runtime-phase) for more information.
The `zitadel start-from-setup`-command first executes [the setup phase](#setup-zitadel) and afterwards runs the ZITADEL server.
The `zitadel start-from-init`-command first executes [the init phase](#Initialize-the-database), afterwards [the setup phase](#setup-zitadel) and lastly runs the ZITADEL server.
# Troubleshoot ZITADEL
Instance not found [#instance-not-found]
upstream sent too big header while reading response header from upstream [#upstream-sent-too-big-header-while-reading-response-header-from-upstream]
Container restarts after upgrading to a new version [#container-restarts-after-upgrading-to-a-new-version]
When you upgrade ZITADEL to a newer version, the setup phase runs database migrations automatically.
**Which command to use for upgrades:**
* `start-from-init` (used in the quickstart) runs **init → setup → start** in sequence. The init phase is only needed once ever (for a fresh database), but it is safe to re-run because it skips resources that already exist. Use `start-from-init` only for the quickstart or when you're sure the database needs initialising from scratch.
* **For upgrades on existing installations**, prefer `start-from-setup` (runs setup → start) or run `zitadel setup` followed by `zitadel start` as separate steps. This avoids running the init phase unnecessarily and is the recommended production approach. See [Update and Scale ZITADEL](/self-hosting/manage/updating_scaling) for details.
If the container exits with code 1 and the logs show a database migration error, check the following:
* Make sure the PostgreSQL version meets the [requirements](/self-hosting/manage/database). PostgreSQL 18 support requires ZITADEL v4.11.0 or later.
* Log messages like `role "zitadel" already exists` or `database "zitadel" already exists` during `start-from-init` re-runs are **expected and harmless** — the init phase skips those steps.
* If the container still fails after a version upgrade, check whether the version you are upgrading to lists any known migration issues in the [release notes](https://github.com/zitadel/zitadel/releases).
Instance settings (FIRSTINSTANCE / DEFAULTINSTANCE) not taking effect after restart [#instance-settings-firstinstance-defaultinstance-not-taking-effect-after-restart]
Environment variables prefixed with `ZITADEL_FIRSTINSTANCE_` and `ZITADEL_DEFAULTINSTANCE_` are only applied during the **initial creation** of a ZITADEL instance.
Changing these variables and restarting the container will not update an already-existing instance.
To change instance-level settings on a running installation:
* Use the [Admin Console](https://your-domain/ui/console/instance) to update settings interactively.
* Use the Admin API to automate changes.
**Login V2**: Starting with ZITADEL v4, the Login V2 UI is enabled by default for new installations.
If you are running an existing installation and want to enable it, follow the [Login Client guide](/self-hosting/manage/login-client).
ZITADEL service becomes unhealthy without visible error messages [#zitadel-service-becomes-unhealthy-without-visible-error-messages]
If the `zitadel` container shows as `unhealthy` but the log output does not reveal an obvious error:
1. Increase log verbosity by setting `ZITADEL_LOG_LEVEL: debug` and `ZITADEL_LOGSTORE_ACCESS_STDOUT_ENABLED: true`.
2. Check that the healthcheck command can reach ZITADEL. The built-in `zitadel ready` command uses HTTP when `ZITADEL_TLS_ENABLED=false` and HTTPS otherwise. Ensure that this variable matches your TLS settings.
3. If ZITADEL is running behind a reverse proxy, make sure the proxy forwards traffic correctly before the first healthcheck succeeds. See [Run and configure a reverse proxy](/self-hosting/manage/reverseproxy/reverse_proxy).
# Database
Database Configuration [#database-configuration]
Even if you provision the PostgreSQL user and database manually, you must still run `zitadel init schema`
to bootstrap the required schemas and tables before running `start-from-setup` or `zitadel start`.
Skipping this step causes `relation "eventstore.events" does not exist` errors.
See the [Managed PostgreSQL / No Admin Access](#managed-postgresql--no-admin-access) section below for the exact steps.
The command is `zitadel init schema` (alias: `zitadel init zitadel` for backwards compatibility).
ZITADEL with Postgres [#zitadel-with-postgres]
PostgreSQL is the default database for ZITADEL due to its reliability, robustness, and adherence to SQL standards. It is well-suited for handling the complex data requirements of an identity management system.
If you are using Zitadel v2 and want to use a PostgreSQL database, you can [overwrite the default configuration](/self-hosting/manage/configure/configure).
Currently, versions 14 to 18 are supported.
Connect ZITADEL to your PostgreSQL database by providing a DSN (connection URL) via the environment variable:
```bash
export ZITADEL_DATABASE_POSTGRES_DSN=postgresql://zitadel:password@localhost:5432/zitadel?sslmode=disable
```
If you prefer configuration files, set the DSN in your YAML config:
```yaml
Database:
postgres:
DSN: postgresql://zitadel:password@localhost:5432/zitadel?sslmode=disable
MaxOpenConns: 15
MaxIdleConns: 10
MaxConnLifetime: 1h
MaxConnIdleTime: 5m
```
Pool and tuning settings can be configured alongside the DSN as overlays.
SSL is configured through DSN query parameters. For example, to require TLS with certificate verification:
```bash
postgresql://zitadel:password@db.example.com:5432/zitadel?sslmode=verify-full&sslrootcert=/path/to/ca.crt
```
Managed PostgreSQL / No Admin Access [#managed-postgre-sql-no-admin-access]
If you don't have a PostgreSQL superuser available (e.g. managed services like RDS, Cloud SQL, or Azure Database),
you can pre-provision the user and database yourself and skip the admin-credential requirement.
The `init` command performs two steps that require different privileges:
* **Provisioning step** — `CREATE ROLE`, `CREATE DATABASE`, `GRANT` → requires a superuser/admin.
* **Schema bootstrapping step** — creates schemas (`eventstore`, `projections`, `system`) and tables → requires only database owner privileges.
If you handle the provisioning step manually, you still **must** run the schema bootstrapping step. Use the dedicated sub-command `zitadel init schema`, which performs only schema bootstrapping using the service user credentials — no admin privileges needed.
**Provisioning step — Provision user and database (run as superuser)**
Replace `` with a strong, unique password and store it in a secret manager. Do not use trivial passwords in production.
```sql
CREATE ROLE zitadel LOGIN PASSWORD '';
CREATE DATABASE zitadel WITH OWNER zitadel;
-- The GRANT below is redundant when zitadel is the database owner,
-- but is included for clarity. Owners implicitly hold all privileges.
GRANT CONNECT, CREATE ON DATABASE zitadel TO zitadel;
```
Adjust `pg_hba.conf` if needed to allow the zitadel user to connect.
**Schema bootstrapping step — Bootstrap schemas and tables (run as the service user)**
Provide the DSN with the service user credentials. For managed/remote PostgreSQL, set `sslmode` to `require` or `verify-full` to encrypt the connection. `disable` is only appropriate for local development.
```bash
ZITADEL_DATABASE_POSTGRES_DSN=postgresql://zitadel:@:5432/zitadel?sslmode=require \
zitadel init schema
```
**Run setup and start**
For production deployments, configure TLS to encrypt authentication traffic. See the [TLS configuration guide](/self-hosting/manage/tls_modes) for the available modes.
```bash
ZITADEL_DATABASE_POSTGRES_DSN=postgresql://zitadel:@:5432/zitadel?sslmode=require \
zitadel start-from-setup --masterkey "<32-character-key>"
```
See the [phases guide](/self-hosting/manage/updating_scaling#separating-init-and-setup-from-the-runtime) for details on separating init, setup, and runtime for production deployments.
Zitadel Credentials [#zitadel-credentials]
The [init phase](/self-hosting/manage/updating_scaling#separating-init-and-setup-from-the-runtime) of ZITADEL creates the ZITADEL database user if it does not exist and admin credentials are provided. **Init does not update or deprecate existing users or passwords.** If you need to change the ZITADEL database user or rotate its password, you must do this manually.
The `init` command is designed to be run **only once**, during the initial setup of ZITADEL. Running `init` again after changing the database user or credentials does **not** migrate database object ownerships, nor does it reassign permissions on schemas, tables, or other objects to a new user. You must update these permissions manually if you switch users.
Rotating Database Credentials [#rotating-database-credentials]
If you must rotate the credentials:
* You can change the password for the existing ZITADEL user, but you must manually update the password wherever ZITADEL needs it.
* Creating a new database user requires manual reassignment of ownership and privileges for all required database objects (schemas, tables, etc.) to the new user.
* After migration, remove or deprecate the old user using admin access and commands appropriate for your database provider.
Attempting to run `init` a second time with new credentials will fail if the user already exists, and will not change existing permissions or migrate data.
Dropping and recreating the database does **not** automatically remove existing users. Always verify and clean up old users if necessary.
# Self-Hosted ZITADEL Configuration: Runtime Settings & Environment Variables
This guide assumes you are familiar with [running ZITADEL using the least amount of configuration possible](/self-hosting/deploy/overview).
Configuration files [#configuration-files]
Runtime configuration file [#runtime-configuration-file]
You can configure the runtime using the `--config` flag of the `zitadel` binary.
Also, you can use the environment variables listed in the defaults.yaml.
For overwriting the default configuration for the first instance created by zitadel setup, use the FirstInstance section in the [database initialization file](#database-initialization-file).
defaults.yaml
Database initialization file [#database-initialization-file]
ZITADEL uses a [different configuration file](https://github.com/zitadel/zitadel/blob/main/cmd/setup/steps.yaml) for *database initialization steps*.
Use the `--steps` flag of the `zitadel` binary to provide this configuration file.
Also, you can use the environment variables listed in the steps.yaml.
By using the FirstInstance section, you can overwrite the [DefaultInstance configuration](#runtime-configuration-file) for the first instance created by zitadel setup.
steps.yaml
Pre-existing Database and User [#pre-existing-database-and-user]
By default, when you run `zitadel init` or `zitadel start-from-init`, ZITADEL checks if the specified database and user exist. If they don't, ZITADEL creates them and grants the necessary permissions.
If you manage the database and user manually, you can instruct ZITADEL to skip these checks. This is useful if the ZITADEL user has limited database permissions, for example, if it is not a `SUPERUSER`. This scenario is most common on cloud providers.
To skip the checks, you must ensure the database objects (database, user, grant) exist before running the commands and then set the `Database.postgres.Admin.ExistingDatabase` field in your configuration as shown below. This tells ZITADEL to connect to the existing database as an admin user, bypassing the creation and permission steps.
skip-superuser-queries-config.yaml
Multiple configuration files [#multiple-configuration-files]
ZITADEL merges configuration files when multiple `--config` and `--steps` flags are provided.
You can use these flags to handle standard configuration files differently from secret configuration files.
For example, standard configuration files stored in git may contain public information such as a database hostname.
To use private information — such as a database admin credential — without storing it in git, use an extra `--config` or `--steps` flag that requests the private information from a secret manager.
Environment variables [#environment-variables]
All configuration properties are configurable using environment variables.
ZITADEL environment variable keys are prefixed with `ZITADEL_`.
For example, to configure the default ZITADEL instance admin username and password set the `zitadel` binary runtime environment variables `ZITADEL_FIRSTINSTANCE_ORG_HUMAN_USERNAME` and `ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD`.
All supported environment variables are listed in the [runtime configuration file](#runtime-configuration-file) and the [database initialization file](#database-initialization-file).
Proxy configuration [#proxy-configuration]
A proxy for outgoing connections can be configured using the environment variables: Use `HTTP_PROXY` for outgoing HTTP requests, and `HTTPS_PROXY` for outgoing HTTPS requests.
These environment variables are used as a proxy URL.
To exclude specific hosts from proxying, set the `NO_PROXY` environment variable: The value is interpreted as a comma-separated string.
For more information on the `NO_PROXY` environment variable, read the [`httpproxy` Go doc](https://pkg.go.dev/golang.org/x/net/http/httpproxy#Config).
Masterkey [#masterkey]
The masterkey is used to AES256-encrypt other generated encryption keys.
It must be 32 bytes.
There are three ways to pass the masterkey to the `zitadel` binary:
* By value: Use the flag `--masterkey My_Master_Key_Which_Has_32_Bytes`
* By environment variable `ZITADEL_MASTERKEY`: Use the flag `--masterkeyFromEnv`
* By file: Use the flag `--masterkeyFile /path/to/file`
Passing the configuration [#passing-the-configuration]
What's next [#whats-next]
* Read more about [the login process](/guides/integrate/login/login-users).
* If you are running ZITADEL in production, you need to [customize your own domain](../custom-domain).
* Check out all possible [runtime configuration properties and their defaults in the source code](https://github.com/zitadel/zitadel/blob/main/cmd/defaults.yaml)
* Check out all possible [setup step configuration properties and their defaults in the source code](https://github.com/zitadel/zitadel/blob/main/cmd/setup/steps.yaml)
The ZITADEL management console [requires end-to-end HTTP/2 support](/self-hosting/manage/http2)
# Configure ZITADEL
Detailed configuration options for ZITADEL.
* [Configuration Guide](/self-hosting/manage/configure/configure)
# Metrics
Metrics [#metrics]
ZITADEL exposes operational and performance metrics to support monitoring, alerting, and capacity planning.
Metrics are **enabled by default** in standard deployments and are exposed via an HTTP endpoint that can be consumed by Prometheus and other monitoring systems that understand the Prometheus exposition format or use an OpenTelemetry pipeline.
Metrics endpoint [#metrics-endpoint]
ZITADEL exposes metrics at the following endpoint:
`/debug/metrics`
This endpoint returns a snapshot of the current metrics in **Prometheus-compatible exposition format**.
If you changed ports, paths, or network exposure as part of your deployment (for example via Helm values, reverse proxies, or ingress), adjust the endpoint accordingly.
Instrumentation model [#instrumentation-model]
Internally, ZITADEL instruments metrics using **OpenTelemetry (OTel)**.
The exposed metrics include:
* **Process and runtime metrics**
(for example Go runtime, memory usage, goroutines)
* **HTTP and gRPC server metrics**
(request counts, latencies, error rates)
* **ZITADEL-specific metrics**
(server behavior and internal components available in your version)
The exact set of metrics and labels may evolve between ZITADEL releases.
Collection and compatibility [#collection-and-compatibility]
The `/debug/metrics` endpoint can be consumed in multiple ways:
* **Prometheus** can scrape the endpoint directly.
* **OpenTelemetry Collector** can scrape the endpoint using a Prometheus receiver and forward metrics to other backends.
* **Managed monitoring platforms** (for example Grafana Cloud, Amazon Managed Service for Prometheus, Datadog, New Relic) can ingest the same endpoint via Prometheus-compatible agents or collectors.
ZITADEL does **not** require a specific metrics backend. Any system capable of scraping or ingesting Prometheus-format metrics can be used.
Settings [#settings]
Metrics are enabled by default in standard ZITADEL deployments.
If metrics were explicitly disabled in your configuration, re-enable them before proceeding. The default configuration is defined in the project's `defaults.yaml` file:
* [https://github.com/zitadel/zitadel/blob/main/cmd/defaults.yaml](https://github.com/zitadel/zitadel/blob/main/cmd/defaults.yaml)
Refer to the configuration documentation for details on enabling or adjusting metrics behavior.
Next steps [#next-steps]
Choose how you want to collect and store metrics:
* **Prometheus** — scrape metrics directly from ZITADEL
* **OpenTelemetry Collector** — aggregate and forward metrics to another backend
* **Managed observability platforms** — use their Prometheus-compatible agents
The following sections describe concrete setups, starting with **Prometheus**.
# Prometheus
Prometheus [#prometheus]
This page shows platform-agnostic ways to scrape ZITADEL metrics using:
* Kubernetes **annotation-based autodiscovery** (no Operator required)
* Kubernetes with **Prometheus Operator** using **ServiceMonitor**
* Standalone Prometheus on a VM or using **Docker**
* How to verify scraping, plus a minimal alert example
> ZITADEL’s endpoint path is `/debug/metrics`. If you changed ports or paths via your deployment tooling, adjust the examples accordingly.
Before you begin [#before-you-begin]
* You have a running ZITADEL instance reachable from your monitoring stack.
* You know the base URL and port to reach ZITADEL (e.g., `http://zitadel.zitadel.svc:8080` in Kubernetes or `http://localhost:8080` locally).
* Metrics are enabled in your runtime [configuration](https://zitadel.com/docs/self-hosting/manage/configure) (they are enabled by default in standard setups). If you explicitly disabled metrics in your configuration, re-enable them before proceeding. The (default) configuration is located in the [defaults.yaml](https://github.com/zitadel/zitadel/blob/main/cmd/defaults.yaml).
Option A — Kubernetes (annotation-based autodiscovery) [#option-a-kubernetes-annotation-based-autodiscovery]
This approach is common when you **don’t use the Prometheus Operator**. Vanilla Prometheus can **auto-discover** scrape targets by reading standard annotations on Pods/Services:
```yaml
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/path: "/debug/metrics"
prometheus.io/port: "8080"
```
When your Prometheus server is configured with **Kubernetes service discovery** and **relabeling rules** that honor these annotations, it will automatically discover and scrape ZITADEL without any per-target `scrape_configs`.
1\) Ensure Prometheus has Kubernetes discovery and relabeling [#1-ensure-prometheus-has-kubernetes-discovery-and-relabeling]
Your Prometheus configuration (often installed via Helm) should include jobs like the following. These are canonical examples that keep annotated Pods and map the annotated path/port to the actual metrics endpoint:
```yaml
scrape_configs:
- job_name: "kubernetes-pods"
kubernetes_sd_configs:
- role: pod
relabel_configs:
# Only keep pods with prometheus.io/scrape: "true"
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
# Use prometheus.io/path for the metrics path
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
# Replace the address with :
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
target_label: __address__
```
> Most Prometheus Helm charts already ship with similar discovery jobs and relabeling rules. If you installed Prometheus via Helm, you likely already have these in place.
2\) Annotate ZITADEL Pods (or the Service) [#2-annotate-zitadel-pods-or-the-service]
If you deploy ZITADEL via Helm and the chart emits scrape annotations on the Deployment/Pods, no extra work is needed. Otherwise, add the annotations yourself (via values override or a strategic patch):
```yaml
spec:
template:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/path: "/debug/metrics"
prometheus.io/port: "8080"
```
If you prefer annotating the Service and your Prometheus config uses `role: service` discovery, add the same keys to the Service metadata.
3\) RBAC [#3-rbac]
Prometheus must have permission to **list/watch** Pods/Endpoints in the target namespaces. Ensure its ServiceAccount has the standard ClusterRole/ClusterRoleBinding for discovery. Missing RBAC typically shows up as discovery errors in the Prometheus logs.
Option B — Kubernetes with Prometheus Operator (ServiceMonitor) [#option-b-kubernetes-with-prometheus-operator-service-monitor]
If you run **kube-prometheus-stack** or the **Prometheus Operator**, use a **ServiceMonitor** (or **PodMonitor**). ZITADEL’s Helm chart provides **out-of-the-box** [ServiceMonitor](https://github.com/zitadel/zitadel-charts/blob/main/charts/zitadel/templates/servicemonitor.yaml) support that you can enable via values—no manual YAML is required.
Enable the built-in ServiceMonitor (recommended) [#enable-the-built-in-service-monitor-recommended]
```bash
helm repo add zitadel https://charts.zitadel.com
helm upgrade --install zitadel zitadel/zitadel \
--namespace zitadel --create-namespace \
--set metrics.enabled=true \
--set metrics.serviceMonitor.enabled=true
```
**Optional but common settings:**
* Make the ServiceMonitor discoverable by your Prometheus (Operator) instance (many stacks match a label like `release=kube-prometheus-stack`):
```bash
--set metrics.serviceMonitor.additionalLabels.release=kube-prometheus-stack
```
* Place the ServiceMonitor in a central monitoring namespace (default is the ZITADEL release namespace):
```bash
--set metrics.serviceMonitor.namespace=monitoring
```
* Tune scraping:
```bash
--set metrics.serviceMonitor.scrapeInterval=15s \
--set metrics.serviceMonitor.scrapeTimeout=10s
```
* Network/TLS customization (use only if you need them):
```bash
--set metrics.serviceMonitor.scheme=https \
--set metrics.serviceMonitor.tlsConfig.insecureSkipVerify=true
```
* Relabeling:
```bash
--set metrics.serviceMonitor.relabellings[0].action=replace \
--set metrics.serviceMonitor.metricRelabellings[0].action=drop
```
What the chart generates (explained) [#what-the-chart-generates-explained]
The chart renders a `ServiceMonitor` roughly equivalent to:
```yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: # {{ include "zitadel.fullname" . }}
# namespace: # only if you set it
labels:
# Standard chart labels + any you add:
# {{- include "zitadel.start.labels" . | nindent 4 }}
# {{- toYaml .Values.metrics.serviceMonitor.additionalLabels | nindent 4 }}
spec:
jobLabel: # {{ include "zitadel.fullname" . }}
namespaceSelector:
matchNames:
- "" # defaults to the Helm release namespace
selector:
matchLabels:
# Matches the ZITADEL Service created by the chart
# {{- include "zitadel.service.selectorLabels" . | nindent 6 }}
endpoints:
- port: "-server" # e.g., "http-server" or "https-server"
path: /debug/metrics
# Optional tunables below are included only if set:
interval:
scrapeTimeout:
scheme: # http|https
tlsConfig: # metrics.serviceMonitor.tlsConfig
# ...
proxyUrl:
honorLabels:
honorTimestamps:
relabelings: # metrics.serviceMonitor.relabellings
# ...
metricRelabelings: # metrics.serviceMonitor.metricRelabellings
# ...
```
**Details that matter:**
* **Port name**: The chart uses
`port: {{ regexReplaceAll "\\W+" .Values.service.protocol "-" }}-server`
which resolves to **`http-server`** when `service.protocol=http` (default) or **`https-server`** when `service.protocol=https`. You do **not** need to edit this—just make sure you didn’t rename the ZITADEL Service port.
* **Namespace selection**: By default, the ServiceMonitor targets the **ZITADEL release namespace** via:
```yaml
namespaceSelector:
matchNames:
- ""
```
Set `metrics.serviceMonitor.namespace` if you want the ServiceMonitor object itself to live elsewhere (e.g., `monitoring`). The `selector.matchLabels` still points to the ZITADEL **Service** labels.
* **Labels for discovery**: If your Prometheus (Operator) instance selects ServiceMonitors by label (common in kube-prometheus-stack), add those under `metrics.serviceMonitor.additionalLabels`—for example:
```yaml
metrics:
serviceMonitor:
additionalLabels:
release: kube-prometheus-stack
```
* **Path**: The chart fixes the metrics path to **`/debug/metrics`** (matches ZITADEL’s endpoint).
Manual ServiceMonitor (only if you don’t want Helm to render it) [#manual-service-monitor-only-if-you-dont-want-helm-to-render-it]
If you prefer to manage the `ServiceMonitor` yourself, keep it aligned with the chart’s conventions:
* Target the ZITADEL **Service** (not pods) using the same selector labels the chart adds.
* Use the correct **port name** (`http-server` or `https-server`) and **path** (`/debug/metrics`).
* Ensure your Prometheus (Operator) selects this ServiceMonitor by label/namespace.
> **Tip**
> If you already run the **Prometheus Operator**, prefer this ServiceMonitor approach. If you run **vanilla Prometheus without the Operator**, consider the **annotation-based discovery** method instead (Option A).
Option C — Standalone Prometheus (VM / Docker) [#option-c-standalone-prometheus-vm-docker]
If you run Prometheus outside of Kubernetes, add a static job pointing at ZITADEL’s metrics endpoint:
```yaml
global:
scrape_interval: 15s
scrape_configs:
- job_name: "zitadel"
metrics_path: "/debug/metrics"
scheme: "http" # use https if TLS is enabled for Zitadel
static_configs:
- targets: [":8080"] # e.g., "localhost:8080", "zitadel.internal:8080" or "host.docker.internal:8080"
```
In this snippet, replace `:8080` with the appropriate address. This could be localhost:8080 for local deployments, or a DNS name / IP of the server or Kubernetes service where ZITADEL is running. If ZITADEL is behind a reverse proxy or ingress, ensure that `/debug/metrics` is reachable (you might expose it internally only). The metrics\_path is set to /debug/metrics to match ZITADEL’s endpoint. We use http scheme assuming an internal/non-TLS endpoint; if you have enabled TLS on ZITADEL, use https and the appropriate port (e.g., `443`) and adjust any hostname (like zitadel.yourdomain.com).
> When running **Prometheus in Docker** on your workstation:
>
> * **macOS/Windows**: if ZITADEL runs on your host, use `host.docker.internal:8080`.
> * **Linux**: either add `--add-host=host.docker.internal:host-gateway` to `docker run`, attach Prometheus to the same Docker network as ZITADEL and use the service name (e.g., `zitadel:8080`), or run Prometheus with `--network host` (Linux only).
Verify scraping [#verify-scraping]
Use Prometheus’s built-in UI to confirm your target is up:
1. Open the UI (default `http://localhost:9090`).
2. Go to **Status → Targets** and find the `zitadel` target. It should be **UP**.
3. Go to **Graph** and run:
```yaml
up{job="zitadel"}
```
Value **1** indicates successful scrapes.
To explore ZITADEL metrics, type `zitadel` in the expression box and pick from auto-complete. Common families include:
* **Go runtime / process** (e.g., `go_goroutines`, `process_resident_memory_bytes`)
* **HTTP / gRPC** request counters/latencies (names vary by build and exporters)
* **ZITADEL server / DB** metrics (e.g., connection pool counts or other instrumentation available in your version)
> Metric names and labels can change between releases. Use the **Graph → Insert metric at cursor** dropdown to discover what your instance exposes.
Minimal alerting example [#minimal-alerting-example]
Create a basic rule to alert when ZITADEL stops scraping:
```yaml
# /etc/prometheus/alerts/zitadel.yml
groups:
- name: zitadel-basic
rules:
- alert: ZitadelTargetDown
expr: up{job="zitadel"} == 0
for: 5m
labels:
severity: critical
annotations:
summary: "ZITADEL metrics target is down"
description: "Prometheus has not scraped ZITADEL successfully for 5 minutes."
```
Reference the rule file in your Prometheus config:
```yaml
rule_files:
- "/etc/prometheus/alerts/*.yml"
```
(Configure Alertmanager routing according to your environment.)
Troubleshooting [#troubleshooting]
**Target is DOWN / connection refused**
* In Docker: remember **`localhost` inside the Prometheus container is the container itself**, not your host. Use `host.docker.internal:8080` (plus `--add-host=host.docker.internal:host-gateway` on Linux), or join Prometheus to the **same Docker network** as ZITADEL and use the **service name** (e.g., `zitadel:8080`), or run with `--network host` on Linux.
* In Kubernetes: verify the Service port name (for example, `http-server` or `https-server`) and path (`/debug/metrics`) match your ServiceMonitor (or annotations). Check that Prometheus has RBAC to list Pods/Endpoints.
**Metrics path mismatch**
* ZITADEL uses **`/debug/metrics`**. If you see 404s, confirm your Prometheus job or annotations aren’t still using `/metrics`.
**No targets discovered (Kubernetes)**
* If using annotations, make sure your Prometheus config has **Kubernetes discovery & relabeling** rules that honor `prometheus.io/*` annotations and that the Pods/Service are annotated.
* If using ServiceMonitor, ensure your Prometheus Operator instance **selects** the ServiceMonitor by label/namespace.
**Nothing shows up in the Graph dropdown**
* First confirm `up{job="zitadel"}` returns **1**. If yes, metrics are being scraped—start typing generic prefixes like `go_` or `process_` to explore. ZITADEL’s exported metric set can evolve; check the raw output at `/debug/metrics` to see exactly what is exposed by your version.
Alternatives that understand OTEL/Prometheus [#alternatives-that-understand-otel-prometheus]
While Prometheus is the most common choice, other collectors and services can ingest the same endpoint:
* **Amazon Managed Service for Prometheus (AMP)** — managed, Prometheus-compatible backend on AWS.
* **AWS CloudWatch via ADOT/OTel Collector** — scrape with the OpenTelemetry Collector and export to CloudWatch Metrics.
* **Grafana Cloud / VictoriaMetrics / Thanos** — remote-write targets or managed TSDBs for Prometheus data.
* **Datadog / New Relic / Splunk Observability** — agents or OTel pipelines can ingest Prometheus-format metrics.
If you already operate one of these platforms, you can point their agents/collectors at `/debug/metrics` or use an OTel Collector with a **Prometheus receiver** and the appropriate exporter.
# Reverse Proxy Configuration
ZITADEL exposes a single HTTP/2 port (default `8080`) that serves both gRPC and HTTP APIs, as well as the management console.
Running a reverse proxy in front of ZITADEL is the recommended approach for production deployments because it:
* Terminates TLS and provides HTTPS to clients
* Exposes standard ports (`80`/`443`) while keeping ZITADEL on an internal port
* Handles certificate renewal (e.g. via Let's Encrypt)
* Enables path-based routing between the ZITADEL API and the Login UI
HTTP/2 and h2c requirements [#http-2-and-h-2-c-requirements]
ZITADEL relies on HTTP/2 for all gRPC communication.
When the reverse proxy terminates TLS, it must forward traffic to ZITADEL using **h2c** (unencrypted HTTP/2), not HTTP/1.1.
Not all reverse proxies support h2c by default — check the proxy-specific guides below for configuration examples.
For more details, see [HTTP/2 Support in ZITADEL](/self-hosting/manage/http2).
TLS modes [#tls-modes]
ZITADEL supports three TLS modes that affect how the reverse proxy should be configured:
| Mode | Description | Typical use |
| ---------- | -------------------------------------------------------------------- | ---------------------------------- |
| `disabled` | ZITADEL listens on plain HTTP, reverse proxy handles nothing special | Local development, h2c passthrough |
| `external` | ZITADEL listens on plain HTTP; reverse proxy terminates TLS | Most production setups |
| `enabled` | ZITADEL itself terminates TLS (end-to-end encryption) | Environments requiring mutual TLS |
For more details, see [TLS Modes](/self-hosting/manage/tls_modes).
Proxy guides [#proxy-guides]
Check out one of the following guides to configure your favorite reverse proxy:
* [Traefik](/self-hosting/manage/reverseproxy/traefik)
* [NGINX](/self-hosting/manage/reverseproxy/nginx)
* [Caddy](/self-hosting/manage/reverseproxy/caddy)
* [Apache httpd](/self-hosting/manage/reverseproxy/httpd)
* [Cloudflare](/self-hosting/manage/reverseproxy/cloudflare)
* [Cloudflare Tunnel](/self-hosting/manage/reverseproxy/cloudflare_tunnel)
* [Fronting Zitadel Cloud](/self-hosting/manage/reverseproxy/zitadel_cloud)
Login UI routing [#login-ui-routing]
When running both the ZITADEL API and the Login V2 UI (`zitadel-login`) behind a reverse proxy, the proxy must route traffic to the correct upstream:
* `/ui/v2/login` → Login UI container (port `3000` by default)
* All other paths → ZITADEL API container (port `8080` by default)
The proxy-specific guides include Docker Compose examples that demonstrate this split routing.
For tested proxy versions, see [ZITADEL Requirements](/self-hosting/manage/requirements#reverse-proxy).
# Management API
API Reference for Management
> **Terminology Update:** We have streamlined our naming conventions to improve clarity. The term **Management** has been replaced with **Management**. To avoid breaking changes the APIs still make use of the old term. Both terms refer to the same underlying functionality.
# ActivateCustomLabelPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Create Application (API)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [CreateApplication](/reference/api/application/zitadel.application.v2.ApplicationService.CreateApplication) instead to create an API application
Create a new API client. The client id will be generated and returned in the response.
Depending on the chosen configuration also a secret will be generated and returned.
# Create Application Key
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [CreateApplicationKey](/reference/api/application/zitadel.application.v2.ApplicationService.CreateApplicationKey) instead to create an application key.
Create a new application key, they are used for authorizing API Applications. Key details will be returned in the response, make sure to save it.
# AddAppleProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new Apple identity provider in the organization
# AddAzureADProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new Azure AD identity provider in the organization
# AddCustomLabelPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddCustomLockoutPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddCustomLoginPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddCustomNotificationPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddCustomPasswordAgePolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddCustomPasswordComplexityPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddCustomPrivacyPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddGenericOAuthProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new OAuth2 identity provider in the organization
# AddGenericOIDCProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new OIDC identity provider in the organization
# AddGitHubEnterpriseServerProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new GitHub Enterprise Server identity provider in the organization
# AddGitHubProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new GitHub identity provider in the organization
# AddGitLabProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new GitLab identity provider in the organization
# AddGitLabSelfHostedProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new self hosted GitLab identity provider in the organization
# AddGoogleProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new Google identity provider in the organization
# Create User (Human)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 CreateUser](/reference/api/user/zitadel.user.v2.UserService.CreateUser) instead.
Create a new user. The newly created user will get an initialization email if either the email address is not marked as verified or no password is set. If a password is set the user will not be requested to set a new one on the first login.
# AddIDPToLoginPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AddJWTProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new JWT identity provider in the organization
# AddLDAPProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new LDAP identity provider in the organization
# Create Key for service account
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 AddKey](/reference/api/user/zitadel.user.v2.UserService.AddKey) instead.
If a public key is not supplied, a new key is generated and will be returned in the response.
Make sure to store the returned key.
If an RSA public key is supplied, the private key is omitted from the response.
Machine keys are used to authenticate with jwt profile.
# Create User (Machine)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 CreateUser](/reference/api/user/zitadel.user.v2.UserService.CreateUser) instead.
Create a new user with the type machine for your API, service or device. These users are used for non-interactive authentication flows.
# AddMultiFactorToLoginPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Create Application (OIDC)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [CreateApplication](/reference/api/application/zitadel.application.v2.ApplicationService.CreateApplication) instead to create an OIDC application.
Create a new OIDC client. The client id will be generated and returned in the response. Depending on the chosen configuration also a secret will be returned.
# Create Organization
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 CreateOrganization](/reference/api/org/zitadel.org.v2.OrganizationService.AddOrganization) instead
Create a new organization. Based on the provided name a domain will be generated to be able to identify users within an organization.
# Add Domain
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 AddOrganizationDomain](/reference/api/org/zitadel.org.v2.OrganizationService.AddOrganizationDomain) instead.
Add a new domain to an organization. The domains are used to identify to which organization a user belongs.
# AddOrgJWTIDP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Add Organization Member
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [CreateAdministrator](/reference/api/internal_permission/zitadel.internal_permission.v2.InternalPermissionService.CreateAdministrator) instead.
Members are users with permission to administrate Zitadel on different levels. This request adds a new user to the members list on the organization level with one or multiple roles.
# AddOrgOIDCIDP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Add Passkey Registration Link
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 RegisterPasskey](/reference/api/user/zitadel.user.v2.UserService.RegisterPasskey) instead.
Adds a new passkey authenticator link to the user and returns it in the response. The link enables the user to register a new device if current passkey devices are all platform authenticators. e.g. User has already registered Windows Hello and wants to register FaceID on the iPhone.
# Create a Personal-Access-Token (PAT)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 AddPersonalAccessToken](/reference/api/user/zitadel.user.v2.UserService.AddPersonalAccessToken) instead.
Generates a new PAT for the user. Currently only available for machine users.
The token will be returned in the response, make sure to store it.
PATs are ready-to-use tokens and can be sent directly in the authentication header.
# Create Project
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [project v2 service CreateProject](/reference/api/project/zitadel.project.v2.ProjectService.CreateProject) instead.
Create a new project. A Project is a vessel for different applications sharing the same role context.
# Add Project Grant
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [CreateProjectGrant](/reference/api/project/zitadel.project.v2.ProjectService.CreateProjectGrant) instead.
Grant a project to another organization. The project grant will allow the granted organization to access the project and manage the role assignments for its users. Project Grant will be listed in the granted project of the granted organization.
# Add Project Grant Member
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [CreateAdministrator](/reference/api/internal_permission/zitadel.internal_permission.v2.InternalPermissionService.CreateAdministrator) instead.
Members are users with permission to administrate Zitadel on different levels. This request returns all users with memberships on the project grant level, matching the search queries. The search queries will be AND linked.
# Add Project Member
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [CreateAdministrator](/reference/api/internal_permission/zitadel.internal_permission.v2.InternalPermissionService.CreateAdministrator) instead.
Members are users with permission to administrate Zitadel on different levels. This request adds a new user to the members list on the project level with one or multiple roles.
# Add Project Role
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [project v2 service AddProjectRole](/reference/api/project/zitadel.project.v2.ProjectService.AddProjectRole) instead.
Add a new project role to a project. The key must be unique within the project.
# Create Application (SAML)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [CreateApplication](/reference/api/application/zitadel.application.v2.ApplicationService.CreateApplication) instead to create a SAML application.
Create a new SAML client. Returns an entity ID.
# AddSAMLProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new SAML identity provider in the organization
# AddSecondFactorToLoginPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Add User Grant
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: [Add an authorization](/reference/api/authorization/zitadel.authorization.v2.AuthorizationService.CreateAuthorization) to grant a user access to an owned or granted project.
Add a user grant for a specific user. User grants are the roles users have for a specific project and organization.
Note: User grant refers to role assignments.
# AddZitadelProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a new Zitadel identity provider in the organization
# Bulk Add Project Role
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [project v2 service AddProjectRole](/reference/api/project/zitadel.project.v2.ProjectService.AddProjectRole) instead.
Add a list of roles to a project. The keys must be unique within the project.
# Bulk Delete Metadata
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 DeleteOrganizationMetadata](/reference/api/org/zitadel.org.v2.OrganizationService.DeleteOrganizationMetadata) instead.
Remove a list of metadata objects from an organization with a list of keys.
# Bulk Remove User Grants
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: [Delete authorizations one after the other](/reference/api/authorization/zitadel.authorization.v2.AuthorizationService.DeleteAuthorization) to remove access for multiple users on multiple owned or granted projects.
Remove a list of user grants (role assignments). The users will not be able to use the granted project anymore. Also, the roles will not be included in the tokens when requested.
Note: User grant refers to role assignments.
# Delete User Metadata By Key
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [DeleteUserMetadata](/reference/api/user/zitadel.user.v2.UserService.DeleteUserMetadata) instead.
Remove a list of metadata objects from a user with a list of keys.
# Bulk Set Organization Metadata
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 SetOrganizationMetadata](/reference/api/org/zitadel.org.v2.OrganizationService.SetOrganizationMetadata) instead.
This endpoint sets a list of metadata to the organization. Make sure the values are base64 encoded.
# Bulk Set User Metadata
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [SetUserMetadata](/reference/api/user/zitadel.user.v2.UserService.SetUserMetadata) instead.
Add or update multiple metadata values for a user. Make sure the values are base64 encoded.
# ClearFlow
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# CreateAction
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# DeactivateAction
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Deactivate Application
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [DeactivateApplication](/reference/api/application/zitadel.application.v2.ApplicationService.DeactivateApplication) instead to deactivate an app.
Set the state of an application to deactivated. It is not possible to request tokens for deactivated apps. Request returns an error if the application is already deactivated.
# Deactivate Organization
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 DeactivateOrganization](/reference/api/org/zitadel.org.v2.OrganizationService.DeactivateOrganization) instead.
Sets the state of my organization to deactivated. Users of this organization will not be able to log in.
# DeactivateOrgIDP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Deactivate Project
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [project v2 service DeactivateProject](/reference/api/project/zitadel.project.v2.ProjectService.DeactivateProject) instead.
Set the state of a project to deactivated. Request returns an error if the project is already deactivated.
# Deactivate Project Grant
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [DeactivateProjectGrant](/reference/api/project/zitadel.project.v2.ProjectService.DeactivateProjectGrant) instead.
Set the state of the project grant to deactivated. The grant has to be active to be able to deactivate.
# Deactivate User
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 DeactivateUser](/reference/api/user/zitadel.user.v2.UserService.DeactivateUser) instead.
The state of the user will be changed to 'deactivated'. The user will not be able to log in anymore.
The endpoint returns an error if the user is already in the state 'deactivated'.
Use deactivate user when the user should not be able to use the account anymore, but you still need access to the user data.
# Deactivate User Grant
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: [Deactivate an authorization](/reference/api/authorization/zitadel.authorization.v2.AuthorizationService.DeactivateAuthorization) to disable a user's access to an owned or granted project.
Deactivate the user grant. The user will not be able to use the granted project anymore. Also, the roles will not be included in the tokens when requested. An error will be returned if the user grant is already deactivated.
Note: User grant refers to role assignments.
# DeleteAction
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# DeleteProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Remove an identity provider
Will remove all linked providers of this configuration on the users
# Create Secret for Service Account
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 AddSecret](/reference/api/user/zitadel.user.v2.UserService.AddSecret) instead.
Create a new secret for a service account. It is used to authenticate the user (client credential grant).
# Generate Domain Verification
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 GenerateOrganizationDomainValidation](/reference/api/org/zitadel.org.v2.OrganizationService.GenerateOrganizationDomainValidation) instead.
Generate a new file to be able to verify your domain with DNS or HTTP challenge.
# GetAction
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Get Application By ID
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [GetApplication](/reference/api/application/zitadel.application.v2.ApplicationService.GetApplication) instead to fetch an app
Get an application of any type (OIDC, API, SAML).
# Get Application Key By ID
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [GetApplicationKey](/reference/api/application/zitadel.application.v2.ApplicationService.GetApplicationKey) instead to get an application key.
Returns an application key. Keys are used for authorizing API Applications.
# GetCustomDomainClaimedMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomInitMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomInviteUserMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomLoginTexts
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomPasswordChangeMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomPasswordResetMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomPasswordlessRegistrationMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomVerifyEmailMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomVerifyEmailOTPMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomVerifyPhoneMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetCustomVerifySMSOTPMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultDomainClaimedMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultInitMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultInviteUserMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultLabelPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultLockoutPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultLoginPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultLoginTexts
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultNotificationPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultPasswordAgePolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultPasswordChangeMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultPasswordComplexityPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultPasswordResetMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultPasswordlessRegistrationMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultPrivacyPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultVerifyEmailMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultVerifyEmailOTPMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultVerifyPhoneMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDefaultVerifySMSOTPMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetDomainPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetFlow
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Get Granted Project By ID
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [project v2 service ListProjectGrants](/reference/api/project/zitadel.project.v2.ProjectService.ListProjectGrants) instead.
Returns a project owned by another organization and granted to my organization. A Project is a vessel for different applications sharing the same role context.
# Get User Email (Human)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 GetUserByID](/reference/api/user/zitadel.user.v2.UserService.GetUserByID) instead.
Get the email address and the verification state of the address.
# Get User Phone (Human)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 GetUserByID](/reference/api/user/zitadel.user.v2.UserService.GetUserByID) instead.
Get the phone number and the verification state of the number. The phone number is only for informational purposes and to send messages, not for Authentication (2FA).
# Get User Profile (Human)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 GetUserByID](/reference/api/user/zitadel.user.v2.UserService.GetUserByID) instead.
Get basic information like first\_name and last\_name of a user.
# GetIAM
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetLabelPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetLockoutPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetLoginPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Get Service account Key By ID
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 ListUsers](/reference/api/user/zitadel.user.v2.UserService.ListUsers) instead.
Get a specific Key of a service account by its id. Machine keys are used to authenticate with jwt profile authentication.
# GetMyOrg
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetNotificationPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetOIDCInformation
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Get Organization By Domain
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization v2 service ListOrganizations](/reference/api/org/zitadel.org.v2.OrganizationService.ListOrganizations) instead.
Search an organization by the domain, overall organizations. The domain must match exactly.
# GetOrgIAMPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
deprecated: please use DomainPolicy instead
# GetOrgIDPByID
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Get Organization Metadata By Key
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 ListOrganizationMetadata](/reference/api/org/zitadel.org.v2.OrganizationService.ListOrganizationMetadata) instead.
Get a metadata object from an organization by a specific key.
# GetPasswordAgePolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetPasswordComplexityPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Get Personal-Access-Token (PAT) by ID
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 ListPersonalAccessTokens](/reference/api/user/zitadel.user.v2.UserService.ListPersonalAccessTokens) instead.
Returns the PAT for a user, currently only available for machine users/service accounts. PATs are ready-to-use tokens and can be sent directly in the authentication header.
# GetPreviewLabelPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# GetPrivacyPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Get Project By ID
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [project v2 service GetProject](/reference/api/project/zitadel.project.v2.ProjectService.GetProject) instead.
Returns a project owned by the organization (no granted projects). A Project is a vessel for different applications sharing the same role context.
# Project Grant By ID
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [ListProjectGrants](/reference/api/project/zitadel.project.v2.ProjectService.ListProjectGrants) instead.
Returns a project grant. A project grant is when the organization grants its project to another organization.
# GetProviderByID
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns an identity provider of the organization
# GetSupportedLanguages
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# User by ID
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 ListUsers with InUserIDQuery](/reference/api/user/zitadel.user.v2.UserService.ListUsers) instead.
Returns the full user or Service Account including the profile, email, etc.
# Get User by login name (globally)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 ListUsers with LoginNameQuery](/reference/api/user/zitadel.user.v2.UserService.ListUsers) instead.
Get a user by login name searched over all organizations. The request only returns data if the login name matches exactly.
# Get User Grant By ID
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: [List authorizations](/reference/api/authorization/zitadel.authorization.v2.AuthorizationService.ListAuthorizations) and filter by its ID.
Returns a user grant per ID. A user grant is a role a user has for a specific project and organization.
Note: User grant refers to role assignments.
# Get User Metadata By Key
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [ListUserMetadata](/reference/api/user/zitadel.user.v2.UserService.ListUserMetadata) instead.
Get a metadata object from a user by a specific key.
# Healthz
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Create/Import User (Human)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 UpdateHumanUser](/reference/api/user/zitadel.user.v2.UserService.UpdateHumanUser) instead.
Create/import a new user. The newly created user will get an initialization email if either the email address is not marked as verified or no password is set. If a password is set the user will not be requested to set a new one on the first login.
# Check for existing user
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 ListUsers](/reference/api/user/zitadel.user.v2.UserService.ListUsers) instead, is unique if no user returned.
Returns if a user with the requested email or username is unique. So you can create the user.
# ListActions
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Search Project Grants
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [ListProjectGrants](/reference/api/project/zitadel.project.v2.ProjectService.ListProjectGrants) instead.
Returns a list of project grants. A project grant is when the organization grants its project to another organization.
# ListAppChanges
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# List Application Keys
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [ListApplicationKeys](/reference/api/application/zitadel.application.v2.ApplicationService.ListApplicationKeys) instead to list application keys.
Search application keys. Keys are used for authorizing API Applications.
# Search Applications
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [ListApplications](/reference/api/application/zitadel.application.v2.ApplicationService.ListApplications) instead to list applications
Returns all applications within a project, that match the query.
# ListFlowTriggerTypes
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListFlowTypes
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Search Granted Project Roles
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [project v2 service ListProjectGrants](/reference/api/project/zitadel.project.v2.ProjectService.ListProjectGrants) instead.
Lists the roles a granted projects has. These are the roles, that have been granted by the owner organization to my organization.
# Search Granted Project
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [project v2 service ListProjects](/reference/api/project/zitadel.project.v2.ProjectService.ListProjects) instead.
Lists projects my organization got granted from another organization. A Project is a vessel for different applications sharing the same role context.
# Get User Authentication Factors (2FA/MFA)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 ListAuthenticationMethodTypes](/reference/api/user/zitadel.user.v2.UserService.ListAuthenticationMethodTypes) instead.
Get a list of authentication factors the user has set. Including Second Factors (2FA) and Multi-Factors (MFA).
# List Social Logins
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 ListLinkedIDPs](/reference/api/user/zitadel.user.v2.UserService.ListIDPLinks) instead.
Returns a list of all linked identity providers/social logins of the user. (e. Google, Microsoft, AzureAD, etc.).
# Search Passkey authentication
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 ListPasskeys](/reference/api/user/zitadel.user.v2.UserService.ListPasskeys) instead.
Get a list of configured passkey authentication methods from the user. Passkey is a device-dependent authentication like FingerScan, WindowsHello or a Hardware Token.
# ListLoginPolicyIDPs
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListLoginPolicyMultiFactors
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListLoginPolicySecondFactors
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# List Machine Keys
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 ListKeys](/reference/api/user/zitadel.user.v2.UserService.ListKeys) instead.
Get the list of keys of a service account. Machine keys are used to authenticate with jwt profile authentication.
# ListOrgChanges
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Search Domains
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 ListOrganizationDomains](/reference/api/org/zitadel.org.v2.OrganizationService.ListOrganizationDomains) instead.
Returns the list of registered domains of an organization. The domains are used to identify to which organization a user belongs.
# ListOrgIDPs
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListOrgMemberRoles
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# List Organization Members
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [ListAdministrators](/reference/api/internal_permission/zitadel.internal_permission.v2.InternalPermissionService.ListAdministrators) instead.
Members are users with permission to administrate Zitadel on different levels. This request returns all users with memberships on the organization level, matching the search queries. The search queries will be AND linked.
# Search Organization Metadata
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 ListOrganizationMetadata](/reference/api/org/zitadel.org.v2.OrganizationService.ListOrganizationMetadata) instead.
Get the metadata of an organization filtered by your query.
# List Personal-Access-Tokens (PATs)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 ListPersonalAccessTokens](/reference/api/user/zitadel.user.v2.UserService.ListPersonalAccessTokens) instead.
Returns a list of PATs for a user, currently only available for machine users/service accounts. PATs are ready-to-use tokens and can be sent directly in the authentication header.
# ListProjectChanges
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListProjectGrantChanges
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ListProjectGrantMemberRoles
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# List Project Grant Members
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [ListAdministrators](/reference/api/internal_permission/zitadel.internal_permission.v2.InternalPermissionService.ListAdministrators) instead.
Members are users with permission to administrate Zitadel on different levels. This request returns all users with memberships on the project grant level, matching the search queries. The search queries will be AND linked.
# Search Project Grants from Project
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [ListProjectGrants](/reference/api/project/zitadel.project.v2.ProjectService.ListProjectGrants) instead.
Returns a list of project grants for a specific project. A project grant is when the organization grants its project to another organization.
# ListProjectMemberRoles
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# List Project Members
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [ListAdministrators](/reference/api/internal_permission/zitadel.internal_permission.v2.InternalPermissionService.ListAdministrators) instead.
Members are users with permission to administrate Zitadel on different levels. This request returns all users with memberships on the project level, matching the search queries. The search queries will be AND linked.
# Search Project Roles
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [project v2 service ListProjectRoles](/reference/api/project/zitadel.project.v2.ProjectService.ListProjectRoles) instead.
Returns all roles of a project matching the search query.
# Search Project
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [project v2 service ListProjects](/reference/api/project/zitadel.project.v2.ProjectService.ListProjects) instead.
Lists projects my organization is the owner of (no granted projects). A Project is a vessel for different applications sharing the same role context.
# ListProviders
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns all identity providers, which match the query
Limit should always be set, there is a default limit set by the service
# ListUserChanges
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Search User Grants
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: [List authorizations](/reference/api/authorization/zitadel.authorization.v2.AuthorizationService.ListAuthorizations) and pass the user ID filter to search for a users grants on owned or granted projects.
Returns a list of user grants that match the search queries. User grants/role assignments are the roles users have for a specific project and organization.
Note: User grant refers to role assignments.
# List Zitadel Permissions
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [ListAdministrators](/reference/api/internal_permission/zitadel.internal_permission.v2.InternalPermissionService.ListAdministrators) instead.
Show all the permissions the user has in Zitadel (ZITADEL Manager).
# Search User Metadata
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [ListUserMetadata](/reference/api/user/zitadel.user.v2.UserService.ListUserMetadata) instead.
Get the metadata of a user filtered by your query.
# Search Users
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 ListUsers](/reference/api/user/zitadel.user.v2.UserService.ListUsers) instead.
Search for users within an organization. By default, we will return users of your organization. Make sure to include a limit and sorting for pagination.
# Lock User
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 LockUser](/reference/api/user/zitadel.user.v2.UserService.LockUser) instead.
The state of the user will be changed to 'locked'. The user will not be able to log in anymore.
The endpoint returns an error if the user is already in the state 'locked'.
Use this endpoint if the user should not be able to log in temporarily because of an event that happened (wrong password, etc.).
# MigrateGenericOIDCProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Migrate an existing OIDC identity provider in the organization
# ReactivateAction
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Reactivate Application
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [ReactivateApplication](/reference/api/application/zitadel.application.v2.ApplicationService.ReactivateApplication) instead to reactivate an app.
Set the state of an application to active. Request returns an error if the application is not deactivated.
# Reactivate Organization
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 ActivateOrganization](/reference/api/org/zitadel.org.v2.OrganizationService.ActivateOrganization) instead.
Set the state of my organization to active. The state of the organization has to be deactivated to perform the request. Users of this organization will be able to log in again.
# ReactivateOrgIDP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Reactivate Project
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [project v2 service ActivateProject](/reference/api/project/zitadel.project.v2.ProjectService.ActivateProject) instead.
Set the state of a project to active. Request returns an error if the project is not deactivated.
# Reactivate Project Grant
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [ActivateProjectGrant](/reference/api/project/zitadel.project.v2.ProjectService.ActivateProjectGrant) instead.
Set the state of the project grant to active. The grant has to be deactivated to be able to reactivate.
# Deactivate User
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 ReactivateUser](/reference/api/user/zitadel.user.v2.UserService.ReactivateUser) instead.
Reactivate a user with the state 'deactivated'. The user will be able to log in again afterward.
The endpoint returns an error if the user is not in the state 'deactivated'.
# Reactivate User Grant
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: [Activate an authorization](/reference/api/authorization/zitadel.authorization.v2.AuthorizationService.ActivateAuthorization) to enable a user's access to an owned or granted project.
Reactivate a deactivated user grant. The user will be able to use the granted project again. An error will be returned if the user grant is not deactivated.
Note: User grant refers to role assignments.
# Generate New API Client Secret
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [GenerateClientSecret](/reference/api/application/zitadel.application.v2.ApplicationService.GenerateClientSecret) instead to (re-)generate an API app client secret
Generates a new client secret for the API application, make sure to save the response.
# Generate New OIDC Client Secret
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [GenerateClientSecret](/reference/api/application/zitadel.application.v2.ApplicationService.GenerateClientSecret) instead to (re-)generate an OIDC app client secret.
Generates a new client secret for the OIDC application, make sure to save the response.
# RegenerateSAMLProviderCertificate
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Regenerate certificate for an existing SAML identity provider in the organization
# Remove Application
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [DeleteApplication](/reference/api/application/zitadel.application.v2.ApplicationService.DeleteApplication) instead to delete an app.
Remove an application. It is not possible to request tokens for removed apps. Request returns an error if the application is already deactivated.
# Delete Application Key
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [DeleteApplicationKey](/reference/api/application/zitadel.application.v2.ApplicationService.DeleteApplicationKey) instead to delete an application key.
Remove an application key. The API application will not be able to authorize with the key anymore.
# RemoveCustomLabelPolicyFont
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RemoveCustomLabelPolicyIcon
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RemoveCustomLabelPolicyIconDark
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RemoveCustomLabelPolicyLogo
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# RemoveCustomLabelPolicyLogoDark
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Remove Multi-Factor OTP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 RemoveTOTP](/reference/api/user/zitadel.user.v2.UserService.RemoveTOTP) instead.
Remove the configured OTP as a factor from the user. OTP is an authentication app, like Authy or Google/Microsoft Authenticator.
# Remove Multi-Factor OTP Email
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 RemoveOTPEmail](/reference/api/user/zitadel.user.v2.UserService.RemoveOTPEmail) instead.
Remove the configured OTP Email as a factor from the user. As only one OTP Email per user is allowed, the user will not have OTP Email as a second factor afterward.
# Remove Multi-Factor OTP SMS
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 RemoveOTPSMS](/reference/api/user/zitadel.user.v2.UserService.RemoveOTPSMS) instead.
Remove the configured OTP SMS as a factor from the user. As only one OTP SMS per user is allowed, the user will not have OTP SMS as a second factor afterward.
# Remove Multi-Factor U2F
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 RemoveU2F](/reference/api/user/zitadel.user.v2.UserService.RemoveU2F) instead.
Remove the configured Universal Second Factor (U2F) as a factor from the user. U2F is a device-dependent factor like FingerPrint, Windows-Hello, etc.
# Delete User Avatar (Human)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Removes the avatar that is currently set on the user.
# Remove Social Login
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 RemoveIDPLink](/reference/api/user/zitadel.user.v2.UserService.RemoveIDPLink) instead.
Remove a configured social logins/identity providers of the user (e.g. Google, Microsoft, AzureAD, etc.). The user will not be able to log in with the given provider afterward. Make sure the user does have other possibilities to authenticate.
# Delete Passkey
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 RemovePasskey](/reference/api/user/zitadel.user.v2.UserService.RemovePasskey) instead.
Remove a configured passkey authentication method from the user. (e.g FaceID, FingerScane, WindowsHello, etc.).
# Remove User Phone (Human)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use user service v2 [user service v2 SetPhone](/reference/api/user/zitadel.user.v2.UserService.SetPhone) instead.
Remove the configured phone number of a user.
# RemoveIDPFromLoginPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Delete Key for service account
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 RemoveKey](/reference/api/user/zitadel.user.v2.UserService.RemoveKey) instead.
Delete a specific key from a user.
The user will not be able to authenticate with that key afterward.
# Delete Secret of Service Account
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 RemoveSecret](/reference/api/user/zitadel.user.v2.UserService.RemoveSecret) instead.
Delete a secret of a service account. The user will not be able to authenticate with the secret afterward.
# RemoveMultiFactorFromLoginPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Delete Organization
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 DeleteOrganization](/reference/api/org/zitadel.org.v2.OrganizationService.DeleteOrganization) instead.
Deletes my organization and all its resources (Users, Projects, Grants to and from the org). Users of this organization will not be able to log in.
# Remove Domain
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 DeleteOrganizationDomain](/reference/api/org/zitadel.org.v2.OrganizationService.DeleteOrganizationDomain) instead.
Delete a new domain from an organization. The domains are used to identify to which organization a user belongs. If the uses use the domain for login, this will not be possible afterwards. They have to use another domain instead.
# RemoveOrgIDP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Remove Organization Member
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [DeleteAdministrator](/reference/api/internal_permission/zitadel.internal_permission.v2.InternalPermissionService.DeleteAdministrator) instead.
Members are users with permission to administrate Zitadel on different levels. This request removes a user from the members list on an instance level. The user can still have roles on another level (iam, project).
# Delete Organization Metadata By Key
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 DeleteOrganizationMetadata](/reference/api/org/zitadel.org.v2.OrganizationService.DeleteOrganizationMetadata) instead.
Remove a metadata object from an organization with a specific key.
# Remove a Personal-Access-Token (PAT) by ID
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 RemovePersonalAccessToken](/reference/api/user/zitadel.user.v2.UserService.RemovePersonalAccessToken) instead.
Delete a PAT from a user. Afterward, the user will not be able to authenticate with that token anymore.
# Remove Project
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [project v2 service DeleteProject](/reference/api/project/zitadel.project.v2.ProjectService.DeleteProject) instead.
Set the state of a project to active. Request returns an error if the project is not deactivated.
# Remove Project Grant
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [DeleteProjectGrant](/reference/api/project/zitadel.project.v2.ProjectService.DeleteProjectGrant) instead.
Remove a project grant. All user grants (role assignments) for this project grant will also be removed. A user will not have access to the project afterward (if permissions are checked).
# Remove Project Grant Member
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [DeleteAdministrator](/reference/api/internal_permission/zitadel.internal_permission.v2.InternalPermissionService.DeleteAdministrator) instead.
Members are users with permission to administrate Zitadel on different levels. This request removes a user from the members list on a project grant level. The user can still have roles on another level (iam, organization, project).
# Remove Project Member
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [DeleteAdministrator](/reference/api/internal_permission/zitadel.internal_permission.v2.InternalPermissionService.DeleteAdministrator) instead.
Members are users with permission to administrate Zitadel on different levels. This request removes a user from the members list on an project level. The user can still have roles on another level (iam, organization).
# Remove Project Role
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [project v2 service RemoveProjectRole](/reference/api/project/zitadel.project.v2.ProjectService.RemoveProjectRole) instead.
Removes the role from the project and on every resource it has a dependency. This includes project grants and user grants (role assignments).
# RemoveSecondFactorFromLoginPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Unlock User
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 DeleteUser](/reference/api/user/zitadel.user.v2.UserService.DeleteUser) instead.
The state of the user will be changed to 'deleted'. The user will not be able to log in anymore. Endpoints requesting this user will return an error 'User not found.
# Remove User Grant
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: [Delete an authorization](/reference/api/authorization/zitadel.authorization.v2.AuthorizationService.DeleteAuthorization) to remove a users access to an owned or granted project.
Removes the user grant from the user. The user will not be able to use the granted project anymore. Also, the roles will not be included in the tokens when requested.
Note: User grant refers to role assignments.
# Delete User Metadata By Key
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [DeleteUserMetadata](/reference/api/user/zitadel.user.v2.UserService.DeleteUserMetadata) instead.
Get a metadata object from a user by a specific key.
# Resend User Email Verification
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 ResendEmailCode](/reference/api/user/zitadel.user.v2.UserService.ResendEmailCode) instead.
Resend the email verification notification to the given email address of the user.
# Resend User Initialization Email
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: not used anymore in user state so will be removed.
A newly created user will get an initialization email to verify the email address and set a password. Resend the email with this request to the user's email address, or a newly added address.
# Resend User Phone Verification
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use user service v2 [user service v2 ResendPhoneCode](/reference/api/user/zitadel.user.v2.UserService.ResendPhoneCode) instead.
Resend the notification for the verification of the phone number, to the number stored on the user.
# ResetCustomDomainClaimedMessageTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomInitMessageTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomInviteUserMessageTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomLoginTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomPasswordChangeMessageTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomPasswordResetMessageTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomPasswordlessRegistrationMessageTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomVerifyEmailMessageTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomVerifyEmailOTPMessageTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomVerifyPhoneMessageTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetCustomVerifySMSOTPMessageTextToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetLabelPolicyToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetLockoutPolicyToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetLoginPolicyToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetNotificationPolicyToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetPasswordAgePolicyToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetPasswordComplexityPolicyToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# ResetPrivacyPolicyToDefault
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Send Reset Password Notification
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 PasswordReset](/reference/api/user/zitadel.user.v2.UserService.PasswordReset) instead.
The user will receive an email with a link to change the password.
# Send Passkey Registration Link
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 RegisterPasskey](/reference/api/user/zitadel.user.v2.UserService.RegisterPasskey) instead.
Adds a new passkey authenticator link to the user and sends it to the user per email. The link enables the user to register a new device if current passkey devices are all platform authenticators. e.g. User has already registered Windows Hello and wants to register FaceID on the iPhone.
# SetCustomDomainClaimedMessageCustomText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetCustomInitMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetCustomInviteUserMessageCustomText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetCustomLoginText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetCustomPasswordChangeMessageCustomText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetCustomPasswordResetMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetCustomPasswordlessRegistrationMessageCustomText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetCustomVerifyEmailMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetCustomVerifyEmailOTPMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetCustomVerifyPhoneMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetCustomVerifySMSOTPMessageText
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Set Human Initial Password
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 SetPassword](/reference/api/user/zitadel.user.v2.UserService.SetPassword) instead.
# Set User Password
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 SetPassword](/reference/api/user/zitadel.user.v2.UserService.SetPassword) instead.
# Set Organization Metadata
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 SetOrganizationMetadata](/reference/api/org/zitadel.org.v2.OrganizationService.SetOrganizationMetadata) instead.
This endpoint either adds or updates a metadata value for the requested key. Make sure the value is base64 encoded.
# SetPrimaryOrgDomain
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# SetTriggerActions
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Set User Metadata
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [SetUserMetadata](/reference/api/user/zitadel.user.v2.UserService.SetUserMetadata) instead.
This endpoint either adds or updates a metadata value for the requested key. Make sure the value is base64 encoded.
# Unlock User
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 UnlockUser](/reference/api/user/zitadel.user.v2.UserService.UnlockUser) instead.
Unlock a user with the state 'locked'. The user will be able to log in again afterward.
The endpoint returns an error if the user is not in the state 'locked'.
# Update API Application Config
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [PatchApplication](/reference/api/application/zitadel.application.v2.ApplicationService.UpdateApplication) instead to update the config of an API app.
Update the OIDC-specific configuration of an application.
# UpdateAction
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Update Application
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [PatchApplication](/reference/api/application/zitadel.application.v2.ApplicationService.UpdateApplication) instead to update the generic params of an app.
Update the basic information of an application. This doesn't include information that are dependent on the application type (OIDC, API, SAML)
# UpdateAppleProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing Apple identity provider in the organization
# UpdateAzureADProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing Azure AD identity provider in the organization
# UpdateCustomLabelPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateCustomLockoutPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateCustomLoginPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateCustomNotificationPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateCustomPasswordAgePolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateCustomPasswordComplexityPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateCustomPrivacyPolicy
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateGenericOAuthProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing OAuth2 identity provider in the organization
# UpdateGenericOIDCProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing OIDC identity provider in the organization
# UpdateGitHubEnterpriseServerProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing GitHub Enterprise Server identity provider in the organization
# UpdateGitHubProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing GitHub identity provider in the organization
# UpdateGitLabProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing GitLab identity provider in the organization
# UpdateGitLabSelfHostedProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing self hosted GitLab identity provider in the organization
# UpdateGoogleProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing Google identity provider in the organization
# Update User Email (Human)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 SetEmail](/reference/api/user/zitadel.user.v2.UserService.SetEmail) instead.
Change the email address of a user. If the state is set to not verified, the user will get a verification email.
# Update User Phone (Human)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 UpdateUser](/reference/api/user/zitadel.user.v2.UserService.UpdateUser) instead.
Change the phone number of a user. If the state is set to not verified, the user will get an SMS to verify (if a notification provider is configured). The phone number is only for informational purposes and to send messages, not for Authentication (2FA).
# Update User Profile (Human)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 UpdateHumanUser](/reference/api/user/zitadel.user.v2.UserService.UpdateHumanUser) instead.
Update the profile information from a user. The profile includes basic information like first\_name and last\_name.
# UpdateJWTProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing JWT identity provider in the organization
# UpdateLDAPProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing LDAP identity provider in the organization
# Update Service Account
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 UpdateUser](/reference/api/user/zitadel.user.v2.UserService.UpdateUser) instead.
Change a service account/service account. It is used for accounts with non-interactive authentication possibilities.
# Update OIDC Application Config
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [PatchApplication](/reference/api/application/zitadel.application.v2.ApplicationService.UpdateApplication) instead to update the config of an OIDC app.
Update the OIDC specific configuration of an application.
# Update Organization
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 UpdateOrganization](/reference/api/org/zitadel.org.v2.OrganizationService.UpdateOrganization) instead.
Change the name of the organization.
# UpdateOrgIDP
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateOrgIDPJWTConfig
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# UpdateOrgIDPOIDCConfig
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Update Organization Member
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [UpdateAdministrator](/reference/api/internal_permission/zitadel.internal_permission.v2.InternalPermissionService.UpdateAdministrator) instead.
Members are users with permission to administrate Zitadel on different levels. This request changes the roles of an existing member. The whole roles list will be updated. Make sure to include roles that you don't want to change (remove).
# Update Project
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [project v2 service UpdateProject](/reference/api/project/zitadel.project.v2.ProjectService.UpdateProject) instead.
Update a project and its settings. A Project is a vessel for different applications sharing the same role context.
# Change Project Grant
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [UpdateProjectGrant](/reference/api/project/zitadel.project.v2.ProjectService.UpdateProjectGrant) instead.
Change the roles of the project that is granted to another organization. The project grant will allow the granted organization to access the project and manage the role assignments for its users. Project Grant will be listed in the granted project of the granted organization.
# Update Project Grant Member
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [UpdateAdministrator](/reference/api/internal_permission/zitadel.internal_permission.v2.InternalPermissionService.UpdateAdministrator) instead.
Members are users with permission to administrate Zitadel on different levels. This request changes the roles of an existing member. The whole roles list will be updated. Make sure to include roles that you don't want to change (remove).
# Update Project Member
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [UpdateAdministrator](/reference/api/internal_permission/zitadel.internal_permission.v2.InternalPermissionService.UpdateAdministrator) instead.
Members are users with permission to administrate Zitadel on different levels. This request changes the roles of an existing member. The whole roles list will be updated. Make sure to include roles that you don't want to change (remove).
# Change Project Role
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [project v2 service UpdateProjectRole](/reference/api/project/zitadel.project.v2.ProjectService.UpdateProjectRole) instead.
Change a project role. The key is not editable. If a key should change, remove the role and create a new one.
# Update SAML Application Config
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: Use [PatchApplication](/reference/api/application/zitadel.application.v2.ApplicationService.UpdateApplication) instead to update the config of a SAML app.
Update the SAML specific configuration of an application.
# UpdateSAMLProvider
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Change an existing SAML identity provider in the organization
# Update User Grant
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: [Update an authorization](/reference/api/authorization/zitadel.authorization.v2.AuthorizationService.UpdateAuthorization) to update a user's roles on an owned or granted project.
Update the roles of a user grant. User grants are the roles users have for a specific project and organization.
Note: User grant refers to role assignments.
# Change user name
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [user service v2 UpdateUser](/reference/api/user/zitadel.user.v2.UserService.UpdateUser) instead.
Change the username of the user. Be aware that the user has to log in with the newly added username afterward.
# Verify Domain
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deprecated: use [organization service v2 VerifyOrganizationDomain](/reference/api/org/zitadel.org.v2.OrganizationService.VerifyOrganizationDomain) instead.
Make sure you have added the required verification to your domain, depending on the method you have chosen (HTTP or DNS challenge). Zitadel will check it and set the domain as verified if it was successful. A verify domain has to be unique.
# machine jwt profile grant benchmark of zitadel v2.65.0
Tests are halted after this test run because of too many [client read events](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/wait-event.clientread.html) on the database.
Performance test results [#performance-test-results]
| Metric | Value |
| :------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Baseline | none |
| Test start | 22-10-2024 16:20 UTC |
| Test duration | 30min |
| Executed test | machine\_jwt\_profile\_grant |
| k6 version | v0.54.0 |
| VUs | 50 |
| Client location | US1 |
| Client machine specification | e2-high-cpu-4 |
| Zitadel location | US1 |
| Zitadel container specification | vCPUs: 2 Memory: 512 MiB Container count: 2 |
| Zitadel feature flags | none |
| Database | postgres v15 |
| Database location | US1 |
| Database specification | vCPUs: 4 Memory: 16 GiB |
| Zitadel metrics during test | |
| Observed errors | Many client read events during push |
| Top 3 most expensive database queries | 1: Query events `instance_id = $1 AND aggregate_type = $2 AND aggregate_id = $3 AND event_type = ANY($4)` 2: latest sequence query during push events 3: writing events during push (caused lock wait events) |
| k6 iterations per second | 193 |
| k6 overview | [output](#k6-output) |
| flowchart outcome | Halt tests, must resolve an issue |
/token endpoint latencies [#token-endpoint-latencies]
k6 output [#k-6-output]
```bash
checks...............................: 100.00% ✓ 695739 ✗ 0
data_received........................: 479 MB 265 kB/s
data_sent............................: 276 MB 153 kB/s
http_req_blocked.....................: min=178ns avg=5µs max=119.8ms p(50)=460ns p(95)=702ns p(99)=921ns
http_req_connecting..................: min=0s avg=1.24µs max=43.45ms p(50)=0s p(95)=0s p(99)=0s
http_req_duration....................: min=18ms avg=255.3ms max=1.22s p(50)=241.56ms p(95)=479.19ms p(99)=600.92ms
{ expected_response:true }.........: min=18ms avg=255.3ms max=1.22s p(50)=241.56ms p(95)=479.19ms p(99)=600.92ms
http_req_failed......................: 0.00% ✓ 0 ✗ 347998
http_req_receiving...................: min=25.92µs avg=536.96µs max=401.94ms p(50)=89.44µs p(95)=2.39ms p(99)=11.12ms
http_req_sending.....................: min=24.01µs avg=63.86µs max=4.48ms p(50)=60.97µs p(95)=88.69µs p(99)=141.74µs
http_req_tls_handshaking.............: min=0s avg=2.8µs max=51.05ms p(50)=0s p(95)=0s p(99)=0s
http_req_waiting.....................: min=17.65ms avg=254.7ms max=1.22s p(50)=240.88ms p(95)=478.6ms p(99)=600.6ms
http_reqs............................: 347998 192.80552/s
iteration_duration...................: min=33.86ms avg=258.77ms max=1.22s p(50)=245ms p(95)=482.61ms p(99)=604.32ms
iterations...........................: 347788 192.689171/s
login_ui_enter_login_name_duration...: min=218.61ms avg=218.61ms max=218.61ms p(50)=218.61ms p(95)=218.61ms p(99)=218.61ms
login_ui_enter_password_duration.....: min=18ms avg=18ms max=18ms p(50)=18ms p(95)=18ms p(99)=18ms
login_ui_init_login_duration.........: min=90.96ms avg=90.96ms max=90.96ms p(50)=90.96ms p(95)=90.96ms p(99)=90.96ms
login_ui_token_duration..............: min=140.02ms avg=140.02ms max=140.02ms p(50)=140.02ms p(95)=140.02ms p(99)=140.02ms
oidc_token_duration..................: min=29.85ms avg=255.38ms max=1.22s p(50)=241.61ms p(95)=479.23ms p(99)=600.95ms
org_create_org_duration..............: min=64.51ms avg=64.51ms max=64.51ms p(50)=64.51ms p(95)=64.51ms p(99)=64.51ms
user_add_machine_key_duration........: min=44.93ms avg=87.89ms max=159.52ms p(50)=84.43ms p(95)=144.59ms p(99)=155.54ms
user_create_machine_duration.........: min=65.75ms avg=266.53ms max=421.58ms p(50)=276.59ms p(95)=380.84ms p(99)=414.43ms
vus..................................: 0 min=0 max=50
vus_max..............................: 50 min=50 max=50
running (30m04.9s), 00/50 VUs, 347788 complete and 0 interrupted iterations
default ✓ [======================================] 50 VUs 30m0s
```
# machine jwt profile grant benchmark of zitadel v2.66.0
The tests showed heavy database load by time by the first two database queries. These queries need to be analyzed further.
Performance test results [#performance-test-results]
| Metric | Value |
| :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Baseline | none |
| Purpose | Test current performance |
| Test start | 15:39 UTC |
| Test duration | 30min |
| Executed test | machine\_jwt\_profile\_grant |
| k6 version | v0.54.0 |
| VUs | 150 |
| Client location | US1 |
| ZITADEL location | US1 |
| ZITADEL container specification | vCPU: 2 Memory: 512 Mib Container count: 5 |
| ZITADEL Version | v2.66.0 |
| ZITADEL feature flags | webKey: true, improvedPerformance: \["IMPROVED\_PERFORMANCE\_ORG\_BY\_ID", "IMPROVED\_PERFORMANCE\_PROJECT", "IMPROVED\_PERFORMANCE\_USER\_GRANT", "IMPROVED\_PERFORMANCE\_ORG\_DOMAIN\_VERIFIED", "IMPROVED\_PERFORMANCE\_PROJECT\_GRANT"] |
| Database | type: psql version: v15.8 |
| Database location | US1 |
| Database specification | vCPU: 8 memory: 32Gib |
| ZITADEL metrics during test | |
| Observed errors | |
| Top 3 most expensive database queries | 1: Write events using the newly added eventstore.push function 2: Query events by instance\_id, aggregate\_type, aggregate\_id, event\_types 3: Query user |
| k6 Iterations per second | 439 |
| k6 output | [output](#k6-output) |
| flowchart outcome | Scale out |
Endpoint latencies [#endpoint-latencies]
k6 output [#k-6-output]
```bash
✓ openid configuration
✗ token status ok
↳ 99% — ✓ 790655 / ✗ 5
✗ access token returned
↳ 99% — ✓ 790655 / ✗ 5
█ setup
✓ user defined
✓ authorize status ok
✓ login name status ok
✓ login shows password page
✓ password status ok
✓ password callback
✓ code set
✓ token status ok
✓ access token created
✓ id token created
✓ info created
✓ org created
✓ create user is status ok
✓ generate machine key status ok
█ teardown
✓ org removed
checks...............................: 99.99% ✓ 1581773 ✗ 10
data_received........................: 1.1 GB 623 kB/s
data_sent............................: 628 MB 347 kB/s
http_req_blocked.....................: min=167ns avg=20.68µs max=493.59ms p(50)=468ns p(95)=717ns p(99)=928ns
http_req_connecting..................: min=0s avg=10.06µs max=388.27ms p(50)=0s p(95)=0s p(99)=0s
http_req_duration....................: min=17.71ms avg=337.34ms max=16.27s p(50)=249.03ms p(95)=888.75ms p(99)=1.4s
{ expected_response:true }.........: min=17.71ms avg=337.29ms max=3.56s p(50)=249.03ms p(95)=888.7ms p(99)=1.4s
http_req_failed......................: 0.00% ✓ 5 ✗ 791265
http_req_receiving...................: min=25.49µs avg=1.58ms max=539.43ms p(50)=89.69µs p(95)=7.55ms p(99)=23.46ms
http_req_sending.....................: min=22.7µs avg=69.14µs max=480.23ms p(50)=59.16µs p(95)=85.15µs p(99)=129.88µs
http_req_tls_handshaking.............: min=0s avg=9.38µs max=98.15ms p(50)=0s p(95)=0s p(99)=0s
http_req_waiting.....................: min=15.11ms avg=335.69ms max=16.26s p(50)=246.91ms p(95)=888.27ms p(99)=1.4s
http_reqs............................: 791270 437.256468/s
iteration_duration...................: min=32.28ms avg=341.46ms max=16.27s p(50)=253ms p(95)=892.49ms p(99)=1.41s
iterations...........................: 790660 436.919382/s
login_ui_enter_login_name_duration...: min=179.27ms avg=179.27ms max=179.27ms p(50)=179.27ms p(95)=179.27ms p(99)=179.27ms
login_ui_enter_password_duration.....: min=17.71ms avg=17.71ms max=17.71ms p(50)=17.71ms p(95)=17.71ms p(99)=17.71ms
login_ui_init_login_duration.........: min=77.66ms avg=77.66ms max=77.66ms p(50)=77.66ms p(95)=77.66ms p(99)=77.66ms
login_ui_token_duration..............: min=86.79ms avg=86.79ms max=86.79ms p(50)=86.79ms p(95)=86.79ms p(99)=86.79ms
oidc_token_duration..................: min=28.38ms avg=337.54ms max=16.27s p(50)=249.17ms p(95)=889.01ms p(99)=1.4s
org_create_org_duration..............: min=44.94ms avg=44.94ms max=44.94ms p(50)=44.94ms p(95)=44.94ms p(99)=44.94ms
user_add_machine_key_duration........: min=38.11ms avg=66.64ms max=160.59ms p(50)=60.28ms p(95)=104.99ms p(99)=112.5ms
user_create_machine_duration.........: min=37.12ms avg=122.76ms max=1.03s p(50)=78.25ms p(95)=266.95ms p(99)=306.94ms
vus..................................: 150 min=0 max=150
vus_max..............................: 150 min=150 max=150
running (30m09.6s), 000/150 VUs, 790660 complete and 0 interrupted iterations
default ✓ [======================================] 150 VUs 30m0s
```
# machine jwt profile grant benchmark of zitadel v2.70.0
The performance goals of [this issue](https://github.com/zitadel/zitadel/issues/8352) are reached. Next we will test linear scalability.
Performance test results [#performance-test-results]
| Metric | Value |
| :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Baseline | none |
| Purpose | Test current performance |
| Test start | 09:48 UTC |
| Test duration | 30min |
| Executed test | machine\_jwt\_profile\_grant |
| k6 version | v0.57.0 |
| VUs | 600 |
| Client location | US1 |
| ZITADEL location | US1 |
| ZITADEL container specification | vCPU: 6 Memory: 6 Gi Container min scale: 2 Container max scale: 7 |
| ZITADEL Version | v2.70.0 |
| ZITADEL feature flags | webKey: true, improvedPerformance: \["IMPROVED\_PERFORMANCE\_ORG\_BY\_ID", "IMPROVED\_PERFORMANCE\_PROJECT", "IMPROVED\_PERFORMANCE\_USER\_GRANT", "IMPROVED\_PERFORMANCE\_ORG\_DOMAIN\_VERIFIED", "IMPROVED\_PERFORMANCE\_PROJECT\_GRANT"] |
| Database | type: psql version: v17.2 |
| Database location | US1 |
| Database specification | vCPU: 8 memory: 32Gib |
| ZITADEL metrics during test | |
| Observed errors | |
| Top 3 most expensive database queries | 1: Write events using eventstore.push function 2: Query user 3: Query events by instance id, aggregate type, aggregate id, event types, owner |
| k6 Iterations per second | 1806 |
| k6 output | [output](#k6-output) |
| flowchart outcome | Scale out |
Endpoint latencies [#endpoint-latencies]
k6 output [#k-6-output]
```bash
✓ openid configuration
✗ token status ok
↳ 99% — ✓ 3289559 / ✗ 5
✗ access token returned
↳ 99% — ✓ 3289559 / ✗ 5
█ setup
✓ user defined
✓ authorize status ok
✓ login name status ok
✓ login shows password page
✓ password status ok
✓ password callback
✓ code set
✓ token status ok
✓ access token created
✓ id token created
✓ info created
✓ org created
✓ create user is status ok
✓ generate machine key status ok
█ teardown
✓ org removed
checks...............................: 99.99% 6580931 out of 6580941
data_received........................: 4.8 GB 2.6 MB/s
data_sent............................: 2.8 GB 1.5 MB/s
http_req_blocked.....................: min=110ns avg=53.66µs max=937.62ms p(50)=420ns p(95)=660ns p(99)=989ns
http_req_connecting..................: min=0s avg=22.26µs max=532.7ms p(50)=0s p(95)=0s p(99)=0s
http_req_duration....................: min=16.42ms avg=323.71ms max=3.4s p(50)=209.85ms p(95)=903.86ms p(99)=1.01s
{ expected_response:true }.........: min=16.42ms avg=323.71ms max=3.4s p(50)=209.85ms p(95)=903.85ms p(99)=1.01s
http_req_failed......................: 0.00% 5 out of 3291974
http_req_receiving...................: min=18.23µs avg=1.73ms max=913.21ms p(50)=73.15µs p(95)=8.73ms p(99)=29.35ms
http_req_sending.....................: min=17.54µs avg=53.13µs max=501.66ms p(50)=46.07µs p(95)=75.92µs p(99)=120.24µs
http_req_tls_handshaking.............: min=0s avg=29.45µs max=657.29ms p(50)=0s p(95)=0s p(99)=0s
http_req_waiting.....................: min=3.84ms avg=321.92ms max=3.4s p(50)=206.45ms p(95)=901.97ms p(99)=1.01s
http_reqs............................: 3291974 1807.469453/s
iteration_duration...................: min=18.62ms avg=328.21ms max=3.41s p(50)=215.77ms p(95)=907.48ms p(99)=1.01s
iterations...........................: 3289564 1806.146234/s
login_ui_enter_login_name_duration...: min=131.65ms avg=131.65ms max=131.65ms p(50)=131.65ms p(95)=131.65ms p(99)=131.65ms
login_ui_enter_password_duration.....: min=18.55ms avg=18.55ms max=18.55ms p(50)=18.55ms p(95)=18.55ms p(99)=18.55ms
login_ui_init_login_duration.........: min=68.72ms avg=68.72ms max=68.72ms p(50)=68.72ms p(95)=68.72ms p(99)=68.72ms
login_ui_token_duration..............: min=77.56ms avg=77.56ms max=77.56ms p(50)=77.56ms p(95)=77.56ms p(99)=77.56ms
oidc_token_duration..................: min=16.42ms avg=323.79ms max=3.4s p(50)=209.87ms p(95)=903.91ms p(99)=1.01s
org_create_org_duration..............: min=38.42ms avg=38.42ms max=38.42ms p(50)=38.42ms p(95)=38.42ms p(99)=38.42ms
user_add_machine_key_duration........: min=23.47ms avg=280.32ms max=851.94ms p(50)=304.18ms p(95)=437.53ms p(99)=443.37ms
user_create_machine_duration.........: min=63.91ms avg=406.02ms max=663.96ms p(50)=437.61ms p(95)=518.85ms p(99)=521.88ms
vus..................................: 425 min=0 max=600
vus_max..............................: 600 min=600 max=600
running (30m21.3s), 000/600 VUs, 3289564 complete and 0 interrupted iterations
default ✓ [======================================] 600 VUs 30m0s
```
# oidc session benchmark of Zitadel v2.70.0
The test implements [Support for (OIDC) Standard in a Custom Login UI flow](https://zitadel.com/docs/guides/integrate/login-ui/oidc-standard).
The tests showed that querying the user takes too much time because Zitadel ensures the projection is up to date. This performance bottleneck must be resolved.
Performance test results [#performance-test-results]
| Metric | Value |
| :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Baseline | none |
| Purpose | Test current performance |
| Test start | 14:24 UTC |
| Test duration | 30min |
| Executed test | oidc\_session |
| k6 version | v0.57.0 |
| VUs | 600 |
| Client location | US1 |
| ZITADEL location | US1 |
| ZITADEL container specification | vCPU: 6 Memory: 6 Gi Container min scale: 2 Container max scale: 7 |
| ZITADEL Version | v2.70.0 |
| ZITADEL feature flags | webKey: true, improvedPerformance: \["IMPROVED\_PERFORMANCE\_ORG\_BY\_ID", "IMPROVED\_PERFORMANCE\_PROJECT", "IMPROVED\_PERFORMANCE\_USER\_GRANT", "IMPROVED\_PERFORMANCE\_ORG\_DOMAIN\_VERIFIED", "IMPROVED\_PERFORMANCE\_PROJECT\_GRANT"] |
| Database | type: psql version: v17.2 |
| Database location | US1 |
| Database specification | vCPU: 8 memory: 32Gib |
| ZITADEL metrics during test | |
| Observed errors | |
| Top 3 most expensive database queries | 1: lock current\_states table 2: write events 3: get events for projection |
| k6 Iterations per second | 153 |
| k6 output | [output](#k6-output) |
| flowchart outcome | Resolve locking issue |
Endpoint latencies [#endpoint-latencies]
k6 output [#k-6-output]
```bash
✓ authorize status ok
✓ auth request id returned
✓ add Session status ok
✓ finalize auth request status ok
█ setup
✓ user defined
✓ authorize status ok
✓ login name status ok
✓ login shows password page
✓ password status ok
✓ password callback
✓ code set
✓ token status ok
✓ access token created
✓ id token created
✓ info created
✓ org created
✓ create user is status ok
✓ generate machine key status ok
✓ member added successful
✓ openid configuration
✓ access token returned
█ teardown
✓ org removed
checks...............................: 100.00% 1097103 out of 1097103
data_received........................: 482 MB 267 kB/s
data_sent............................: 206 MB 114 kB/s
http_req_blocked.....................: min=150ns avg=185.63µs max=639.06ms p(50)=360ns p(95)=790ns p(99)=1.11µs
http_req_connecting..................: min=0s avg=76.84µs max=394.03ms p(50)=0s p(95)=0s p(99)=0s
http_req_duration....................: min=2.27ms avg=1.31s max=6.57s p(50)=326.44ms p(95)=3.94s p(99)=4.28s
{ expected_response:true }.........: min=2.27ms avg=1.31s max=6.57s p(50)=326.44ms p(95)=3.94s p(99)=4.28s
http_req_failed......................: 0.00% 0 out of 823429
http_req_receiving...................: min=22.92µs avg=143.73µs max=245.98ms p(50)=105.17µs p(95)=188.26µs p(99)=260.56µs
http_req_sending.....................: min=22.37µs avg=67.8µs max=41.57ms p(50)=63.65µs p(95)=104.8µs p(99)=138.46µs
http_req_tls_handshaking.............: min=0s avg=106.12µs max=580.5ms p(50)=0s p(95)=0s p(99)=0s
http_req_waiting.....................: min=2.11ms avg=1.31s max=6.57s p(50)=326.17ms p(95)=3.94s p(99)=4.28s
http_reqs............................: 823429 456.440453/s
iteration_duration...................: min=713.37ms avg=3.94s max=8.94s p(50)=3.92s p(95)=4.98s p(99)=5.44s
iterations...........................: 274271 152.032998/s
login_ui_enter_login_name_duration...: min=113.75ms avg=113.75ms max=113.75ms p(50)=113.75ms p(95)=113.75ms p(99)=113.75ms
login_ui_enter_password_duration.....: min=2.27ms avg=2.27ms max=2.27ms p(50)=2.27ms p(95)=2.27ms p(99)=2.27ms
login_ui_init_login_duration.........: min=20.48ms avg=156.67ms max=6.57s p(50)=126.64ms p(95)=280.16ms p(99)=675.36ms
login_ui_token_duration..............: min=68.53ms avg=68.53ms max=68.53ms p(50)=68.53ms p(95)=68.53ms p(99)=68.53ms
membership_iam_member................: min=34.16ms avg=34.16ms max=34.16ms p(50)=34.16ms p(95)=34.16ms p(99)=34.16ms
oidc_auth_requst_by_id_duration......: min=20.59ms avg=370.56ms max=2.87s p(50)=294.12ms p(95)=911.42ms p(99)=1.08s
oidc_session_duration................: min=713ms avg=3.94s max=8.94s p(50)=3.92s p(95)=4.98s p(99)=5.44s
oidc_token_duration..................: min=40.67ms avg=40.67ms max=40.67ms p(50)=40.67ms p(95)=40.67ms p(99)=40.67ms
org_create_org_duration..............: min=48.92ms avg=48.92ms max=48.92ms p(50)=48.92ms p(95)=48.92ms p(99)=48.92ms
session_add_session_duration.........: min=92.04ms avg=3.4s max=6.16s p(50)=3.46s p(95)=4.18s p(99)=4.48s
user_add_machine_key_duration........: min=32.08ms avg=32.08ms max=32.08ms p(50)=32.08ms p(95)=32.08ms p(99)=32.08ms
user_create_machine_duration.........: min=91.73ms avg=91.73ms max=91.73ms p(50)=91.73ms p(95)=91.73ms p(99)=91.73ms
vus..................................: 82 min=0 max=600
vus_max..............................: 600 min=600 max=600
running (30m04.0s), 000/600 VUs, 274271 complete and 0 interrupted iterations
default ✓ [======================================] 600 VUs 30m0s
```
# create session benchmark of zitadel v4
Benchmark results of v4 release of Zitadel.
Performance test results [#performance-test-results]
| Metric | Value |
| :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Baseline | none |
| Purpose | Test current performance |
| Test start | 11:31 UTC |
| Test duration | 30min |
| Executed test | add\_session |
| k6 version | v1.0.0-rc1 |
| VUs | 600 |
| Client location | US1 |
| ZITADEL location | US1 |
| ZITADEL container specification | vCPU: 6 Memory: 6 Gi Container min scale: 2 Container max scale: 7 |
| ZITADEL Version | v4.0.0-rc2 |
| ZITADEL feature flags | webKey: true, improvedPerformance: \["IMPROVED\_PERFORMANCE\_ORG\_BY\_ID", "IMPROVED\_PERFORMANCE\_PROJECT", "IMPROVED\_PERFORMANCE\_USER\_GRANT", "IMPROVED\_PERFORMANCE\_ORG\_DOMAIN\_VERIFIED", "IMPROVED\_PERFORMANCE\_PROJECT\_GRANT"] |
| Database | type: psql version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8 memory: 32Gib |
| ZITADEL metrics during test | |
| Observed errors | |
| Top 3 most expensive database queries | 1: Query events by instance\_id, aggregate\_types, event\_types, position 2: write events 3: list members |
| k6 Iterations per second | 607 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
Endpoint latencies [#endpoint-latencies]
Until :37 were containers starting because it was a cold start test.
k6 output [#k-6-output]
```bash
█ TOTAL RESULTS
checks_total.......................: 2244565 1213.769815/s
checks_succeeded...................: 99.99% 2244563 out of 2244565
checks_failed......................: 0.00% 2 out of 2244565
✓ user defined
✓ authorize status ok
✓ login name status ok
✓ login shows password page
✓ password status ok
✓ password callback
✓ code set
✓ token status ok
✓ access token created
✓ id token created
✓ info created
✓ org created
✓ create user is status ok
✗ add Session status ok
↳ 99% — ✓ 1121975 / ✗ 2
✓ add session is status ok
✓ org removed
CUSTOM
add_session_duration....................................................: min=236ms avg=962.51ms max=3.66s p(50)=951ms p(95)=1.33s p(99)=1.55s
login_ui_enter_login_name_duration......................................: min=184.04ms avg=184.04ms max=184.04ms p(50)=184.04ms p(95)=184.04ms p(99)=184.04ms
login_ui_enter_password_duration........................................: min=15.68ms avg=15.68ms max=15.68ms p(50)=15.68ms p(95)=15.68ms p(99)=15.68ms
login_ui_init_login_duration............................................: min=128.36ms avg=128.36ms max=128.36ms p(50)=128.36ms p(95)=128.36ms p(99)=128.36ms
login_ui_token_duration.................................................: min=87.2ms avg=87.2ms max=87.2ms p(50)=87.2ms p(95)=87.2ms p(99)=87.2ms
org_create_org_duration.................................................: min=76.73ms avg=76.73ms max=76.73ms p(50)=76.73ms p(95)=76.73ms p(99)=76.73ms
session_add_session_duration............................................: min=0s avg=961.43ms max=3.66s p(50)=950.06ms p(95)=1.33s p(99)=1.55s
user_create_human_duration..............................................: min=548.74ms avg=10.69s max=16.39s p(50)=13.39s p(95)=15.97s p(99)=16.28s
HTTP
http_req_duration.......................................................: min=0s avg=966.15ms max=18.87s p(50)=950.04ms p(95)=1.33s p(99)=1.55s
{ expected_response:true }............................................: min=15.68ms avg=966.15ms max=18.87s p(50)=950.04ms p(95)=1.33s p(99)=1.55s
http_req_failed.........................................................: 0.00% 2 out of 1123187
http_reqs...............................................................: 1123187 607.374025/s
EXECUTION
iteration_duration......................................................: min=1.63ms avg=962.61ms max=3.66s p(50)=951.17ms p(95)=1.33s p(99)=1.55s
iterations..............................................................: 1121977 606.719706/s
vus.....................................................................: 0 min=0 max=600
vus_max.................................................................: 600 min=600 max=600
NETWORK
data_received...........................................................: 617 MB 334 kB/s
data_sent...............................................................: 184 MB 100 kB/s
running (30m49.3s), 000/600 VUs, 1121977 complete and 0 interrupted iterations
default ✓ [======================================] 600 VUs 30m0s
```
# human password login benchmark of zitadel v4
Benchmark results of v4 release of Zitadel.
This benchmark tests the flow of a user signing in to Zitadel using username and password which is a highly CPU intensive task for checking the password.
Performance test results [#performance-test-results]
| Metric | Value |
| :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Baseline | none |
| Purpose | Test current performance |
| Test start | 06:43 UTC |
| Test duration | 30min |
| Executed test | human\_password\_login |
| k6 version | v1.0.0-rc1 |
| VUs | 600 |
| Client location | US1 |
| ZITADEL location | US1 |
| ZITADEL container specification | vCPU: 6 Memory: 6 Gi Container min scale: 2 Container max scale: 7 |
| ZITADEL Version | v4.0.0-rc2 |
| ZITADEL feature flags | webKey: true, improvedPerformance: \["IMPROVED\_PERFORMANCE\_ORG\_BY\_ID", "IMPROVED\_PERFORMANCE\_PROJECT", "IMPROVED\_PERFORMANCE\_USER\_GRANT", "IMPROVED\_PERFORMANCE\_ORG\_DOMAIN\_VERIFIED", "IMPROVED\_PERFORMANCE\_PROJECT\_GRANT"] |
| Database | type: psql version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8 memory: 32Gib |
| ZITADEL metrics during test | |
| Observed errors | |
| Top 3 most expensive database queries | 1: Locking projections for reduce 2: Query events by aggregate type and event type 3: Query events by instance id, aggregate types, event types, position |
| k6 Iterations per second | 39 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
Endpoint latencies [#endpoint-latencies]
Until :45 were containers starting because it was a cold start test.
k6 output [#k-6-output]
```bash
█ TOTAL RESULTS
checks_total.......................: 867495 464.541713/s
checks_succeeded...................: 99.88% 866490 out of 867495
checks_failed......................: 0.11% 1005 out of 867495
✓ user defined
✗ authorize status ok
↳ 99% — ✓ 72470 / ✗ 435
✗ login name status ok
↳ 99% — ✓ 72331 / ✗ 139
✓ login shows password page
✗ password status ok
↳ 99% — ✓ 72032 / ✗ 155
✗ password callback
↳ 99% — ✓ 72000 / ✗ 32
✓ code set
✗ token status ok
↳ 99% — ✓ 71866 / ✗ 134
✓ access token created
✓ id token created
✓ info created
✓ org created
✓ create user is status ok
✗ openid configuration
↳ 98% — ✓ 600 / ✗ 11
✗ userinfo status ok
↳ 99% — ✓ 71755 / ✗ 99
✓ org removed
CUSTOM
human_password_login_duration...........................................: min=984ms avg=14.73s max=58.88s p(50)=12.41s p(95)=36.8s p(99)=40.55s
login_ui_enter_login_name_duration......................................: min=98.28ms avg=10.57s max=38.72s p(50)=5.99s p(95)=32.4s p(99)=36.16s
login_ui_enter_password_duration........................................: min=1.12ms avg=119.12ms max=1m0s p(50)=2.15ms p(95)=3.96ms p(99)=3.8s
login_ui_init_login_duration............................................: min=41.73ms avg=600.61ms max=22.51s p(50)=292.69ms p(95)=1.84s p(99)=2.82s
login_ui_token_duration.................................................: min=53.03ms avg=712.39ms max=21.76s p(50)=339.01ms p(95)=2.11s p(99)=3.32s
oidc_user_info_duration.................................................: min=14.54ms avg=187.13ms max=21.51s p(50)=74.47ms p(95)=398.78ms p(99)=619.27ms
org_create_org_duration.................................................: min=50.09ms avg=50.09ms max=50.09ms p(50)=50.09ms p(95)=50.09ms p(99)=50.09ms
user_create_human_duration..............................................: min=425.26ms avg=11.83s max=16.89s p(50)=13.67s p(95)=16.07s p(99)=16.78s
HTTP
http_req_duration.......................................................: min=1.12ms avg=1.87s max=1m0s p(50)=257.41ms p(95)=12.28s p(99)=31.18s
{ expected_response:true }............................................: min=1.12ms avg=1.84s max=38.72s p(50)=256.14ms p(95)=11.83s p(99)=31.19s
http_req_failed.........................................................: 0.19% 1117 out of 581385
http_reqs...............................................................: 581385 311.330421/s
EXECUTION
iteration_duration......................................................: min=983.33ms avg=14.88s max=1m11s p(50)=12.64s p(95)=36.85s p(99)=40.62s
iterations..............................................................: 72904 39.039936/s
vus.....................................................................: 18 min=0 max=600
vus_max.................................................................: 600 min=600 max=600
NETWORK
data_received...........................................................: 1.2 GB 658 kB/s
data_sent...............................................................: 235 MB 126 kB/s
running (31m07.4s), 000/600 VUs, 72904 complete and 0 interrupted iterations
```
# introspection benchmark of zitadel v4
Benchmark results of v4 release of Zitadel.
Performance test results [#performance-test-results]
| Metric | Value |
| :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Baseline | none |
| Purpose | Test current performance |
| Test start | 13:33 UTC |
| Test duration | 30min |
| Executed test | introspect |
| k6 version | v1.0.0-rc1 |
| VUs | 600 |
| Client location | US1 |
| ZITADEL location | US1 |
| ZITADEL container specification | vCPU: 6 Memory: 6 Gi Container min scale: 2 Container max scale: 7 |
| ZITADEL Version | v4.0.0-rc2 |
| ZITADEL feature flags | webKey: true, improvedPerformance: \["IMPROVED\_PERFORMANCE\_ORG\_BY\_ID", "IMPROVED\_PERFORMANCE\_PROJECT", "IMPROVED\_PERFORMANCE\_USER\_GRANT", "IMPROVED\_PERFORMANCE\_ORG\_DOMAIN\_VERIFIED", "IMPROVED\_PERFORMANCE\_PROJECT\_GRANT"] |
| Database | type: psql version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8 memory: 32Gib |
| ZITADEL metrics during test | |
| Observed errors | A lot of over fetching on the database |
| Top 3 most expensive database queries | 1: get app 2: Query events by instance id, aggregate types, event types, position 3: Query executions |
| k6 Iterations per second | 18 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
Endpoint latencies [#endpoint-latencies]
k6 output [#k-6-output]
```bash
█ TOTAL RESULTS
checks_total.......................: 36207 19.664102/s
checks_succeeded...................: 99.99% 36205 out of 36207
checks_failed......................: 0.00% 2 out of 36207
✓ user defined
✓ authorize status ok
✓ login name status ok
✓ login shows password page
✓ password status ok
✓ password callback
✓ code set
✓ token status ok
✓ access token created
✓ id token created
✓ info created
✓ org created
✓ add project status ok
✓ add api status ok
✓ add app key status ok
✓ openid configuration
✗ introspect status ok
↳ 99% — ✓ 33792 / ✗ 2
✓ org removed
CUSTOM
app_add_app_duration....................................................: min=32.73ms avg=779.27ms max=1.11s p(50)=771.76ms p(95)=1.08s p(99)=1.1s
app_add_app_key_duration................................................: min=340.78ms avg=12.14s max=17.19s p(50)=13.51s p(95)=16.78s p(99)=16.93s
login_ui_enter_login_name_duration......................................: min=212.91ms avg=212.91ms max=212.91ms p(50)=212.91ms p(95)=212.91ms p(99)=212.91ms
login_ui_enter_password_duration........................................: min=29.53ms avg=29.53ms max=29.53ms p(50)=29.53ms p(95)=29.53ms p(99)=29.53ms
login_ui_init_login_duration............................................: min=65.24ms avg=65.24ms max=65.24ms p(50)=65.24ms p(95)=65.24ms p(99)=65.24ms
login_ui_token_duration.................................................: min=98.54ms avg=98.54ms max=98.54ms p(50)=98.54ms p(95)=98.54ms p(99)=98.54ms
oidc_introspect_duration................................................: min=731.31µs avg=32.04s max=50.94s p(50)=28.25s p(95)=47.83s p(99)=49.39s
org_create_org_duration.................................................: min=51.76ms avg=51.76ms max=51.76ms p(50)=51.76ms p(95)=51.76ms p(99)=51.76ms
project_add_project_duration............................................: min=52.11ms avg=754.58ms max=1.2s p(50)=778.66ms p(95)=1.14s p(99)=1.19s
HTTP
http_req_duration.......................................................: min=731.31µs avg=30.22s max=50.94s p(50)=26.12s p(95)=47.74s p(99)=49.35s
{ expected_response:true }............................................: min=13.11ms avg=30.22s max=50.94s p(50)=26.12s p(95)=47.74s p(99)=49.35s
http_req_failed.........................................................: 0.00% 2 out of 36204
http_reqs...............................................................: 36204 19.662473/s
EXECUTION
iteration_duration......................................................: min=1.53ms avg=32.14s max=50.94s p(50)=28.25s p(95)=47.83s p(99)=49.39s
iterations..............................................................: 33794 18.353597/s
vus.....................................................................: 0 min=0 max=600
vus_max.................................................................: 600 min=600 max=600
NETWORK
data_received...........................................................: 25 MB 14 kB/s
data_sent...............................................................: 33 MB 18 kB/s
running (30m41.3s), 000/600 VUs, 33794 complete and 0 interrupted iterations
default ✓ [======================================] 600 VUs 30m0s
```
# machine client credentials login benchmark of zitadel v4
Benchmark results of v4 release of Zitadel.
Performance test results [#performance-test-results]
| Metric | Value |
| :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Baseline | none |
| Purpose | Test current performance |
| Test start | 08:28 UTC |
| Test duration | 30min |
| Executed test | machine\_client\_credentials\_login |
| k6 version | v1.0.0-rc1 |
| VUs | 600 |
| Client location | US1 |
| ZITADEL location | US1 |
| ZITADEL container specification | vCPU: 6 Memory: 6 Gi Container min scale: 2 Container max scale: 7 |
| ZITADEL Version | v4.0.0-rc2 |
| ZITADEL feature flags | webKey: true, improvedPerformance: \["IMPROVED\_PERFORMANCE\_ORG\_BY\_ID", "IMPROVED\_PERFORMANCE\_PROJECT", "IMPROVED\_PERFORMANCE\_USER\_GRANT", "IMPROVED\_PERFORMANCE\_ORG\_DOMAIN\_VERIFIED", "IMPROVED\_PERFORMANCE\_PROJECT\_GRANT"] |
| Database | type: psql version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8 memory: 32Gib |
| ZITADEL metrics during test | |
| Observed errors | |
| Top 3 most expensive database queries | 1: Locking projections for reduce 2: Query events by aggregate type and event type 3: user by id query |
| k6 Iterations per second | 424 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
Endpoint latencies [#endpoint-latencies]
Until :37 were containers starting because it was a cold start test.
k6 output [#k-6-output]
```bash
█ TOTAL RESULTS
checks_total.......................: 2336254 1274.250394/s
checks_succeeded...................: 99.99% 2336250 out of 2336254
checks_failed......................: 0.00% 4 out of 2336254
✓ user defined
✓ authorize status ok
✓ login name status ok
✓ login shows password page
✓ password status ok
✓ password callback
✓ code set
✓ token status ok
✓ access token created
✓ id token created
✓ info created
✓ org created
✓ create user is status ok
✓ generate machine secret status ok
✓ openid configuration
✗ client credentials status ok
↳ 99% — ✓ 778146 / ✗ 3
✓ client credentials token ok
✗ userinfo status ok
↳ 99% — ✓ 778145 / ✗ 1
✓ org removed
CUSTOM
login_ui_enter_login_name_duration......................................: min=191.97ms avg=191.97ms max=191.97ms p(50)=191.97ms p(95)=191.97ms p(99)=191.97ms
login_ui_enter_password_duration........................................: min=14.65ms avg=14.65ms max=14.65ms p(50)=14.65ms p(95)=14.65ms p(99)=14.65ms
login_ui_init_login_duration............................................: min=91.36ms avg=91.36ms max=91.36ms p(50)=91.36ms p(95)=91.36ms p(99)=91.36ms
login_ui_token_duration.................................................: min=93.72ms avg=93.72ms max=93.72ms p(50)=93.72ms p(95)=93.72ms p(99)=93.72ms
oidc_client_credentials_duration........................................: min=244.96µs avg=794.35ms max=2.22s p(50)=783.9ms p(95)=1.1s p(99)=1.23s
oidc_user_info_duration.................................................: min=578.67µs avg=592.86ms max=1.98s p(50)=583.33ms p(95)=840.94ms p(99)=939.62ms
org_create_org_duration.................................................: min=60.95ms avg=60.95ms max=60.95ms p(50)=60.95ms p(95)=60.95ms p(99)=60.95ms
user_add_machine_secret_duration........................................: min=54.63ms avg=2.62s max=4.92s p(50)=2.67s p(95)=4.69s p(99)=4.87s
user_create_machine_duration............................................: min=159.68ms avg=681.87ms max=868.85ms p(50)=746.38ms p(95)=840.65ms p(99)=859.33ms
HTTP
http_req_duration.......................................................: min=244.96µs avg=693.86ms max=4.92s p(50)=671.37ms p(95)=1.05s p(99)=1.18s
{ expected_response:true }............................................: min=14.27ms avg=693.86ms max=4.92s p(50)=671.37ms p(95)=1.05s p(99)=1.18s
http_req_failed.........................................................: 0.00% 4 out of 1558705
http_reqs...............................................................: 1558705 850.156045/s
EXECUTION
iteration_duration......................................................: min=656.54µs avg=1.38s max=3.43s p(50)=1.38s p(95)=1.8s p(99)=2.01s
iterations..............................................................: 778149 424.421604/s
vus.....................................................................: 292 min=0 max=600
vus_max.................................................................: 600 min=600 max=600
NETWORK
data_received...........................................................: 1.6 GB 898 kB/s
data_sent...............................................................: 528 MB 288 kB/s
running (30m33.4s), 000/600 VUs, 778149 complete and 0 interrupted iterations
default ✓ [======================================] 600 VUs 30m0s
```
# machine jwt profile grant benchmark of zitadel v4
Benchmark results of v4 release of Zitadel.
Performance test results [#performance-test-results]
| Metric | Value |
| :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Baseline | none |
| Purpose | Test current performance |
| Test start | 18:33 UTC |
| Test duration | 30min |
| Executed test | machine\_jwt\_profile\_grant |
| k6 version | v1.0.0-rc1 |
| VUs | 600 |
| Client location | US1 |
| ZITADEL location | US1 |
| ZITADEL container specification | vCPU: 6 Memory: 6 Gi Container min scale: 2 Container max scale: 7 |
| ZITADEL Version | v4.0.0-rc2 |
| ZITADEL feature flags | webKey: true, improvedPerformance: \["IMPROVED\_PERFORMANCE\_ORG\_BY\_ID", "IMPROVED\_PERFORMANCE\_PROJECT", "IMPROVED\_PERFORMANCE\_USER\_GRANT", "IMPROVED\_PERFORMANCE\_ORG\_DOMAIN\_VERIFIED", "IMPROVED\_PERFORMANCE\_PROJECT\_GRANT"] |
| Database | type: psql version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8 memory: 32Gib |
| ZITADEL metrics during test | |
| Observed errors | Zitadel containers did not scale but database would have headroom |
| Top 3 most expensive database queries | 1: Query events by instance id, aggregate types, event types, position 2: write events 3: get user by id |
| k6 Iterations per second | 851 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
Endpoint latencies [#endpoint-latencies]
k6 output [#k-6-output]
```bash
█ TOTAL RESULTS
checks_total.......................: 3128039 1702.561961/s
checks_succeeded...................: 100.00% 3128039 out of 3128039
checks_failed......................: 0.00% 0 out of 3128039
✓ user defined
✓ authorize status ok
✓ login name status ok
✓ login shows password page
✓ password status ok
✓ password callback
✓ code set
✓ token status ok
✓ access token created
✓ id token created
✓ info created
✓ org created
✓ create user is status ok
✓ generate machine key status ok
✓ openid configuration
✓ access token returned
✓ org removed
CUSTOM
login_ui_enter_login_name_duration......................................: min=185.23ms avg=185.23ms max=185.23ms p(50)=185.23ms p(95)=185.23ms p(99)=185.23ms
login_ui_enter_password_duration........................................: min=19.29ms avg=19.29ms max=19.29ms p(50)=19.29ms p(95)=19.29ms p(99)=19.29ms
login_ui_init_login_duration............................................: min=103.07ms avg=103.07ms max=103.07ms p(50)=103.07ms p(95)=103.07ms p(99)=103.07ms
login_ui_token_duration.................................................: min=147.4ms avg=147.4ms max=147.4ms p(50)=147.4ms p(95)=147.4ms p(99)=147.4ms
oidc_token_duration.....................................................: min=58.6ms avg=688.05ms max=3.95s p(50)=470.3ms p(95)=1.16s p(99)=1.24s
org_create_org_duration.................................................: min=88.83ms avg=88.83ms max=88.83ms p(50)=88.83ms p(95)=88.83ms p(99)=88.83ms
user_add_machine_key_duration...........................................: min=29.55ms avg=481.01ms max=742.31ms p(50)=487.76ms p(95)=728.51ms p(99)=739.94ms
user_create_machine_duration............................................: min=88.42ms avg=748.16ms max=4.46s p(50)=732.58ms p(95)=1.01s p(99)=1.03s
HTTP
http_req_duration.......................................................: min=19.29ms avg=687.53ms max=4.46s p(50)=470.17ms p(95)=1.16s p(99)=1.24s
{ expected_response:true }............................................: min=19.29ms avg=687.53ms max=4.46s p(50)=470.17ms p(95)=1.16s p(99)=1.24s
http_req_failed.........................................................: 0.00% 0 out of 1565523
http_reqs...............................................................: 1565523 852.099321/s
EXECUTION
iteration_duration......................................................: min=61.7ms avg=690.96ms max=3.96s p(50)=473.07ms p(95)=1.16s p(99)=1.25s
iterations..............................................................: 1563113 850.787581/s
vus.....................................................................: 1 min=0 max=600
vus_max.................................................................: 600 min=600 max=600
NETWORK
data_received...........................................................: 2.3 GB 1.2 MB/s
data_sent...............................................................: 1.3 GB 725 kB/s
running (30m37.3s), 000/600 VUs, 1563113 complete and 0 interrupted iterations
default ✓ [======================================] 600 VUs 30m0s
```
# machine pat login benchmark of zitadel v4
Benchmark results of v4 release of Zitadel.
Performance test results [#performance-test-results]
| Metric | Value |
| :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Baseline | none |
| Purpose | Test current performance |
| Test start | 07:34 UTC |
| Test duration | 30min |
| Executed test | machine\_pat\_login |
| k6 version | v1.0.0-rc1 |
| VUs | 600 |
| Client location | US1 |
| ZITADEL location | US1 |
| ZITADEL container specification | vCPU: 6 Memory: 6 Gi Container min scale: 2 Container max scale: 7 |
| ZITADEL Version | v4.0.0-rc2 |
| ZITADEL feature flags | webKey: true, improvedPerformance: \["IMPROVED\_PERFORMANCE\_ORG\_BY\_ID", "IMPROVED\_PERFORMANCE\_PROJECT", "IMPROVED\_PERFORMANCE\_USER\_GRANT", "IMPROVED\_PERFORMANCE\_ORG\_DOMAIN\_VERIFIED", "IMPROVED\_PERFORMANCE\_PROJECT\_GRANT"] |
| Database | type: psql version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8 memory: 32Gib |
| ZITADEL metrics during test | |
| Observed errors | |
| Top 3 most expensive database queries | 1: Locking projections for reduce 2: Query events by aggregate type and event type 3: Query events by instance id, aggregate types, event types, position |
| k6 Iterations per second | 1906 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
Endpoint latencies [#endpoint-latencies]
Until :37 were containers starting because it was a cold start test.
k6 output [#k-6-output]
```bash
█ TOTAL RESULTS
checks_total.......................: 867495 464.541713/s
checks_succeeded...................: 99.88% 866490 out of 867495
checks_failed......................: 0.11% 1005 out of 867495
✓ user defined
✗ authorize status ok
↳ 99% — ✓ 72470 / ✗ 435
✗ login name status ok
↳ 99% — ✓ 72331 / ✗ 139
✓ login shows password page
✗ password status ok
↳ 99% — ✓ 72032 / ✗ 155
✗ password callback
↳ 99% — ✓ 72000 / ✗ 32
✓ code set
✗ token status ok
↳ 99% — ✓ 71866 / ✗ 134
✓ access token created
✓ id token created
✓ info created
✓ org created
✓ create user is status ok
✗ openid configuration
↳ 98% — ✓ 600 / ✗ 11
✗ userinfo status ok
↳ 99% — ✓ 71755 / ✗ 99
✓ org removed
CUSTOM
human_password_login_duration...........................................: min=984ms avg=14.73s max=58.88s p(50)=12.41s p(95)=36.8s p(99)=40.55s
login_ui_enter_login_name_duration......................................: min=98.28ms avg=10.57s max=38.72s p(50)=5.99s p(95)=32.4s p(99)=36.16s
login_ui_enter_password_duration........................................: min=1.12ms avg=119.12ms max=1m0s p(50)=2.15ms p(95)=3.96ms p(99)=3.8s
login_ui_init_login_duration............................................: min=41.73ms avg=600.61ms max=22.51s p(50)=292.69ms p(95)=1.84s p(99)=2.82s
login_ui_token_duration.................................................: min=53.03ms avg=712.39ms max=21.76s p(50)=339.01ms p(95)=2.11s p(99)=3.32s
oidc_user_info_duration.................................................: min=14.54ms avg=187.13ms max=21.51s p(50)=74.47ms p(95)=398.78ms p(99)=619.27ms
org_create_org_duration.................................................: min=50.09ms avg=50.09ms max=50.09ms p(50)=50.09ms p(95)=50.09ms p(99)=50.09ms
user_create_human_duration..............................................: min=425.26ms avg=11.83s max=16.89s p(50)=13.67s p(95)=16.07s p(99)=16.78s
HTTP
http_req_duration.......................................................: min=1.12ms avg=1.87s max=1m0s p(50)=257.41ms p(95)=12.28s p(99)=31.18s
{ expected_response:true }............................................: min=1.12ms avg=1.84s max=38.72s p(50)=256.14ms p(95)=11.83s p(99)=31.19s
http_req_failed.........................................................: 0.19% 1117 out of 581385
http_reqs...............................................................: 581385 311.330421/s
EXECUTION
iteration_duration......................................................: min=983.33ms avg=14.88s max=1m11s p(50)=12.64s p(95)=36.85s p(99)=40.62s
iterations..............................................................: 72904 39.039936/s
vus.....................................................................: 18 min=0 max=600
vus_max.................................................................: 600 min=600 max=600
NETWORK
data_received...........................................................: 1.2 GB 658 kB/s
data_sent...............................................................: 235 MB 126 kB/s
running (31m07.4s), 000/600 VUs, 72904 complete and 0 interrupted iterations
```
# manipulate user benchmark of zitadel v4
Benchmark results of v4 release of Zitadel.
Performance test results [#performance-test-results]
| Metric | Value |
| :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Baseline | none |
| Purpose | Test current performance |
| Test start | 15:07 UTC |
| Test duration | 30min |
| Executed test | manipulate\_user |
| k6 version | v1.0.0-rc1 |
| VUs | 600 |
| Client location | US1 |
| ZITADEL location | US1 |
| ZITADEL container specification | vCPU: 6 Memory: 6 Gi Container min scale: 2 Container max scale: 7 |
| ZITADEL Version | v4.0.0-rc2 |
| ZITADEL feature flags | webKey: true, improvedPerformance: \["IMPROVED\_PERFORMANCE\_ORG\_BY\_ID", "IMPROVED\_PERFORMANCE\_PROJECT", "IMPROVED\_PERFORMANCE\_USER\_GRANT", "IMPROVED\_PERFORMANCE\_ORG\_DOMAIN\_VERIFIED", "IMPROVED\_PERFORMANCE\_PROJECT\_GRANT"] |
| Database | type: psql version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8 memory: 32Gib |
| ZITADEL metrics during test | |
| Observed errors | |
| Top 3 most expensive database queries | 1: query events by instance id, aggregate types, event types, position 2: write events 3: query user by id |
| k6 Iterations per second | 16 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
Endpoint latencies [#endpoint-latencies]
Each iteration creates, updates and deletes a user.
k6 output [#k-6-output]
```bash
█ TOTAL RESULTS
checks_total.......................: 199261 108.759178/s
checks_succeeded...................: 57.15% 113882 out of 199261
checks_failed......................: 42.84% 85379 out of 199261
✓ user defined
✓ authorize status ok
✓ login name status ok
✓ login shows password page
✓ password status ok
✓ password callback
✓ code set
✓ token status ok
✓ access token created
✓ id token created
✓ info created
✓ org created
✗ create user is status ok
↳ 99% — ✓ 28528 / ✗ 4
✗ update user is status ok
↳ 25% — ✓ 28469 / ✗ 85359
✗ lock user is status ok
↳ 99% — ✓ 28460 / ✗ 8
✗ delete user is status ok
↳ 99% — ✓ 28412 / ✗ 8
✓ org removed
CUSTOM
delete_user_duration....................................................: min=109.24ms avg=3.48s max=1m0s p(50)=2s p(95)=7.65s p(99)=9.78s
lock_user_duration......................................................: min=77.95ms avg=2.7s max=1m0s p(50)=1.1s p(95)=6.36s p(99)=7.86s
login_ui_enter_login_name_duration......................................: min=175.39ms avg=175.39ms max=175.39ms p(50)=175.39ms p(95)=175.39ms p(99)=175.39ms
login_ui_enter_password_duration........................................: min=34.12ms avg=34.12ms max=34.12ms p(50)=34.12ms p(95)=34.12ms p(99)=34.12ms
login_ui_init_login_duration............................................: min=100.5ms avg=100.5ms max=100.5ms p(50)=100.5ms p(95)=100.5ms p(99)=100.5ms
login_ui_token_duration.................................................: min=154.05ms avg=154.05ms max=154.05ms p(50)=154.05ms p(95)=154.05ms p(99)=154.05ms
org_create_org_duration.................................................: min=71.03ms avg=71.03ms max=71.03ms p(50)=71.03ms p(95)=71.03ms p(99)=71.03ms
update_human_duration...................................................: min=70.77ms avg=2.43s max=1m0s p(50)=807.73ms p(95)=5.99s p(99)=7.36s
user_create_human_duration..............................................: min=503.01ms avg=3.69s max=1m0s p(50)=2.07s p(95)=7.66s p(99)=13.98s
HTTP
http_req_duration.......................................................: min=34.12ms avg=7.64s max=1m0s p(50)=4.07s p(95)=32.29s p(99)=41.1s
{ expected_response:true }............................................: min=34.12ms avg=7.63s max=54.32s p(50)=4.07s p(95)=32.26s p(99)=41.03s
http_req_failed.........................................................: 0.02% 31 out of 142384
http_reqs...............................................................: 142384 77.714991/s
EXECUTION
iteration_duration......................................................: min=17.1s avg=38.22s max=1m30s p(50)=36.76s p(95)=58.22s p(99)=1m7s
iterations..............................................................: 28434 15.519637/s
vus.....................................................................: 0 min=0 max=600
vus_max.................................................................: 600 min=600 max=600
NETWORK
data_received...........................................................: 82 MB 45 kB/s
data_sent...............................................................: 26 MB 14 kB/s
running (30m32.1s), 000/600 VUs, 28434 complete and 98 interrupted iterations
default ✓ [======================================] 600 VUs 30m0s
```
# oidc session benchmark of zitadel v4
Benchmark results of v4 release of Zitadel.
Performance test results [#performance-test-results]
| Metric | Value |
| :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Baseline | none |
| Purpose | Test current performance |
| Test start | 12:33 UTC |
| Test duration | 30min |
| Executed test | oidc\_session |
| k6 version | v1.0.0-rc1 |
| VUs | 600 |
| Client location | US1 |
| ZITADEL location | US1 |
| ZITADEL container specification | vCPU: 6 Memory: 6 Gi Container min scale: 2 Container max scale: 7 |
| ZITADEL Version | v4.0.0-rc2 |
| ZITADEL feature flags | webKey: true, improvedPerformance: \["IMPROVED\_PERFORMANCE\_ORG\_BY\_ID", "IMPROVED\_PERFORMANCE\_PROJECT", "IMPROVED\_PERFORMANCE\_USER\_GRANT", "IMPROVED\_PERFORMANCE\_ORG\_DOMAIN\_VERIFIED", "IMPROVED\_PERFORMANCE\_PROJECT\_GRANT"] |
| Database | type: psql version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8 memory: 32Gib |
| ZITADEL metrics during test | |
| Observed errors | Database CPU usage was 60% |
| Top 3 most expensive database queries | 1: query events by instance id, aggregate types, event types, position 2: writing events 3: query memberships |
| k6 Iterations per second | 105 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
Endpoint latencies [#endpoint-latencies]
k6 output [#k-6-output]
```bash
█ TOTAL RESULTS
checks_total.......................: 1061560 420.116279/s
checks_succeeded...................: 99.99% 1061559 out of 1061560
checks_failed......................: 0.00% 1 out of 1061560
✓ user defined
✓ authorize status ok
✓ login name status ok
✓ login shows password page
✓ password status ok
✓ password callback
✓ code set
✓ token status ok
✓ access token created
✓ id token created
✓ info created
✓ org created
✓ create user is status ok
✓ generate machine key status ok
✓ member added successful
✓ openid configuration
✓ access token returned
✓ auth request id returned
✗ add Session status ok
↳ 99% — ✓ 265394 / ✗ 1
✓ finalize auth request status ok
✓ org removed
CUSTOM
login_ui_enter_login_name_duration......................................: min=100.93ms avg=100.93ms max=100.93ms p(50)=100.93ms p(95)=100.93ms p(99)=100.93ms
login_ui_enter_password_duration........................................: min=4.86ms avg=4.86ms max=4.86ms p(50)=4.86ms p(95)=4.86ms p(99)=4.86ms
login_ui_init_login_duration............................................: min=71.62ms avg=497.81ms max=19m45s p(50)=442.5ms p(95)=557.71ms p(99)=642.49ms
login_ui_token_duration.................................................: min=80.88ms avg=80.88ms max=80.88ms p(50)=80.88ms p(95)=80.88ms p(99)=80.88ms
membership_iam_member...................................................: min=65.45ms avg=65.45ms max=65.45ms p(50)=65.45ms p(95)=65.45ms p(99)=65.45ms
oidc_auth_request_finalize..............................................: min=89.04ms avg=1.32s max=2m8s p(50)=1.26s p(95)=1.62s p(99)=1.78s
oidc_session_duration...................................................: min=1.73s avg=2.88s max=21m54s p(50)=2.73s p(95)=3.26s p(99)=3.54s
oidc_token_duration.....................................................: min=34.16ms avg=34.16ms max=34.16ms p(50)=34.16ms p(95)=34.16ms p(99)=34.16ms
org_create_org_duration.................................................: min=41.58ms avg=41.58ms max=41.58ms p(50)=41.58ms p(95)=41.58ms p(99)=41.58ms
session_add_session_duration............................................: min=105.71ms avg=1.06s max=15m9s p(50)=1s p(95)=1.34s p(99)=1.5s
user_add_machine_key_duration...........................................: min=50.3ms avg=50.3ms max=50.3ms p(50)=50.3ms p(95)=50.3ms p(99)=50.3ms
user_create_machine_duration............................................: min=32.72ms avg=32.72ms max=32.72ms p(50)=32.72ms p(95)=32.72ms p(99)=32.72ms
HTTP
http_req_duration.......................................................: min=4.86ms avg=1.04s max=19m45s p(50)=966.65ms p(95)=1.5s p(99)=1.68s
{ expected_response:true }............................................: min=4.86ms avg=987.51ms max=19m45s p(50)=966.55ms p(95)=1.5s p(99)=1.68s
http_req_failed.........................................................: 0.01% 144 out of 796955
http_reqs...............................................................: 796955 315.397876/s
EXECUTION
iteration_duration......................................................: min=1.73s avg=2.88s max=21m55s p(50)=2.73s p(95)=3.26s p(99)=3.54s
iterations..............................................................: 265117 104.921029/s
vus.....................................................................: 0 min=0 max=600
vus_max.................................................................: 600 min=600 max=600
NETWORK
data_received...........................................................: 475 MB 188 kB/s
data_sent...............................................................: 200 MB 79 kB/s
running (42m06.8s), 000/600 VUs, 265104 complete and 600 interrupted iterations
default ✓ [======================================] 600 VUs 30m0s
```
# otp session benchmark of zitadel v4
Benchmark results of v4 release of Zitadel.
Performance test results [#performance-test-results]
| Metric | Value |
| :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Baseline | none |
| Purpose | Test current performance |
| Test start | 14:56 UTC |
| Test duration | 30min |
| Executed test | otp\_session |
| k6 version | v1.0.0-rc1 |
| VUs | 600 |
| Client location | US1 |
| ZITADEL location | US1 |
| ZITADEL container specification | vCPU: 6 Memory: 6 Gi Container min scale: 2 Container max scale: 7 |
| ZITADEL Version | v4.0.0-rc2 |
| ZITADEL feature flags | webKey: true, improvedPerformance: \["IMPROVED\_PERFORMANCE\_ORG\_BY\_ID", "IMPROVED\_PERFORMANCE\_PROJECT", "IMPROVED\_PERFORMANCE\_USER\_GRANT", "IMPROVED\_PERFORMANCE\_ORG\_DOMAIN\_VERIFIED", "IMPROVED\_PERFORMANCE\_PROJECT\_GRANT"] |
| Database | type: psql version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8 memory: 32Gib |
| ZITADEL metrics during test | |
| Observed errors | database usage was 80% |
| Top 3 most expensive database queries | 1: query events by instance id, aggregate types, event types, position 2: lock projection 3: write events |
| k6 Iterations per second | 60 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
Endpoint latencies [#endpoint-latencies]
k6 output [#k-6-output]
```bash
█ TOTAL RESULTS
checks_total.......................: 338059 181.945817/s
checks_succeeded...................: 99.99% 338058 out of 338059
checks_failed......................: 0.00% 1 out of 338059
✓ user defined
✓ authorize status ok
✓ login name status ok
✓ login shows password page
✓ password status ok
✓ password callback
✓ code set
✓ token status ok
✓ access token created
✓ id token created
✓ info created
✓ org created
✓ create user is status ok
✓ set email otp status ok
✓ add Session status ok
✗ set Session status ok
↳ 99% — ✓ 224563 / ✗ 1
✓ org removed
CUSTOM
login_ui_enter_login_name_duration......................................: min=179.7ms avg=179.7ms max=179.7ms p(50)=179.7ms p(95)=179.7ms p(99)=179.7ms
login_ui_enter_password_duration........................................: min=14.73ms avg=14.73ms max=14.73ms p(50)=14.73ms p(95)=14.73ms p(99)=14.73ms
login_ui_init_login_duration............................................: min=76.13ms avg=76.13ms max=76.13ms p(50)=76.13ms p(95)=76.13ms p(99)=76.13ms
login_ui_token_duration.................................................: min=152.52ms avg=152.52ms max=152.52ms p(50)=152.52ms p(95)=152.52ms p(99)=152.52ms
org_create_org_duration.................................................: min=59.87ms avg=59.87ms max=59.87ms p(50)=59.87ms p(95)=59.87ms p(99)=59.87ms
otp_session_duration....................................................: min=2.2s avg=9.63s max=15.61s p(50)=9.59s p(95)=12.09s p(99)=13.33s
session_add_session_duration............................................: min=154ms avg=8.31s max=12.63s p(50)=8.42s p(95)=10.1s p(99)=10.52s
session_set_session_duration............................................: min=0s avg=659.94ms max=4.45s p(50)=513.63ms p(95)=1.62s p(99)=1.9s
set_human_email_otp_duration............................................: min=29.12ms avg=494.09ms max=806.37ms p(50)=581.1ms p(95)=646.91ms p(99)=680.35ms
user_create_human_duration..............................................: min=698.95ms avg=9.83s max=17.55s p(50)=11.1s p(95)=17s p(99)=17.46s
HTTP
http_req_duration.......................................................: min=0s avg=3.21s max=20.01s p(50)=1.21s p(95)=9.59s p(99)=10.3s
{ expected_response:true }............................................: min=14.73ms avg=3.21s max=20.01s p(50)=1.21s p(95)=9.59s p(99)=10.3s
http_req_failed.........................................................: 0.00% 1 out of 338656
http_reqs...............................................................: 338656 182.267126/s
EXECUTION
iteration_duration......................................................: min=2.2s avg=9.63s max=15.61s p(50)=9.59s p(95)=12.09s p(99)=13.33s
iterations..............................................................: 112282 60.43099/s
vus.....................................................................: 0 min=0 max=600
vus_max.................................................................: 600 min=600 max=600
NETWORK
data_received...........................................................: 183 MB 98 kB/s
data_sent...............................................................: 82 MB 44 kB/s
running (30m58.0s), 000/600 VUs, 112282 complete and 0 interrupted iterations
default ✓ [======================================] 600 VUs 30m0s
```
# session with password benchmark of zitadel v4
Benchmark results of v4 release of Zitadel.
Performance test results [#performance-test-results]
| Metric | Value |
| :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Baseline | none |
| Purpose | Test current performance |
| Test start | 15:14 UTC |
| Test duration | 30min |
| Executed test | password\_session |
| k6 version | v1.0.0-rc1 |
| VUs | 600 |
| Client location | US1 |
| ZITADEL location | US1 |
| ZITADEL container specification | vCPU: 6 Memory: 6 Gi Container min scale: 2 Container max scale: 7 |
| ZITADEL Version | v4.0.0-rc2 |
| ZITADEL feature flags | webKey: true, improvedPerformance: \["IMPROVED\_PERFORMANCE\_ORG\_BY\_ID", "IMPROVED\_PERFORMANCE\_PROJECT", "IMPROVED\_PERFORMANCE\_USER\_GRANT", "IMPROVED\_PERFORMANCE\_ORG\_DOMAIN\_VERIFIED", "IMPROVED\_PERFORMANCE\_PROJECT\_GRANT"] |
| Database | type: psql version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8 memory: 32Gib |
| ZITADEL metrics during test | |
| Observed errors | Container startup latency is > 3 minutes |
| Top 3 most expensive database queries | 1: lock projection 2: query events by instance id, aggregate types, event types, position 3: query events by aggregate type, event type |
| k6 Iterations per second | 51 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
Endpoint latencies [#endpoint-latencies]
k6 output [#k-6-output]
```bash
█ TOTAL RESULTS
checks_total.......................: 188813 101.308159/s
checks_succeeded...................: 99.35% 187589 out of 188813
checks_failed......................: 0.64% 1224 out of 188813
✓ user defined
✓ authorize status ok
✓ login name status ok
✓ login shows password page
✓ password status ok
✓ password callback
✓ code set
✓ token status ok
✓ access token created
✓ id token created
✓ info created
✓ org created
✓ create user is status ok
✓ set email otp status ok
✗ add Session status ok
↳ 99% — ✓ 93457 / ✗ 686
✗ set Session status ok
↳ 99% — ✓ 92919 / ✗ 538
✓ org removed
CUSTOM
login_ui_enter_login_name_duration......................................: min=240.4ms avg=240.4ms max=240.4ms p(50)=240.4ms p(95)=240.4ms p(99)=240.4ms
login_ui_enter_password_duration........................................: min=14.32ms avg=14.32ms max=14.32ms p(50)=14.32ms p(95)=14.32ms p(99)=14.32ms
login_ui_init_login_duration............................................: min=84.45ms avg=84.45ms max=84.45ms p(50)=84.45ms p(95)=84.45ms p(99)=84.45ms
login_ui_token_duration.................................................: min=148.53ms avg=148.53ms max=148.53ms p(50)=148.53ms p(95)=148.53ms p(99)=148.53ms
org_create_org_duration.................................................: min=59.73ms avg=59.73ms max=59.73ms p(50)=59.73ms p(95)=59.73ms p(99)=59.73ms
password_session_duration...............................................: min=371ms avg=10.97s max=1m4s p(50)=9.52s p(95)=23.81s p(99)=27.57s
session_add_session_duration............................................: min=36.81ms avg=10.62s max=1m0s p(50)=9.13s p(95)=23.23s p(99)=29.47s
session_set_session_duration............................................: min=94.6ms avg=881.65ms max=1m0s p(50)=365.48ms p(95)=1.88s p(99)=4.75s
set_human_email_otp_duration............................................: min=39.12ms avg=570.13ms max=868.83ms p(50)=549.06ms p(95)=844.01ms p(99)=864.28ms
user_create_human_duration..............................................: min=502.43ms avg=10.82s max=16.72s p(50)=13.42s p(95)=16.28s p(99)=16.56s
HTTP
http_req_duration.......................................................: min=14.32ms avg=5.75s max=1m0s p(50)=583.63ms p(95)=22.01s p(99)=26.51s
{ expected_response:true }............................................: min=14.32ms avg=5.47s max=59.87s p(50)=566.75ms p(95)=21.8s p(99)=24.55s
http_req_failed.........................................................: 0.64% 1224 out of 189410
http_reqs...............................................................: 189410 101.628481/s
EXECUTION
iteration_duration......................................................: min=38.37ms avg=11.5s max=1m21s p(50)=9.68s p(95)=24.21s p(99)=54.53s
iterations..............................................................: 94143 50.512698/s
vus.....................................................................: 0 min=0 max=600
vus_max.................................................................: 600 min=600 max=600
NETWORK
data_received...........................................................: 102 MB 55 kB/s
data_sent...............................................................: 45 MB 24 kB/s
running (31m03.7s), 000/600 VUs, 94143 complete and 0 interrupted iterations
default ✓ [======================================] 600 VUs 30m0s
```
# user info benchmark of zitadel v4
Benchmark results of v4 release of Zitadel.
Performance test results [#performance-test-results]
| Metric | Value |
| :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Baseline | none |
| Purpose | Test current performance |
| Test start | 16:06 UTC |
| Test duration | 30min |
| Executed test | user\_info |
| k6 version | v1.0.0-rc1 |
| VUs | 600 |
| Client location | US1 |
| ZITADEL location | US1 |
| ZITADEL container specification | vCPU: 6 Memory: 6 Gi Container min scale: 2 Container max scale: 7 |
| ZITADEL Version | v4.0.0-rc2 |
| ZITADEL feature flags | webKey: true, improvedPerformance: \["IMPROVED\_PERFORMANCE\_ORG\_BY\_ID", "IMPROVED\_PERFORMANCE\_PROJECT", "IMPROVED\_PERFORMANCE\_USER\_GRANT", "IMPROVED\_PERFORMANCE\_ORG\_DOMAIN\_VERIFIED", "IMPROVED\_PERFORMANCE\_PROJECT\_GRANT"] |
| Database | type: psql version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8 memory: 32Gib |
| ZITADEL metrics during test | Container startup latency is > 3 minutes |
| Observed errors | Container startup latency is > 3 minutes, elevated error rate during container startup |
| Top 3 most expensive database queries | 1: events by aggregate type and event type 2: events by instance id, aggregate types, event types, position 3: user by id |
| k6 Iterations per second | 2653 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
Endpoint latencies [#endpoint-latencies]
k6 output [#k-6-output]
```bash
█ TOTAL RESULTS
checks_total.......................: 4784344 2653.094897/s
checks_succeeded...................: 99.97% 4783333 out of 4784344
checks_failed......................: 0.02% 1011 out of 4784344
✓ user defined
✓ authorize status ok
✓ login name status ok
✓ login shows password page
✓ password status ok
✓ password callback
✓ code set
✓ token status ok
✓ access token created
✓ id token created
✓ info created
✓ org created
✓ create user is status ok
✗ openid configuration
↳ 89% — ✓ 600 / ✗ 74
✗ userinfo status ok
↳ 99% — ✓ 4782709 / ✗ 936
✗ org removed
↳ 0% — ✓ 0 / ✗ 1
CUSTOM
login_ui_enter_login_name_duration......................................: min=149.1ms avg=176.4ms max=203.69ms p(50)=176.4ms p(95)=200.96ms p(99)=203.15ms
login_ui_enter_password_duration........................................: min=34.7ms avg=248.44ms max=462.19ms p(50)=248.44ms p(95)=440.82ms p(99)=457.92ms
login_ui_init_login_duration............................................: min=83.7ms avg=85.38ms max=87.05ms p(50)=85.38ms p(95)=86.88ms p(99)=87.01ms
login_ui_token_duration.................................................: min=92.78ms avg=118.84ms max=144.89ms p(50)=118.84ms p(95)=142.29ms p(99)=144.37ms
oidc_user_info_duration.................................................: min=453.34µs avg=224.58ms max=1m0s p(50)=83.13ms p(95)=792.51ms p(99)=926.8ms
org_create_org_duration.................................................: min=53.42ms avg=53.42ms max=53.42ms p(50)=53.42ms p(95)=53.42ms p(99)=53.42ms
user_create_human_duration..............................................: min=373.72ms avg=373.72ms max=373.72ms p(50)=373.72ms p(95)=373.72ms p(99)=373.72ms
HTTP
http_req_duration.......................................................: min=453.34µs avg=225.49ms max=1m0s p(50)=83.13ms p(95)=792.51ms p(99)=927.03ms
{ expected_response:true }............................................: min=3.29ms avg=212.92ms max=29.69s p(50)=83.1ms p(95)=792.3ms p(99)=923.17ms
http_req_failed.........................................................: 0.02% 1011 out of 4784339
http_reqs...............................................................: 4784339 2653.092124/s
EXECUTION
iteration_duration......................................................: min=731.85µs avg=225.74ms max=1m0s p(50)=83.33ms p(95)=792.71ms p(99)=927.27ms
iterations..............................................................: 4783719 2652.748311/s
vus.....................................................................: 600 min=0 max=600
vus_max.................................................................: 600 min=600 max=600
NETWORK
data_received...........................................................: 3.4 GB 1.9 MB/s
data_sent...............................................................: 378 MB 210 kB/s
running (30m03.3s), 000/600 VUs, 4783719 complete and 0 interrupted iterations
default ✓ [======================================] 600 VUs 30m0s
```
# Authentication Methods
ZITADEL supports various authentication methods for OIDC clients.
# Integrating Your Application with ZITADEL using RFC 8628 OAuth 2.0 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? [#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? [#why-integrate-with-zitadel]
ZITADEL offers a comprehensive identity and access management solution, providing features like single sign-on (SSO), multifactor 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 [#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.
3. Select “Native” and enter a name for the application, and click “Continue”.
4. Select “Device Code”. Click “Continue”.
5. Check the details and click “Create”.
6. Copy the “Client ID” and store it for later use.
Device Client Example [#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!**
* `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 [#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.
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 [#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 [#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 [#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.
# Authenticate users with OpenID Connect (OIDC)
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
# OpenID Connect (OIDC) Authentication: Authorization Code Flow & PKCE guide
Overview [#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 [#oidc-o-auth-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.
Authorization Code Flow [#authorization-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:
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 [#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 [#application-type]
This will start a wizard asking you for an application name and a type.
Authentication method [#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 Authorization 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:
Authentication method [#authentication-method]
When selecting Native the authentication method always needs to be PKCE.
Authentication method [#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.
# Log Out Users from an Application with ZITADEL
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 [#open-id-connect-single-logout]
Single Sign On (SSO) vs Single Logout (SLO) [#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 log out 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 [#open-id-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]
Session Management in OpenID Connect defines a mechanism for a client (Relying Party, RP) to monitor the state of the user session from an 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 [#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 [#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.
This is not yet implemented in ZITADEL
Back-Channel Logout [#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 log out from all clients even in the case the user agent was closed.
This is not yet implemented in ZITADEL
Scenarios [#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 [#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.
# Recommended authorization flows with OpenID Connect (OIDC) and OAuth 2.x
Introduction [#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 (i.e. without legacy requirements) and you found a 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, i.e. 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 [#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 (i.e. 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, e.g. 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
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 [#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.
Confidentiality of client credentials
Client profile
Examples of clients
Public
User-Agent
Single-page web applications (SPA), generally JavaScript executed in Browser
Native
Native, Mobile, or Desktop applications
Confidential
Web
Server-side web applications such as java, .net, …
Machine-to-Machine
APIs, generally services without direct user-interaction at the authorization server
Our recommended authorization flows [#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.
# OpenID Connect and OAuth2 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 [#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 [#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 [#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 [#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 [#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 settings from the request. This config is a one-of field of:
* RSA
* ECDSA
* ED25519
When the request does not contain any specific settings,
[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 ' \
-d '{}'
```
RSA [#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 an 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 ' \
-d '{
"rsa": {
"bits": "RSA_BITS_3072",
"hasher": "RSA_HASHER_SHA512"
}
}'
```
ECDSA [#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 ' \
-d '{
"ecdsa": {
"curve": "ECDSA_CURVE_P256"
}
}'
```
ED25519 [#ed-25519]
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 an ed25519 web key:
```bash
curl -L 'https://${CUSTOM_DOMAIN}/v2/web_keys' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer ' \
-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 [#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 [#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 clean up 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 [#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 allow usage of the `id_token_hint` for at least 3 months.
Users that haven't logged in / refreshed tokens with the client application 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 set up 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 '
curl -L 'https://${CUSTOM_DOMAIN}/v2/web_keys' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer ' \
-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 '
```
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, a new key added and Key ID 2 can be deleted.
JSON web key set [#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 [#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 settings yaml:
```yaml
OIDC:
JWKSCacheControlMaxAge: 5m
```
Setting the value to `0` will result in a `no-store` value in the `Cache-Control` header.
# Overview
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
# Configure Zitadel with Caddy
You can either setup your environment for TLS mode external or TLS mode enabled.
TLS mode external [#tls-mode-external]
TLS mode enabled [#tls-mode-enabled]
Known issues [#known-issues]
TE: trailers header causes requests to hang [#te-trailers-header-causes-requests-to-hang]
When a browser sends a `TE: trailers` HTTP header (common with HTTP/2), Caddy may forward it upstream where it conflicts with ZITADEL's gRPC-gateway handling.
Requests that result in an error response will hang indefinitely instead of returning the error to the client.
**Workaround**: strip the header in Caddy before it reaches ZITADEL:
```
reverse_proxy h2c://zitadel:8080 {
header_up -TE
}
```
More Information [#more-information]
# Configure Zitadel with Cloudflare
Settings [#settings]
* [Make sure HTTP/2 is enabled](https://support.cloudflare.com/hc/en-us/articles/200168076-Understanding-Cloudflare-HTTP-2-and-HTTP-3-Support)
* [Verify that gRPC is enabled](https://support.cloudflare.com/hc/en-us/articles/360050483011-Understanding-Cloudflare-gRPC-support)
* [Verify that traffic is proxied through Cloudflare](https://developers.cloudflare.com/dns/manage-dns-records/reference/proxied-dns-records/)
* [Configure Zitadel to use the TLS Mode enabled](/self-hosting/manage/tls_modes#enabled)
[Cloudflare does only support gRPC with TLS!](https://support.cloudflare.com/hc/en-us/articles/360050483011-Understanding-Cloudflare-gRPC-support)
Troubleshooting [#troubleshooting]
If something is not working please check the Cloudflare WAF rules for potential violations.
These two rules are known to be triggered:
* 100001 Anomaly:Header:User-Agent - Missing Cloudflare Specials
* 100004 Anomaly:Header:User-Agent, Anomaly:Header:Referer - Missing or empty
# Configure Zitadel with Cloudflare Tunnel
[The Cloudflare tunnel client currently has an issue which disallows it to force HTTP/2 usage towards the origin.](https://github.com/cloudflare/cloudflared/issues/682)
# Configure Zitadel with Traefik
You can either setup your environment for TLS mode disabled, TLS mode external or TLS mode enabled.
TLS mode external [#tls-mode-external]
TLS mode enabled [#tls-mode-enabled]
More Information [#more-information]
# Configure Zitadel with NGINX
You can either setup your environment for TLS mode disabled, TLS mode external or TLS mode enabled.
TLS mode external [#tls-mode-external]
TLS mode enabled [#tls-mode-enabled]
More Information [#more-information]
# Configure Zitadel with Apache httpd
You can either setup your environment for TLS mode disabled, TLS mode external or TLS mode enabled.
TLS mode external [#tls-mode-external]
More Information [#more-information]
# Front Zitadel Cloud with a CDN, WAF or Reverse Proxy
Fronting Zitadel Cloud [#fronting-zitadel-cloud]
You can use your reverse-proxy, content delivery network (CDN) or web application firewall (WAF) to front Zitadel Cloud.
However, we currently do not recommend this for production settings.
To configure your service that fronts Zitadel please have a look at the vendors in this page.
Things to look out for when fronting Zitadel Cloud [#things-to-look-out-for-when-fronting-zitadel-cloud]
* Cache-control - Zitadel Cloud uses a CDN to globally distribute data. Please try to avoid overriding this header as it may lead to side-effects
* Rate Limits - Zitadel Cloud uses a combination of static and dynamic rate limits. If you receive occasional 429 headers you are rate limited.