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

## Todos for release

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

## What we did

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

**Documentation System Migration and New Docs App**

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

**Configuration and Tooling Updates**

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

**Licensing and Miscellaneous**

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

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

---------

Co-authored-by: Federico Coppede <[email protected]>
This commit is contained in:
Florian Forster
2026-01-29 08:38:36 -08:00
committed by GitHub
co-authored by Federico Coppede
parent 7b6395c348
commit d8ea041c56
2826 changed files with 78739 additions and 23571 deletions
+203
View File
@@ -0,0 +1,203 @@
{
"root": true,
"pages": [
"introduction",
{
"title": "Core Resources",
"pages": [
{
"title": "V2",
"pages": [
{
"title": "User",
"pages": [
"../reference/api/user"
]
},
{
"title": "Session",
"pages": [
"../reference/api/session"
]
},
{
"title": "OIDC",
"pages": [
"../reference/api/oidc"
]
},
{
"title": "SAML",
"pages": [
"../reference/api/saml"
]
},
{
"title": "Settings",
"pages": [
"../reference/api/settings"
]
},
{
"title": "Feature",
"pages": [
"../reference/api/feature"
]
},
{
"title": "Organization",
"pages": [
"../reference/api/org"
]
},
{
"title": "Identity Provider",
"pages": [
"../reference/api/idp"
]
},
{
"title": "Web Key",
"pages": [
"../reference/api/webkey"
]
},
{
"title": "Action",
"pages": [
"../reference/api/action"
]
},
{
"title": "Instance",
"pages": [
"../reference/api/instance"
]
},
{
"title": "Project",
"pages": [
"../reference/api/project"
]
},
{
"title": "Application",
"pages": [
"../reference/api/application"
]
},
{
"title": "Authorizations",
"pages": [
"../reference/api/authorization"
]
},
{
"title": "Internal Permissions",
"pages": [
"../reference/api/internal_permission"
]
}
]
},
{
"title": "V1",
"pages": [
{
"title": "Authenticated User",
"pages": [
"../reference/api-v1/auth"
]
},
{
"title": "Organization Objects",
"pages": [
"../reference/api-v1/management"
]
},
{
"title": "Instance Objects",
"pages": [
"../reference/api-v1/admin"
]
},
{
"title": "Instance Lifecycle",
"pages": [
"../reference/api-v1/system"
]
},
"migration_v1_to_v2"
]
},
{
"title": "Assets",
"pages": [
"assets/assets"
]
}
]
},
{
"title": "Observability",
"pages": [
"observability/metrics",
"observability/health"
]
},
{
"title": "Provision Users",
"pages": [
"scim2"
]
},
{
"title": "gRPC Status Codes",
"url": "statuscodes"
},
{
"title": "Rate Limits (Cloud)",
"url": "/legal/policies/rate-limit-policy"
},
{
"title": "Benchmarks",
"pages": [
{
"title": "v2.65.0",
"pages": [
"benchmarks/v2.65.0/machine_jwt_profile_grant/index"
]
},
{
"title": "v2.66.0",
"pages": [
"benchmarks/v2.66.0/machine_jwt_profile_grant/index"
]
},
{
"title": "v2.70.0",
"pages": [
"benchmarks/v2.70.0/machine_jwt_profile_grant/index",
"benchmarks/v2.70.0/oidc_session/index"
]
},
{
"title": "v4",
"pages": [
"benchmarks/v4/add_session/index",
"benchmarks/v4/human_password_login/index",
"benchmarks/v4/introspect/index",
"benchmarks/v4/machine_client_credentials_login/index",
"benchmarks/v4/machine_jwt_profile_grant/index",
"benchmarks/v4/machine_pat_login/index",
"benchmarks/v4/manipulate_user/index",
"benchmarks/v4/oidc_session/index",
"benchmarks/v4/otp_session/index",
"benchmarks/v4/password_session/index",
"benchmarks/v4/user_info/index"
]
}
]
}
]
}
@@ -0,0 +1,347 @@
---
title: 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
Append claims returned on OIDC requests.
### 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 hardcoded claim
Extend the claims by a hardcoded value.
<details open="">
<summary>Code example</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/add_claim.js
```
</details>
### Set dynamic claim from user metadata
Extend the claims by dynamically read metadata from a user and sets the picture-claim if idpPicture-metadata value is present.
<details open="">
<summary>{props.summary ? props.summary : 'Code example'}</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/add_picture_claim_from_idp_metadata.js
```
</details>
### Set dynamic claim from organization metadata
Extend the claims by dynamically read metadata from an organization and sets the present metadata.
<details open="">
<summary>Code example</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/org_metadata_claim.js
```
</details>
### Custom role mapping in claims
Some products require specific role mapping from ZITADEL, no worries we got you covered 😉
<details open="">
<summary>Code example</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/custom_roles.js
```
</details>
### Custom role mapping including org metadata in claims
There's even a possibility to use the metadata of organizations the user is granted to
<details open="">
<summary>Code example</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/custom_roles_org_metadata.js
```
</details>
## Customize SAML response
Append attributes returned on SAML requests.
### Triggers
- Complement SAMLResponse
- [Pre SAMLResponse creation](./customize-samlresponse#pre-saml-response-creation)
### Custom role mapping in attributes
Some products require specific role mapping from ZITADEL, no worries we got you covered 😉
<details open="">
<summary>Code example</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/set_custom_attribute.js
```
</details>
### Set dynamic attribute from organization metadata
Extend the attributes by dynamically read metadata from an organization and sets the present metadata.
<details open="">
<summary>Code example</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/org_metadata_attribute.js
```
</details>
## Manipulate user
You can automate manual tasks like setting default grants during user creation.
### Set email always verified
Useful if you trust the provided information or don't want the users to verify their e-mail addresses.
#### Triggers
- Internal Authentication
- [Pre Creation](/apis/actions/internal-authentication#pre-creation)
- External Authentication
- [Pre Creation](/apis/actions/external-authentication#pre-creation)
<details open="">
<summary>Code example</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/verify_email.js
```
</details>
### Add grants to users
Allows you to add default user grants to a user after it was created or federated.
#### Triggers
- Internal Authentication
- [Post Creation](/apis/actions/internal-authentication#post-creation)
- External Authentication
- [Post Creation](/apis/actions/external-authentication#post-creation)
<details open="">
<summary>Code example</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/add_user_grant.js
```
</details>
### Add metadata to users
Adding metadata to users allows you to set default metadata on users.
#### 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)
<details open="">
<summary>Code example</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/add_metadata.js
```
</details>
## 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
- External Authentication
- [Post Authentication](./external-authentication#post-authentication)
### Fields provided by Okta as OIDC IdP
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.
<details open="">
<summary>Code example</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/okta_identity_provider.js
```
</details>
### 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.
<details open="">
<summary>Code example</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/gitlab_identity_provider.js
```
</details>
### Fields provided by Github
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.
<details open="">
<summary>Code example</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/github_identity_provider.js
```
</details>
### 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.
<details open="">
<summary>Code example</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/set_idp_picture_metadata.js
```
</details>
### 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.
<details open="">
<summary>Code example</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/okta_saml_prefil_register_form.js
```
</details>
### 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.
<details open="">
<summary>Code example</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/entra_id_saml_prefil_register_form.js
```
</details>
### 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.
<details open="">
<summary>Code example</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/post_auth_saml.js
```
</details>
## 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
Execution paths might change based on the application initiating the authentication.
#### 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)
<details open="">
<summary>Code example</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/execute_action_on_specific_app.js
```
</details>
This example uses [zitadel's log module](/apis/actions/modules#log)
### Check authentication error
Your action can also check for errors during the login process.
#### Triggers
- Internal Authentication
- [Post Authentication](/apis/actions/internal-authentication#post-authentication)
- External Authentication
- [Post Authentication](/apis/actions/external-authentication#post-authentication)
<details open="">
<summary>Code example</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/post_auth_log.js
```
</details>
This example uses [zitadel's log module](/apis/actions/modules#log)
### 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).
<details open="">
<summary>Code example</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/throw_error.js
```
</details>
@@ -0,0 +1,72 @@
---
title: 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)
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
- `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
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
- `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
@@ -0,0 +1,34 @@
---
title: 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
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
- `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
@@ -0,0 +1,137 @@
---
title: 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
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
- `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
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
- `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 nick name
- `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.
<ul><li>0: unspecified</li><li>1: female</li><li>2: male</li><li>3: diverse</li></ul>
- `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
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
- `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)`)`
@@ -0,0 +1,103 @@
---
title: 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
A user has authenticated directly at ZITADEL.
ZITADEL validated the users inputs for password, one-time password 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
- `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
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
- `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 nick name
- `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.
<ul><li>0: unspecified</li><li>1: female</li><li>2: male</li><li>3: diverse</li></ul>
- `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
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
- `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)`)`
@@ -0,0 +1,58 @@
---
title: Actions
---
This page describes the options you have when writing ZITADEL actions scripts.
## Language
ZITADEL interprets the scripts as JavaScript.
Make sure your scripts are ECMAScript 5.1(+) compliant.
Go to the [goja GitHub page](https://github.com/dop251/goja) for detailed reference about the underlying library features and limitations.
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).
Actions are a key feature to extend the functionality of ZITADEL and continuously improve the feature and expand the use cases. Check out our [roadmap](https://zitadel.com/roadmap) for more details.
## Action
The action object describes the logic defined by the user. Actions can be linked to multiple [triggers](#flows).
The name of the action must corelate with the javascript function in the code section. This function will be called by ZITADEL.
For reading and mutating state, the runtime executes the function that has the same name as the action.
The function receives the JavaScript objects `ctx` and `api`.
The object `ctx` provides readable information as object properties and by callable functions.
The object `api` provides mutable properties and state mutating functions.
The script of an action called **doSomething** should have a function called `doSomething` and look something like this:
```js
function doSomething(ctx, api){
// read from ctx and manipulate with api
}
```
## Flows
Flows are the links between an [action](#action) and a specific point during a user interaction with ZITADEL. These specific point are called [Trigger Types](#trigger-types).
## Trigger Types
Trigger types define the point during execution of request. Each trigger defines which readable information (`ctx`) and mutual properties (`api`) are passed into the called function as well as which libraries are available for the function. Each trigger type is described in the [available flow types section](#available-flow-types)
## Available Flow Types
Currently ZITADEL provides the following flows:
- [Internal Authentication](./internal-authentication)
- [External Authentication](./external-authentication)
- [Complement Token](./complement-token)
- [Complement SAML Response](./customize-samlresponse)
## Available Modules inside Javascript
- [HTTP module](./modules#http) to call API's
- [Logging module](./modules#log) logs information to stdout
- [UUID module](./modules#uuid) generates uuids
+148
View File
@@ -0,0 +1,148 @@
---
title: Modules
---
ZITADEL provides the following modules.
## HTTP
This module provides functionality to call REST APIs.
### Import
```js
let http = require('zitadel/http')
```
### `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
- `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
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
```js reference
https://github.com/zitadel/actions/blob/main/examples/make_api_call.js#L10-L20
```
## Log
The log module provides you with the functionality to log to stdout.
### Import
```js
let logger = require("zitadel/log")
```
### `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
```js
logger.log("This is an info log.")
logger.warn("This is a warn log.")
logger.error("This is an error log.")
```
#### Parameters
- `msg` *string*
The message you want to print out.
## UUID
This module provides functionality to generate a UUID
### Import
```js
let uuid = require("zitadel/uuid")
```
### `uuid.vX()` 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
- `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
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
```js
let uuid = require("zitadel/uuid")
function setUUID(ctx, api) {
if (api.metadata === undefined) {
return;
}
api.v1.user.appendMetadata('custom-id', uuid.v4());
}
```
+214
View File
@@ -0,0 +1,214 @@
---
title: Objects
---
## 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
- `key` *string*
- `value` Array of *byte*
## metadata result
- `count` *number*
- `sequence` *number*
- `timestamp` *Date*
- `metadata` Array of [*metadata*](#metadata)
## metadata
- `creationDate` *Date*
- `changeDate` *Date*
- `resourceOwner` *string*
- `sequence` *number*
- `key` *string*
- `value` `Any`
## user grant
- `projectID` *string*
Required. Id of the project to be granted
- `projectGrantID` *string*
Optional. If the grant is for a project grant, include projectGrantID
- `roles` Array of *string*
Containing the roles
## user
- `id` *string*
- `creationDate` *Date*
- `changeDate` *Date*
- `resourceOwner` *string*
- `sequence` *number*
Unsigned 64 bit integer
- `state` *number*
<ul><li>0: unspecified</li><li>1: active</li><li>2: inactive</li><li>3: deleted</li><li>4: locked</li><li>5: suspended</li><li>6: initial</li></ul>
- `username` *string*
- `loginNames` Array of *string*
- `preferredLoginName` *string*
- `human`
Set if the user is 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*
<ul><li>0: unspecified</li><li>1: female</li><li>2: male</li><li>3: diverse</li></ul>
- `email` *string*
- `isEmailVerified` *boolean*
- `phone` *string*
- `isPhoneVerified` *boolean*
- `machine`
Set if the user is a machine
- `name` *string*
- `description` *string*
## human user
- `id` *string*
- `creationDate` *Date*
- `changeDate` *Date*
- `resourceOwner` *string*
- `sequence` *number*
- `state` *number*
<ul><li>0: unspecified</li><li>1: active</li><li>2: inactive</li><li>3: deleted</li><li>4: locked</li><li>5: suspended</li><li>6: initial</li></ul>
- `username` *string*
- `loginNames` Array of *string*
- `preferredLoginName` *string*
- `profile`
- `firstName` *string*
- `lastName` *string*
- `nickName` *string*
- `displayName` *string*
- `preferredLanguage` *string*
In [RFC 5646](https://www.rfc-editor.org/rfc/rfc5646) format
- `email`
- `email` *string*
- `isEmailVerified` *boolean*
- `phone`
- `phone` *string*
- `isPhoneVerified` *boolean*
## Auth Request
This object contains context information about the request to the [authorization endpoint](/apis/openidoauth/endpoints#authorization-endpoint).
- `id` *string*
- `agentId` *string*
- `creationDate` *Date*
- `changeDate` *Date*
- `browserInfo` *browserInfo*
- `userAgent` *string*
- `acceptLanguage` *string*
- `remoteIp` *string*
- `applicationId` *string*
- `callbackUri` *string*
- `transferState` *string*
- `prompt` Array of *Number*
<ul><li>0: not specified</li><li>1: none</li><li>2: login</li><li>3: consent</li><li>4: select_account</li><li>5: create</li></ul>
- `uiLocales` Array of *string*
- `loginHint` *string*
- `maxAuthAge` *Number*
Duration in nanoseconds
- `instanceId` *string*
- `request`
- `oidc`
- `scopes` Array of *string*
- `userId` *string*
- `userName` *string*
- `loginName` *string*
- `displayName` *string*
- `resourceOwner` *string*
- `requestedOrgId` *string*
- `requestedOrgName` *string*
- `requestedPrimaryDomain` *string*
- `requestedOrgDomain` *bool*
- `applicationResourceOwner` *string*
- `privateLabelingSetting` *Number*
<ul><li>0: Unspecified</li><li>1: Enforce project's policy</li><li>2: Allow user's organization login policy</li></ul>
- `selectedIdpConfigId` *string*
- `linkingUsers` Array of [*ExternalUser*](#external-user)
- `passwordVerified` *bool*
- `mfasVerified` Array of *Number*
<ul><li>0: OTP</li><li>1: U2F</li><li>2: U2F User verification</li></ul>
- `audience` Array of *string*
- `authTime` *Date*
## 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
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*
Additionally there could additional fields depending on the configuration of your [project](../../guides/manage/console/projects-overview#role-settings) and your [application](../../guides/manage/console/applications-overview#token-settings)
## user grant list
This object represents a list of user grant 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*
<ul><li>0: unspecified</li><li>1: active</li><li>2: inactive</li><li>3: removed</li></ul>
- `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
+12
View File
@@ -0,0 +1,12 @@
---
title: Core Resources
sidebar_label: Core Resources
---
import DocCardList from '@/components/docusaurus/doc-card-list';
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:
<DocCardList />
+197
View File
@@ -0,0 +1,197 @@
---
title: zitadel/assets
---
## AssetsService
### UploadDefaultLabelPolicyFont()
> UploadDefaultLabelPolicyFont()
POST: /instance/policy/label/font
### GetDefaultLabelPolicyFont()
> GetDefaultLabelPolicyFont()
GET: /instance/policy/label/font
### GetPreviewDefaultLabelPolicyFont()
> GetPreviewDefaultLabelPolicyFont()
GET: /instance/policy/label/font/_preview
### UploadDefaultLabelPolicyIcon()
> UploadDefaultLabelPolicyIcon()
POST: /instance/policy/label/icon
### UploadDefaultLabelPolicyIconDark()
> UploadDefaultLabelPolicyIconDark()
POST: /instance/policy/label/icon/dark
### GetDefaultLabelPolicyIcon()
> GetDefaultLabelPolicyIcon()
GET: /instance/policy/label/icon
### GetDefaultLabelPolicyIconDark()
> GetDefaultLabelPolicyIconDark()
GET: /instance/policy/label/icon/dark
### GetPreviewDefaultLabelPolicyIcon()
> GetPreviewDefaultLabelPolicyIcon()
GET: /instance/policy/label/icon/_preview
### GetPreviewDefaultLabelPolicyIconDark()
> GetPreviewDefaultLabelPolicyIconDark()
GET: /instance/policy/label/icon/dark/_preview
### UploadDefaultLabelPolicyLogo()
> UploadDefaultLabelPolicyLogo()
POST: /instance/policy/label/logo
### UploadDefaultLabelPolicyLogoDark()
> UploadDefaultLabelPolicyLogoDark()
POST: /instance/policy/label/logo/dark
### GetDefaultLabelPolicyLogo()
> GetDefaultLabelPolicyLogo()
GET: /instance/policy/label/logo
### GetDefaultLabelPolicyLogoDark()
> GetDefaultLabelPolicyLogoDark()
GET: /instance/policy/label/logo/dark
### GetPreviewDefaultLabelPolicyLogo()
> GetPreviewDefaultLabelPolicyLogo()
GET: /instance/policy/label/logo/_preview
### GetPreviewDefaultLabelPolicyLogoDark()
> GetPreviewDefaultLabelPolicyLogoDark()
GET: /instance/policy/label/logo/dark/_preview
### UploadOrgLabelPolicyFont()
> UploadOrgLabelPolicyFont()
POST: /org/policy/label/font
### GetOrgLabelPolicyFont()
> GetOrgLabelPolicyFont()
GET: /org/policy/label/font
### GetPreviewOrgLabelPolicyFont()
> GetPreviewOrgLabelPolicyFont()
GET: /org/policy/label/font/_preview
### UploadOrgLabelPolicyIcon()
> UploadOrgLabelPolicyIcon()
POST: /org/policy/label/icon
### UploadOrgLabelPolicyIconDark()
> UploadOrgLabelPolicyIconDark()
POST: /org/policy/label/icon/dark
### GetOrgLabelPolicyIcon()
> GetOrgLabelPolicyIcon()
GET: /org/policy/label/icon
### GetOrgLabelPolicyIconDark()
> GetOrgLabelPolicyIconDark()
GET: /org/policy/label/icon/dark
### GetPreviewOrgLabelPolicyIcon()
> GetPreviewOrgLabelPolicyIcon()
GET: /org/policy/label/icon/_preview
### GetPreviewOrgLabelPolicyIconDark()
> GetPreviewOrgLabelPolicyIconDark()
GET: /org/policy/label/icon/dark/_preview
### UploadOrgLabelPolicyLogo()
> UploadOrgLabelPolicyLogo()
POST: /org/policy/label/logo
### UploadOrgLabelPolicyLogoDark()
> UploadOrgLabelPolicyLogoDark()
POST: /org/policy/label/logo/dark
### GetOrgLabelPolicyLogo()
> GetOrgLabelPolicyLogo()
GET: /org/policy/label/logo
### GetOrgLabelPolicyLogoDark()
> GetOrgLabelPolicyLogoDark()
GET: /org/policy/label/logo/dark
### GetPreviewOrgLabelPolicyLogo()
> GetPreviewOrgLabelPolicyLogo()
GET: /org/policy/label/logo/_preview
### GetPreviewOrgLabelPolicyLogoDark()
> GetPreviewOrgLabelPolicyLogoDark()
GET: /org/policy/label/logo/dark/_preview
### UploadMyUserAvatar()
> UploadMyUserAvatar()
POST: /users/me/avatar
### GetMyUserAvatar()
> GetMyUserAvatar()
GET: /users/me/avatar
@@ -0,0 +1,43 @@
import OutputSource from "./output.json";
import { BenchmarkChart } from '@/components/benchmark_chart';
TODO: describe the outcome of the test?
## Performance test results
| Metric | Value |
|-:-------------------------------------|-:-----|
| Baseline | none |
| Purpose | |
| Test start | UTC |
| Test duration | 30min |
| Executed test | |
| k6 version | |
| VUs | |
| Client location | |
| Client machine specification | vCPU: <br/> memory: Gb |
| ZITADEL location | |
| ZITADEL container specification | vCPU: <br/> Memory: Gb <br/>Container count: |
| ZITADEL Version | |
| ZITADEL Configuration | |
| ZITADEL feature flags | |
| Database | type: psql<br />version: |
| Database location | |
| Database specification | vCPU: <br/> memory: Gb |
| ZITADEL metrics during test | |
| Observed errors | |
| Top 3 most expensive database queries | |
| Database metrics during test | |
| k6 Iterations per second | |
| k6 overview | |
| k6 output | |
| flowchart outcome | |
## Endpoint latencies
<BenchmarkChart testResults={OutputSource} />
## k6 output
```bash
TODO: add summary of k6
```
+107
View File
@@ -0,0 +1,107 @@
---
title: Benchmarks
sidebar_label: Benchmarks
---
import DocCardList from '@/components/docusaurus/doc-card-list';
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
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
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
### 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
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
#### 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
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
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
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
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
This flowchart shows the procedure after running a test.
![](../../../public/img/benchmark/Flowchart.svg)
## Baseline
Will be established as soon as the goal described above is reached.
## Test results
This chapter provides a table linking to the detailed test results.
<DocCardList />
@@ -0,0 +1,70 @@
---
title: machine jwt profile grant benchmark of zitadel v2.65.0
sidebar_label: machine jwt profile grant
---
import OutputSource from "./output.json";
import { BenchmarkChart } from '@/components/benchmark_chart';
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
| 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<br/>Memory: 512 MiB<br/>Container count: 2 |
| Zitadel feature flags | none |
| Database | postgres v15 |
| Database location | US1 |
| Database specification | vCPUs: 4<br/>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)`<br/>2: latest sequence query during push events<br/>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
<BenchmarkChart testResults={OutputSource} />
## k6 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
```
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,98 @@
---
title: machine jwt profile grant benchmark of zitadel v2.66.0
sidebar_label: machine jwt profile grant
---
import OutputSource from "./output.json";
import { BenchmarkChart } from '@/components/benchmark_chart';
The tests showed heavy database load by time by the first two database queries. These queries need to be analyzed further.
## 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<br/> Memory: 512 Mib <br/>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<br />version: v15.8 |
| Database location | US1 |
| Database specification | vCPU: 8<br/> memory: 32Gib |
| ZITADEL metrics during test | |
| Observed errors | |
| Top 3 most expensive database queries | 1: Write events using the newly added eventstore.push function<br/>2: Query events by instance\_id, aggregate\_type, aggregate\_id, event\_types<br/>3: Query user<br/> |
| k6 Iterations per second | 439 |
| k6 output | [output](#k6-output) |
| flowchart outcome | Scale out |
## Endpoint latencies
<BenchmarkChart testResults={OutputSource} />
## k6 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
```
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,98 @@
---
title: machine jwt profile grant benchmark of zitadel v2.70.0
sidebar_label: machine jwt profile grant
---
import OutputSource from "./output.json";
import { BenchmarkChart } from '@/components/benchmark_chart';
The performance goals of [this issue](https://github.com/zitadel/zitadel/issues/8352) are reached. Next we will test linear scalability.
## 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<br/> Memory: 6 Gi <br/>Container min scale: 2<br/>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<br />version: v17.2 |
| Database location | US1 |
| Database specification | vCPU: 8<br/> memory: 32Gib |
| ZITADEL metrics during test | |
| Observed errors | |
| Top 3 most expensive database queries | 1: Write events using eventstore.push function<br/>2: Query user<br/>3: Query events by instance id, aggregate type, aggregate id, event types, owner<br/> |
| k6 Iterations per second | 1806 |
| k6 output | [output](#k6-output) |
| flowchart outcome | Scale out |
## Endpoint latencies
<BenchmarkChart testResults={OutputSource} />
## k6 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
```
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,106 @@
---
title: oidc session benchmark of Zitadel v2.70.0
sidebar_label: oidc session
---
import OutputSource from "./output.json";
import { BenchmarkChart } from '@/components/benchmark_chart';
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
| 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<br/> Memory: 6 Gi <br/>Container min scale: 2<br/>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<br />version: v17.2 |
| Database location | US1 |
| Database specification | vCPU: 8<br/> memory: 32Gib |
| ZITADEL metrics during test | |
| Observed errors | |
| Top 3 most expensive database queries | 1: lock current\_states table<br/>2: write events<br/>3: get events for projection<br/> |
| k6 Iterations per second | 153 |
| k6 output | [output](#k6-output) |
| flowchart outcome | Resolve locking issue |
## Endpoint latencies
<BenchmarkChart testResults={OutputSource} />
## k6 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
```
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,97 @@
---
title: create session benchmark of zitadel v4
sidebar_label: create session
---
import OutputSource from "./output.json";
import { BenchmarkChart } from '@/components/benchmark_chart';
Benchmark results of v4 release of Zitadel.
## 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<br/> Memory: 6 Gi <br/>Container min scale: 2<br/>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<br />version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8<br/> 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<br/>2: write events<br/>3: list members<br/> |
| k6 Iterations per second | 607 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
## Endpoint latencies
Until :37 were containers starting because it was a cold start test.
<BenchmarkChart testResults={OutputSource} />
## k6 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
```
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,104 @@
---
title: human password login benchmark of zitadel v4
sidebar_label: human password login
---
import OutputSource from "./output.json";
import { BenchmarkChart } from '@/components/benchmark_chart';
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
| 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<br/> Memory: 6 Gi <br/>Container min scale: 2<br/>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<br />version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8<br/> memory: 32Gib |
| ZITADEL metrics during test | |
| Observed errors | |
| Top 3 most expensive database queries | 1: Locking projections for reduce<br/>2: Query events by aggregate type and event type<br/>3: Query events by instance id, aggregate types, event types, position<br/> |
| k6 Iterations per second | 39 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
## Endpoint latencies
Until :45 were containers starting because it was a cold start test.
<BenchmarkChart testResults={OutputSource} />
## k6 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
```
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,98 @@
---
title: introspection benchmark of zitadel v4
sidebar_label: introspection
---
import OutputSource from "./output.json";
import { BenchmarkChart } from '@/components/benchmark_chart';
Benchmark results of v4 release of Zitadel.
## 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<br/> Memory: 6 Gi <br/>Container min scale: 2<br/>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<br />version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8<br/> 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<br/>2: Query events by instance id, aggregate types, event types, position<br/>3: Query executions<br/> |
| k6 Iterations per second | 18 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
## Endpoint latencies
<BenchmarkChart testResults={OutputSource} />
## k6 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
```
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,103 @@
---
title: machine client credentials login benchmark of zitadel v4
sidebar_label: machine client credentials login
---
import OutputSource from "./output.json";
import { BenchmarkChart } from '@/components/benchmark_chart';
Benchmark results of v4 release of Zitadel.
## 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<br/> Memory: 6 Gi <br/>Container min scale: 2<br/>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<br />version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8<br/> memory: 32Gib |
| ZITADEL metrics during test | |
| Observed errors | |
| Top 3 most expensive database queries | 1: Locking projections for reduce<br/>2: Query events by aggregate type and event type<br/>3: user by id query<br/> |
| k6 Iterations per second | 424 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
## Endpoint latencies
Until :37 were containers starting because it was a cold start test.
<BenchmarkChart testResults={OutputSource} />
## k6 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
```
@@ -0,0 +1,95 @@
---
title: machine jwt profile grant benchmark of zitadel v4
sidebar_label: machine jwt profile grant
---
import OutputSource from "./output.json";
import { BenchmarkChart } from '@/components/benchmark_chart';
Benchmark results of v4 release of Zitadel.
## 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<br/> Memory: 6 Gi <br/>Container min scale: 2<br/>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<br />version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8<br/> 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<br/>2: write events<br/>3: get user by id<br/> |
| k6 Iterations per second | 851 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
## Endpoint latencies
<BenchmarkChart testResults={OutputSource} />
## k6 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
```
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,102 @@
---
title: machine pat login benchmark of zitadel v4
sidebar_label: machine pat login
---
import OutputSource from "./output.json";
import { BenchmarkChart } from '@/components/benchmark_chart';
Benchmark results of v4 release of Zitadel.
## 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<br/> Memory: 6 Gi <br/>Container min scale: 2<br/>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<br />version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8<br/> memory: 32Gib |
| ZITADEL metrics during test | |
| Observed errors | |
| Top 3 most expensive database queries | 1: Locking projections for reduce<br/>2: Query events by aggregate type and event type<br/>3: Query events by instance id, aggregate types, event types, position<br/> |
| k6 Iterations per second | 1906 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
## Endpoint latencies
Until :37 were containers starting because it was a cold start test.
<BenchmarkChart testResults={OutputSource} />
## k6 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
```
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,102 @@
---
title: manipulate user benchmark of zitadel v4
sidebar_label: manipulate user
---
import OutputSource from "./output.json";
import { BenchmarkChart } from '@/components/benchmark_chart';
Benchmark results of v4 release of Zitadel.
## 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<br/> Memory: 6 Gi <br/>Container min scale: 2<br/>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<br />version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8<br/> 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<br/>2: write events<br/>3: query user by id<br/> |
| k6 Iterations per second | 16 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
## Endpoint latencies
Each iteration creates, updates and deletes a user.
<BenchmarkChart testResults={OutputSource} />
## k6 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
```
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,104 @@
---
title: oidc session benchmark of zitadel v4
sidebar_label: oidc session
---
import OutputSource from "./output.json";
import { BenchmarkChart } from '@/components/benchmark_chart';
Benchmark results of v4 release of Zitadel.
## 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<br/> Memory: 6 Gi <br/>Container min scale: 2<br/>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<br />version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8<br/> 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<br/>2: writing events<br/>3: query memberships<br/> |
| k6 Iterations per second | 105 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
## Endpoint latencies
<BenchmarkChart testResults={OutputSource} />
## k6 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
```
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,98 @@
---
title: otp session benchmark of zitadel v4
sidebar_label: otp session
---
import OutputSource from "./output.json";
import { BenchmarkChart } from '@/components/benchmark_chart';
Benchmark results of v4 release of Zitadel.
## 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<br/> Memory: 6 Gi <br/>Container min scale: 2<br/>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<br />version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8<br/> 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<br/>2: lock projection<br/>3: write events<br/> |
| k6 Iterations per second | 60 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
## Endpoint latencies
<BenchmarkChart testResults={OutputSource} />
## k6 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
```
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,99 @@
---
title: session with password benchmark of zitadel v4
sidebar_label: password session
---
import OutputSource from "./output.json";
import { BenchmarkChart } from '@/components/benchmark_chart';
Benchmark results of v4 release of Zitadel.
## 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<br/> Memory: 6 Gi <br/>Container min scale: 2<br/>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<br />version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8<br/> memory: 32Gib |
| ZITADEL metrics during test | |
| Observed errors | Container startup latency is > 3 minutes |
| Top 3 most expensive database queries | 1: lock projection<br/>2: query events by instance id, aggregate types, event types, position<br/>3: query events by aggregate type, event type<br/> |
| k6 Iterations per second | 51 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
## Endpoint latencies
<BenchmarkChart testResults={OutputSource} />
## k6 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
```
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,96 @@
---
title: user info benchmark of zitadel v4
sidebar_label: user info
---
import OutputSource from "./output.json";
import { BenchmarkChart } from '@/components/benchmark_chart';
Benchmark results of v4 release of Zitadel.
## 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<br/> Memory: 6 Gi <br/>Container min scale: 2<br/>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<br />version: v17.4 |
| Database location | US1 |
| Database specification | vCPU: 8<br/> 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<br/>2: events by instance id, aggregate types, event types, position<br/>3: user by id<br/> |
| k6 Iterations per second | 2653 |
| k6 output | [output](#k6-output) |
| flowchart outcome | |
## Endpoint latencies
<BenchmarkChart testResults={OutputSource} />
## k6 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
```
File diff suppressed because it is too large Load Diff
+333
View File
@@ -0,0 +1,333 @@
---
title: ZITADEL API Reference Overview
sidebar_label: Overview
---
import { ApiCard } from "@/components/apicard";
import Column from "@/components/column";
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-resource-based).
[Actions](/guides/manage/console/actions-overview) allow to extend ZITADEL with custom code to change default behaviors or calling external systems.
## Authentication & authorization
### Authentication for human users (interactive)
ZITADEL implements industry standards such as OpenID Connect, OAuth 2.0, or SAML for authentication.
Please refer to our guides how to [authenticate users](/guides/integrate/login/login-users) through an interactive authentication process.
For user authentication on devices with limited accessibility (eg, SmartTV, Smart Watch 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 users and machines
Service users allow for machine-to-machine (M2M) communication.
Follow the guides to learn how to [authenticate service users](/guides/integrate/service-users/authenticate-service-users).
Accessing the ZITADEL APIs through a service user 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
- [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 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
### Custom
ZITADEL allows to authenticate users by creating a session with the [Session API](/reference/api/session), get OIDC authentication request details with the [OIDC service API](/reference/api/oidc) or get SAML request details with the [SAML service API](/reference/api/saml).
User authorizations 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 (resource-based)
ZITADEL provides APIs for each [core resource](/apis/v2):
- [User](/reference/api/user)
- [Session](/reference/api/session)
- [Settings](/reference/api/settings)
<Callout>
We are migrating to a resource-based API approach.
You might need to use the existing [service-based](#zitadel-apis-service-based) APIs for now to manage Organizations, Instances, Assets etc.
</Callout>
## ZITADEL APIs (service-based)
<Callout title="Prefer resource-based API">
ZITADEL APIs were organized by UseCase/Context, such as Auth API for authenticated users and Management API for organization managers.
This led to confusion about which API to use, particularly for requests that could be useful across multiple APIs but with different filters.
For instance, SearchUsers on an Instance Level or on an Organization Level.
To address this issue, ZITADEL is migrating to a [resource-based API](#zitadel-apis-resource-based).
</Callout>
<ApiCard title="Authentication" type="AUTH">
<Column>
<div>
### Authentication
The authentication API (aka Auth API) is used for all operations on the currently logged in user. The user id is taken from the sub claim in the token.
</div>
<div className="apicard-right">
#### Auth GRPC
Endpoint:
`${CUSTOM_DOMAIN}/zitadel.auth.v1.AuthService/`
Definition:
[Auth Proto](https://github.com/zitadel/zitadel/blob/main/proto/zitadel/auth.proto)
#### Auth REST
Endpoint:
`${CUSTOM_DOMAIN}/auth/v1/`
API Reference:
[OpenAPI Docs](/reference/api/auth)
</div>
</Column>
</ApiCard>
<ApiCard title="Management" type="MGMT">
<Column>
<div>
### Management
The management API is as the name states the interface where systems can mutate IAM objects like, organizations, projects, clients, users and so on if they have the necessary access rights.
To identify the current organization you can send a header `x-zitadel-orgid` or if no header is set, the organization of the authenticated user is set.
</div>
<div className="apicard-right">
#### Mgmt GRPC
Endpoint:
`${CUSTOM_DOMAIN}/zitadel.management.v1.ManagementService/`
Definition:
[Management Proto](https://github.com/zitadel/zitadel/blob/main/proto/zitadel/management.proto)
#### Mgmt REST
Endpoint:
`${CUSTOM_DOMAIN}/management/v1/`
API Reference:
[OpenAPI Docs](/reference/api/management)
</div>
</Column>
</ApiCard>
<ApiCard title="Administration" type="ADMIN">
<Column>
<div>
### Administration
This API is intended to configure and manage one ZITADEL instance itself.
</div>
<div className="apicard-right">
#### Admin GRPC
Endpoint:
`${CUSTOM_DOMAIN}/zitadel.admin.v1.AdminService/`
Definition:
[Admin Proto](https://github.com/zitadel/zitadel/blob/main/proto/zitadel/admin.proto)
#### Admin REST
Endpoint:
`${CUSTOM_DOMAIN}/admin/v1/`
API Reference:
[OpenAPI Docs](/reference/api/admin)
</div>
</Column>
</ApiCard>
<ApiCard title="Administration" type="SYSTEM">
<Column>
<div>
### 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).
</div>
<div className="apicard-right">
#### 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
Endpoint:
`${CUSTOM_DOMAIN}/system/v1/`
API Reference:
[OpenAPI Docs](/reference/api/system)
</div>
</Column>
</ApiCard>
<ApiCard title="Assets" type="ASSET">
<Column>
<div>
### 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.
</div>
<div>
#### Assets REST
Endpoint:
`${CUSTOM_DOMAIN}/assets/v1/`
Definition:
[Assets](./assets/assets)
</div>
</Column>
</ApiCard>
## API definitions
Each service's proto definition is located in the source control on GitHub.
As we generate the REST services and Swagger file out of the proto definition we recommend that you rely on the proto file.
We annotate the corresponding REST methods on each possible call as well as the AuthN and AuthZ requirements.
The last API (assets) is only a REST API because ZITADEL uses multipart form data for certain elements.
### SDKs
ZITADEL provides some [official and community supported SDKs](/sdk-examples/introduction) for multiple languages and frameworks.
Most languages allow you to build a client from proto definitions, which allows you to build your own client in case an SDK is missing.
### Proto
All of our APIs are generated by proto definitions. You can find all the proto definitions in the [Proto API Definitions](https://github.com/zitadel/zitadel/tree/main/proto/zitadel).
> More about [Protocol Buffer](https://developers.google.com/protocol-buffers)
### Example
See below for an example with the call **GetMyUser**.
```go
//User
rpc GetMyUser(google.protobuf.Empty) returns (UserView) {
option (google.api.http) = {
get: "/users/me"
};
option (zitadel.v1.auth_option) = {
permission: "authenticated"
};
}
```
As you can see the `GetMyUser` function is also available as a REST service under the path `/users/me`.
In the table below you can see the URI of those calls.
| Service | URI |
| :------ | :---------------------------------------------------- |
| REST | `${CUSTOM_DOMAIN}/auth/v1/users/me` |
| GRPC | `${CUSTOM_DOMAIN}/zitadel.auth.v1.AuthService/GetMyUser` |
## 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 Login and Management Console UI, which you'll find under `${CUSTOM_DOMAIN}/ui/console/`.
Are you self-hosting and having troubles with _Instance not found_ errors? [Check out this page](/self-hosting/manage/custom-domain).
## 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.
```yaml
/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/
/ui/
/oidc/v1/
/saml/v2/
/oauth/v2/
/device
/.well-known/openid-configuration
/openapi/
/idps/callback
/v2beta/
/zitadel.user.v2beta.UserService/
/zitadel.session.v2beta.SessionService/
/zitadel.settings.v2beta.SettingsService/
/zitadel.oidc.v2beta.OIDCService/
/zitadel.org.v2beta.OrganizationService/
/v2/
/zitadel.user.v2.UserService/
/zitadel.session.v2.SessionService/
/zitadel.settings.v2.SettingsService/
/zitadel.oidc.v2.OIDCService/
/zitadel.org.v2.OrganizationService/
```
## 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.
@@ -0,0 +1,173 @@
---
title: Migrate from v1 APIs to v2 APIs
---
This guide gives you an overview for migrating from our v1 API to the new and improved v2 API.
This upgrade introduces some significant architectural changes designed to make your development experience smoother, more intuitive, and efficient.
## The v1 Approach: Use-Case Based APIs
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
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
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
This section details the migrated resources, including any breaking changes and other important considerations for your transition from v1 to v2.
### General Changes
#### Sunsetting OpenAPI/REST Support in Favor of Connect RPC
While our v1 API already offered gRPC, it also provided a parallel REST/OpenAPI interface for clients 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 bi-directional streaming.
- Connect RPC: For making CRUD-like (Create, Read, Update, Delete) calls over HTTP. This is the recommended path for most clients 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 clients 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
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
No major changes have been made to the organization requests.
### Organizations
No major changes have been made to the organization requests.
### 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 human or machine users.
- You can use this single endpoint to provision both human users (individuals interacting with your application) and machine users (e.g., service accounts, 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
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
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 configuration object within the request body.
For example, to create a OIDC app, you will provide a 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
```
@@ -0,0 +1,24 @@
---
title: ZITADEL Ready and Health Endpoints
sidebar_label: 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
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.
<Callout>
In Kubernetes this is called the `readinessProbe`.
</Callout>
## 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.
<Callout>
In Kubernetes this is called the `livenessProbe`.
</Callout>
@@ -0,0 +1,15 @@
---
title: ZITADEL Metrics
sidebar_label: 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 [configuration](/self-hosting/manage/configure).
The (default) configuration is located in the [defaults.yaml](https://github.com/zitadel/zitadel/blob/main/cmd/defaults.yaml).
@@ -0,0 +1,11 @@
| Parameter | Description |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| grant_type | Must be `urn:ietf:params:oauth:grant-type:token-exchange` |
| subject_token | A token that represents the identity of the party on behalf of whom the request is being made. |
| subject_token_type | An identifier that indicates the type of the token in the subject_token parameter. |
| actor_token | Optional. A token that represents the identity of the acting party. In ZITADEL this the impersonator. |
| actor_token_type | An identifier that indicates the type of the token in the actor_token parameter. Required when actor_token is provided |
| requested_token_type | Optional. An identifier that indicates the type of the token requested. Defaults to access token if not provided. |
| scope | [Scopes](/apis/openidoauth/scopes) you would like to request from ZITADEL for the requested token. Scopes are space delimited, e.g. `openid email profile`. |
| audience | Optional. Must be a subset of the combined audiences from both subject and actor tokens. |
| resource | Currently not supported |
@@ -0,0 +1,9 @@
| Property | Description |
| ----------------- | ------------------------------------------------------------------------------------ |
| access_token | An `access_token` as opaque token or JWT for the subject user |
| token_type | Type of the `access_token`. Value can be `Bearer` or `N_A` |
| issued_token_type | [Token type](#token-types) of the returned token, matches the `requested_token_type` |
| refresh_token | A refresh token if the `offline_access` scope was requested |
| id_token | An ID Token of the subject user, only with `openid` scope |
| 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 |
@@ -0,0 +1,9 @@
The following table provides a matrix of supported token type parameter and responses for Token Exchange.
| Identifier | subject_token | actor_token | requested_token_type |
| ------------------------------------------------ | ------------------------------------------------------------ | ------------- | -------------------- |
| `urn:ietf:params:oauth:token-type:access_token` | JWT or Opaque | JWT or Opaque | Opaque only |
| `urn:ietf:params:oauth:token-type:refresh_token` | Not allowed | Not allowed | Not allowed |
| `urn:ietf:params:oauth:token-type:id_token` | Allowed | Allowed | Allowed |
| `urn:ietf:params:oauth:token-type:jwt` | JWT signed by client, only in combination with `actor_token` | Not allowed | Access Token as JWT |
| `urn:zitadel:params:oauth:token-type:user_id` | user ID as string, only in combination with `actor_token` | Not allowed | Not allowed |
@@ -0,0 +1,72 @@
---
title: Authentication Methods in ZITADEL
sidebar_label: Authentication Methods
---
## 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
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"
> }
> ```
@@ -0,0 +1,130 @@
---
title: ZITADEL OIDC Authentication Request Playground
sidebar_label: OIDC Playground
---
import { ExternalLink } from "lucide-react";
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.
<a href="https://zitadel.com/playgrounds/oidc" target="_blank" className="flex items-center gap-2 text-primary hover:underline">
Go to the OIDC Playground <ExternalLink className="h-4 w-4" />
</a>
## Why this OIDC playground?
Currently ZITADEL requires human users to authenticate trough 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
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
The <span className="text-yellow-500">Custom Domain</span> to your ZITADEL instance. Use the base-path, the playground will add the required path to the request.
### Required Parameters
<p>
<span className="text-green-500">Client ID</span> is the resource id of an
application. It's the application where you want your users to login. You can
find the resource id in the Console. When using organization grants, use the
client id from the origin organization.
</p>
<p>
<span className="text-blue-500">Redirect URI</span> 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.
</p>
<p>
<span className="text-orange-500">Response Type</span> defines whether a code,
id_token token or just id_token will be returned. Most use cases will need
code.
</p>
More in the <a href="/apis/openidoauth/endpoints#required-request-parameters">documentation</a> about required Parameters.
### Authentication methods
Depending on the authentication and authorization flow of your application you might need to append some information to the authentication request.
<span className="text-teal-600">Authentication method</span> "(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 <span className="text-teal-600">Code Verifier</span> 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
<div>
<span className="text-cyan-500">Prompt</span> defines if and how the user
should be prompted on login. For example:
<ul>
<li>
<code>select_account</code>: user is prompted to select one of the
existing sessions or create a new one
</li>
<li>
<code>create</code>: present the register form
</li>
<li>
<code>login</code>: requires the user to re-authenticate
</li>
<li>
<code>none</code>: user must be authenticated without interaction, an
error is returned otherwise; use for silent-refresh
</li>
</ul>
</div>
<p>
<span className="text-rose-500">Login hint</span> must be a valid logon name
of a user. You can skip the account picker by providing the Login hint.
</p>
There are many more additional parameters. Please refer to the [documentation](/apis/openidoauth/endpoints#additional-parameters) about additional parameters.
## 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
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
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 <span className="text-purple-500">Organization ID</span>.
Please refer to the full [guide on branding](/guides/manage/customize/branding).
### 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
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
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).
@@ -0,0 +1,118 @@
---
title: Claims in ZITADEL
sidebar_label: Claims
---
ZITADEL asserts claims on different places according to the corresponding specifications or project and clients 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
| 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) <br/> `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 | `[email protected]` | 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 | `[email protected]` | 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 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
```javascript reference
https://github.com/zitadel/actions/blob/de69b56f6d0463817953b59a52ffd6afc6a366fb/examples/add_claim.js#L9-L11
```
### Metadata as custom claim
```javascript reference
https://github.com/zitadel/actions/blob/main/examples/add_metadata.js#L9-L15
```
### Format roles claims
```javascript reference
https://github.com/zitadel/actions/blob/main/examples/custom_roles.js#L20-L33
```
## 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 primary domain of the organization 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 client 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's primary domain. |
@@ -0,0 +1,689 @@
---
title: OpenID Connect Endpoints in ZITADEL
sidebar_label: OpenID Connect Endpoints
---
import TokenExchangeRequest from "./_token_exchange_request.mdx";
import TokenExchangeResponse from "./_token_exchange_response.mdx";
import TokenExchangeTypes from "./_token_exchange_types.mdx";
## OpenID 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
`${CUSTOM_DOMAIN}/oauth/v2/authorize`
<Callout>
The authorization_endpoint is located with the login page, due to the need of accessing the same cookie domain
</Callout>
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).
<details>
<summary>Links to specs</summary>
<ul>
<li>
<a href="https://datatracker.ietf.org/doc/html/rfc6749#section-3.1">
Section 3.1 of OAuth2.0 (RFC6749)
</a>
</li>
<li>
<a href="https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint">
Section 3.1.2 of OpenID Connect Core 1.0 incorporating errata set 1
</a>
</li>
</ul>
</details>
### 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` |
<Callout>
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.
</Callout>
Depending on your authorization method you will have to provide additional parameters or headers:
<Tabs
groupId="token-auth-methods"
defaultValue="client_secret_basic"
values={[
{label: 'client_secret_basic', value: 'client_secret_basic'},
{label: 'client_secret_post', value: 'client_secret_post'},
{label: 'none (PKCE)', value: 'none'},
{label: 'private_key_jwt', value: 'private_key_jwt'},
]}
>
<Tab value="client_secret_basic">
no additional parameters required
</Tab>
<Tab value="client_secret_post">
no additional parameters required
</Tab>
<Tab value="none">
| 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
</Tab>
<Tab value="private_key_jwt">
no additional parameters required
</Tab>
</Tabs>
### 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. <br />no prompt: the user will have to choose a session if more than one session exists<br />`none`: user must be authenticated without interaction, an error is returned otherwise <br />`login`: user must reauthenticate / provide a user name <br />`select_account`: user is prompted to select one of the existing sessions or create a new one <br />`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
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.
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
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
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
Regardless of the authorization flow chosen, if an error occurs the following response will be returned to the redirect_uri.
<Callout>
If the redirect_uri is not provided, was not registered or anything other prevents the auth server form returning the response to the client,
the error will be display directly to the user on the auth server
</Callout>
| 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
| 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 configuration. |
| 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
`${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)
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
| 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:
<Tabs
groupId="token-auth-methods"
defaultValue="client_secret_basic"
values={[
{label: 'client_secret_basic', value: 'client_secret_basic'},
{label: 'client_secret_post', value: 'client_secret_post'},
{label: 'none (PKCE)', value: 'none'},
{label: 'private_key_jwt', value: 'private_key_jwt'},
]}
>
<Tab value="client_secret_basic">
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.
</Tab>
<Tab value="client_secret_post">
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 |
</Tab>
<Tab value="none">
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 |
</Tab>
<Tab value="private_key_jwt">
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` |
</Tab>
</Tabs>
#### 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
#### 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
| 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 user |
| 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
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
| 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:
<Tabs
groupId="token-auth-methods"
defaultValue="client_secret_basic"
values={[
{label: 'client_secret_basic', value: 'client_secret_basic'},
{label: 'client_secret_post', value: 'client_secret_post'},
{label: 'none (PKCE)', value: 'none'},
{label: 'private_key_jwt', value: 'private_key_jwt'},
]}
>
<Tab value="client_secret_basic">
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.
</Tab>
<Tab value="client_secret_post">
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 |
</Tab>
<Tab value="none">
Send your `client_id` as parameter in the body. No authentication is required.
</Tab>
<Tab value="private_key_jwt">
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` |
</Tab>
</Tabs>
#### 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
#### 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
| 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
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.
<Callout>
Token Exchange is currently an experimental [beta](https://help.zitadel.com/zitadel-software-release-cycle#beta) feature. Be sure to enable it on the [feature API](/guides/integrate/token-exchange#feature-api) before using it.
</Callout>
#### Request parameters
<TokenExchangeRequest components={props.components} />
Depending on your authorization method you will have to provide additional parameters or headers:
<Tabs
groupId="token-auth-methods"
defaultValue="client_secret_basic"
values={[
{label: 'client_secret_basic', value: 'client_secret_basic'},
{label: 'client_secret_post', value: 'client_secret_post'},
{label: 'none', value: 'none'},
{label: 'private_key_jwt', value: 'private_key_jwt'},
]}
>
<Tab value="client_secret_basic">
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.
</Tab>
<Tab value="client_secret_post">
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 |
</Tab>
<Tab value="none">
Send your `client_id` as parameter in the body. No authentication is required.
</Tab>
<Tab value="private_key_jwt">
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` |
</Tab>
</Tabs>
#### Successful token exchange response
<TokenExchangeResponse components={props.components} />
#### Token types
<TokenExchangeTypes components={props.components} />
### 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
`${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:
<Tabs
groupId="introspect-auth-methods"
defaultValue="client_secret_basic"
values={[
{label: 'client_secret_basic', value: 'client_secret_basic'},
{label: 'private_key_jwt', value: 'private_key_jwt'},
]}
>
<Tab value="client_secret_basic">
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...
```
</Tab>
<Tab value="private_key_jwt">
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...
```
</Tab>
</Tabs>
### 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
If the authorization fails, an HTTP 401 with `invalid_client` will be returned.
## 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
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
If the token is invalid or expired, an HTTP 401 will be returned.
## revocation_endpoint
`${CUSTOM_DOMAIN}/oauth/v2/revoke`
This endpoint enables clients to revoke an `access_token` or `refresh_token` they have been granted.
<Callout>
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.
</Callout>
| Parameter | Description |
| --------- | -------------------------------- |
| token | An access token or refresh token |
Depending on your authorization method you will have to provide additional parameters or headers:
<Tabs
groupId="token-auth-methods"
defaultValue="client_secret_basic"
values={[
{label: 'client_secret_basic', value: 'client_secret_basic'},
{label: 'client_secret_post', value: 'client_secret_post'},
{label: 'none (PKCE)', value: 'none'},
{label: 'private_key_jwt', value: 'private_key_jwt'},
]}
>
<Tab value="client_secret_basic">
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.
</Tab>
<Tab value="client_secret_post">
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 |
</Tab>
<Tab value="none">
Send your `client_id` as parameters in the body:
| Parameter | Description |
| --------- | ---------------------------- |
| client_id | client_id of the application |
</Tab>
<Tab value="private_key_jwt">
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...
```
</Tab>
</Tabs>
## 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
`${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
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.
<Callout title="Keys rotate without prior notice">
Be aware that these keys can be rotated without any prior notice.
</Callout>
### 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
**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).
@@ -0,0 +1,134 @@
---
title: Grant Types in ZITADEL
sidebar_label: Grant Types
---
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
**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
**Link to spec.** [Proof Key for Code Exchange by OAuth Public Clients](https://tools.ietf.org/html/rfc7636)
## 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
**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
**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
**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
Our service user work with the JWT profile to authenticate them against ZITADEL.
1. Create or use an existing service user
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 users 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 user, 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
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
**Link to spec.** [OAuth 2.0 Token Exchange](https://tools.ietf.org/html/rfc8693)
## 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
**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
### 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)
@@ -0,0 +1,34 @@
---
title: Scopes in ZITADEL
sidebar_label: Scopes
---
ZITADEL supports the usage of scopes as way of requesting information from the IAM and also instruct ZITADEL to do certain operations.
## 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
In addition to the standard compliant scopes we utilize 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#authorizations) 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 primary domain scope if a custom login policy is configured. Otherwise the system will not be able to identify the identity provider. |
+2
View File
@@ -0,0 +1,2 @@
*
!.gitignore
+69
View File
@@ -0,0 +1,69 @@
---
title: SAML Endpoints in ZITADEL
---
## 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
`${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
`${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
| 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
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
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 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).
+858
View File
@@ -0,0 +1,858 @@
---
title: SCIM v2.0 (Preview)
---
The Zitadel [SCIM v2](https://scim.cloud/) service provider interface enables seamless integration of identity and
access management (IAM) systems with Zitadel,
following the System for Cross-domain Identity Management (SCIM) v2.0 specification.
This interface allows standardized management of IAM resources, making it easier to automate user provisioning and
deprovisioning.
## 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 configuration 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`<br />`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
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 Users Guide](/guides/integrate/service-users/authenticate-service-users).
## 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
The following attributes are supported in the `SortBy` attribute.
- `meta.created`
- `meta.lastModified`
- `id`
- `username`
- `name.familyName`
- `name.givenName`
- `emails` and `emails.value`
### 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`<br />`emails.value` | `EQ`, `NE`, `CO`, `SW`, `EW` |
| `active` | `EQ`, `NE` |
Filters can have a maximum length of 1000 characters.
## 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.
<details>
<summary>`POST /Users`: Create a minimal user</summary>
```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": "[email protected]",
"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": "[email protected]",
"primary": true
}
]
}
```
</details>
<details>
<summary>`POST /Users`: Create a full user</summary>
```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": "[email protected]",
"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": "[email protected]",
"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": "[email protected]",
"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": "[email protected]",
"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"
}
]
}
```
</details>
<details>
<summary>`GET /Users/{id}`: Retrieve a known user</summary>
```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": "[email protected]",
"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": "[email protected]",
"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"
}
]
}
```
</details>
<details>
<summary>`GET /Users`: List users created after a given date sorted by the creation date</summary>
```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": "[email protected]",
"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": "[email protected]",
"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"
}
]
}
]
}
```
</details>
<details>
<summary>`PATCH /Users/{id}`: Set a user inactive</summary>
```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`
</details>
<details>
<summary>`PATCH /Users/{id}`: Set the password of a user</summary>
```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`
</details>
<details>
<summary>`PUT /Users/{id}`: Replace a full user</summary>
```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": "[email protected]",
"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": "[email protected]",
"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": "[email protected]",
"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": "[email protected]",
"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"
}
]
}
```
</details>
<details>
<summary>`DELETE /Users/{id}`: Delete a user</summary>
```bash
curl -X DELETE "https://${DOMAIN}/scim/v2/${ORG_ID}/Users/${USER_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
```
**Response**: `204 No Content`
</details>
<details>
<summary>`POST /Bulk`: Update the password of one user and delete another one</summary>
```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"
}
]
}
```
</details>
<details>
<summary>`GET /ServiceProviderConfig`: Get service provider configuration</summary>
```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-users/authenticate-service-users",
"type": "oauthbearertoken",
"primary": false
}
]
}
```
</details>
## 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
- **[Zitadel SCIM Documentation](/guides/manage/user/scim2)**: Documentation of Zitadel's SCIM implementation, including configuration details 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).
+19
View File
@@ -0,0 +1,19 @@
---
title: GRPC Status Codes in ZITADEL
sidebar_label: GRPC Status Codes
---
| 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. |
+13
View File
@@ -0,0 +1,13 @@
---
title: APIs V2 (Generally Available)
---
import DocCardList from '@/components/docusaurus/doc-card-list';
APIs V2 organize access by resources (users, settings, etc.), unlike context-specific V1 APIs.
This simplifies finding the right API, especially for multi-organization resources.
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.
<DocCardList />
@@ -0,0 +1,137 @@
---
title: How ZITADEL Processes and Stores Secrets
sidebar_label: Secrets
---
In this chapter you can find information of how ZITADEL processes and stores secrets and credentials in a secure fashion.
<Callout>
We use the terms secret and credentials interchangeable to keep this guide lean.
</Callout>
## 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
By default ZITADEL stores secrets from its users, clients 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.
<Callout>
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)
</Callout>
## Secrets stored in the Secrets Storage
### Public Keys
ZITADEL does handle many different public keys. These include:
- FIDO2
- U2F
- JWT Profile
- Signing Keys
<Callout>
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).
</Callout>
### Hashed Secrets
ZITADEL does handle many different passwords and secrets. These include:
- User Authentication
- Password
- Client / Machine Authentication
- Client Secrets
<Callout>
ZITADEL hashes all Passwords and Client Secrets in an non reversible way to further reduce the risk of a Secrets Storage breach.
</Callout>
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.
<Callout>
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 [configuration](/self-hosting/manage/configure) by the system administrator.
</Callout>
### 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
<Callout>
By default ZITADEL uses `RSA256` for signing purposes and `AES256` for encryption
</Callout>
## Secrets stored outside the Secrets Storage
### 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
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
The initial Admin User of ZITADEL can be configured through [ZITADELs config options](/self-hosting/manage/configure).
<Callout>
To prevent elevated breaches ZITADEL forces the Admin Users password to be changed during the first login.
</Callout>
@@ -0,0 +1,151 @@
---
title: Zitadel's Software Architecture
sidebar_label: 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
Zitadels 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.
![](../../../public/img/zitadel_software_architecture.png)
### Service Layer
The service layer includes all components who are potentially exposed to consumers of Zitadel.
#### 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
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
#### 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
The command handler receives all operations who alter a resource managed by Zitadel.
For example if a user changes his 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 of creating the necessary commands.
After creating the commands the command hand them down to the 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 really can change his 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
Zitadel handles events in two ways.
Events that should be processed in near real time are processed by a in memory pub sub system.
Some events can be handled asynchronously using the spooler.
##### 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
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 are responsible of 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
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
The query handler receives all read relevant operations. These can either be query 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 times, be revalidated against the Event Store.
This is achieved by triggering the projection to make sure that the last sequence of a 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
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
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.
<Callout>
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.
</Callout>
@@ -0,0 +1,73 @@
---
title: Zitadel's Deployment Architecture
sidebar_label: Deployment Architecture
---
## 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
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
### Single Cluster / Region
A Zitadel Cluster is a highly available IAM 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.
![](../../../public/img/zitadel_cluster_architecture.png)
### 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.
![](../../../public/img/zitadel_multicluster_architecture.png)
## 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.
<Callout>
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).
</Callout>
@@ -0,0 +1,94 @@
---
title: ZITADEL Database Structure
sidebar_label: 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
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 defines to which organization/resource_owner the event belongs. 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
| 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
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
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
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
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
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 |
@@ -0,0 +1,53 @@
---
title: ZITADEL Event Store
sidebar_label: Overview
---
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?
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
- 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
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
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
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 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.
@@ -0,0 +1,37 @@
---
title: 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
- 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
Account linking is controlled by your organization and identity provider configuration.
When external identity providers (such as social logins or enterprise SSO) are configured, a user account is created in ZITADEL, and the [external identity](../structure/users#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
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**.
@@ -0,0 +1,39 @@
---
title: ZITADEL Actions
sidebar_label: 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.
<Callout>
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.
</Callout>
## 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
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
- [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)
@@ -0,0 +1,64 @@
---
title: ZITADEL Actions v2
sidebar_label: 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.
<Callout>
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.
</Callout>
<Callout>
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.
</Callout>
## 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
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.
<Callout>
Currently, defined Actions v2 will be executed in addition to [Actions](/concepts/features/actions).
</Callout>
## Migration
- [Migrate Actions v1 to Actions v2](/guides/integrate/actions/migrate-from-v1)
## 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:
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)
@@ -0,0 +1,56 @@
---
title: ZITADEL's In-built Audit Trail
sidebar_label: Audit Trail
---
ZITADEL provides you with an built-in audit trail to track all changes and events over an unlimited period of time.
Most other solutions replace a historic 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 historic record.
The audit trail itself is identical to the state, since ZITADEL calculates the state from all the past changes.
![](../../../public/img/concepts/audit-trail/audit-log-events.png)
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.
<Callout title="Future Plans">
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
</Callout>
## Accessing the Audit Log
### 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 an [user](/guides/manage/console/users-overview).
The same view is available on several other objects such as organization or project.
![](../../../public/img/guides/console/myprofile.png)
### Event View
Administrators can see all events across an instance and filter them directly in [Console](/guides/manage/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
![](../../../public/img/concepts/audit-trail/event-viewer.png)
### 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 trough 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 User](/guides/integrate/service-users/authenticate-service-users) account, allowing you to integrate the events with your own business logic.
## 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).
@@ -0,0 +1,33 @@
---
title: "ZITADEL Management Console: Resource management and customization"
sidebar_label: Management Console
---
The ZITADEL management console is a web-based interface designed to facilitate the management and administration of ZITADEL resources and configurations.
It serves as a central hub where administrators can perform various tasks related to identity and access management within their organization's infrastructure.
The management console is available by navigating to the [custom domain](/concepts/features/custom-domain) of your instance and appending the path `/ui/console`.
Administrators can [restrict end-users from accessing the management console](/guides/solution-scenarios/restrict-console).
Here's an overview of what the management console enables users to do:
1. **Default Settings:** [Managers](/concepts/structure/managers) can access and manage the default settings of the ZITADEL system. This includes configuring authentication methods, security policies, and other system-wide parameters to meet the organization's requirements.
2. **Resource Management:** The management console allows for the creation, updating, and deletion of essential resources such as organizations, users, projects, and applications. Administrators can efficiently manage these entities to ensure proper access control and governance.
3. **User Management:** Administrators can manage user accounts, including creating new user accounts, updating user profiles, resetting passwords, and deactivating or deleting user accounts as needed.
4. **Access Control:** The management console provides tools for defining and managing access control policies. This includes assigning roles and permissions to users, configuring fine-grained access controls, and managing access to specific resources.
5. **Audit Logging:** The management console offers access to [audit logs](/concepts/features/audit-trail) that track user activity and changes made to resources and system settings. Administrators can review these logs to monitor security-related events, track changes, and maintain compliance with regulatory requirements.
6. **Customization and Branding:** The management console allows organizations to customize the branding and appearance of their ZITADEL instance. This includes uploading custom logos, selecting color schemes, and applying other visual customizations to align the interface with the organization's branding guidelines.
7. **Manager Assignment:** Administrators can assign ZITADEL [managers](/concepts/structure/managers) who have elevated privileges for managing resources within the organization. This allows for the delegation of administrative tasks while maintaining proper oversight and control.
Overall, the ZITADEL management console serves as a comprehensive tool for administrators to configure, manage, and monitor identity and access management within their organization, providing the necessary controls to ensure security, compliance, and efficient administration of resources.
Notes:
- Detailed guide on [using the management console](/guides/manage/console/console-overview)
- [Restrict access](/guides/solution-scenarios/restrict-console) to the management console
@@ -0,0 +1,13 @@
---
title: Custom Domain
sidebar_label: 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).
@@ -0,0 +1,40 @@
---
title: External user grant
---
ZITADEL's external user grant 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 grants, you can streamline collaboration with external users while maintaining control over access to your projects within ZITADEL.
![](../../../public/img/concepts/features/external-user-grant.png)
## Where to store users
### Consumer Identity Management (CIAM) / Business-to-Consumer (B2C)
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 grants on the B2C organization.
### Multitenancy / Business-to-Business (B2B)
ZITADEL allows you to create separate [organizations](../structure/organizations) for each of your business partner or tenant.
There might be cases were users from one organization need access to projects from another organization.
You can create an external user grant, that allows the inviting organization to manage the roles for the external user.
## Project Grants vs. User Grants
Project grants are used to delegate access management of an entire project (or specific roles of the project) to another organization.
User grants provide a more granular approach, allowing specific users from external organizations to access your projects.
## Alternative to multiple user accounts
A user account is always unique across a ZITADEL instance.
In some use cases, external user grants are a simple way to allow users access to multiple tenants.
## References
* [API reference for user grants](/reference/api/management)
* [How to manage user grants through ZITADEL's management console](/guides/manage/console/roles#authorizations)
* [More about multi-tenancy with ZITADEL](https://zitadel.com/blog/multi-tenancy-with-organizations)
@@ -0,0 +1,78 @@
---
title: Identity Brokering
sidebar_label: Identity Brokering
---
Link social logins and external identity providers with your identity management platform allowing users to login 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?
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?
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 login 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.
![](../../../public/img/concepts/features/identity-brokering.png)
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 configuration 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?
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 to login again.
Identity brokering describes an authentication scheme where users can login with external identity providers that have a 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
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 login 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.
[Managers](../structure/managers) can configure organization domains that are used for domain-based redirection to an external IdP.
![](../../../public/img/concepts/features/domain-discovery.png)
## 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 configuration 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
* [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)
@@ -0,0 +1,14 @@
---
title: Features
---
<Cards >
<Card href="/concepts/features/identity-brokering" title="Identity Brokering" />
<Card href="/concepts/features/account-linking" title="Account Linking" />
<Card href="/concepts/features/selfservice" title="Self Service" />
<Card href="/concepts/features/custom-domain" title="Custom Domain" />
<Card href="/concepts/features/audit-trail" title="Audit Trail" />
<Card href="/concepts/features/passkeys" title="Passkeys" />
<Card href="/concepts/features/actions" title="Actions" />
<Card href="/concepts/features/console" title="Console" />
</Cards>
@@ -0,0 +1,38 @@
---
title: "Passkeys in ZITADEL: Passwordless phishing-resistant authentication"
sidebar_label: Passkeys
---
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?
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
* **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
* **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
* **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
* 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!
@@ -0,0 +1,156 @@
---
title: Self Service in ZITADEL
sidebar_label: Self Service
---
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.
- `Managers` are users with a [special administrator role](../../guides/manage/console/managers) within ZITADEL and can perform administrative actions such as system configuration or granting access rights to users.
All self-service interfaces are available in different [languages](/guides/manage/customize/texts#internationalization-i-18-n).
<Callout>
ZITADEL covers the typical "CIAM" self-service capabilities as well as delegated access management for multi-tenancy scenarios. Please refer to the section [Managers](#managers).
</Callout>
## Registration
<Callout>
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.
</Callout>
### 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
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
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
### 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
Machine accounts 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 Users](/guides/integrate/service-users/authenticate-service-users) and recommended [OpenID Connect Flows](/guides/integrate/login/oidc/oauth-recommended-flows#different-client-profiles).
## Logout
Users can terminate the session for all their users (logout).
A client can also implement this by calling the [specific endpoint](/apis/openidoauth/endpoints#end-session-endpoint).
## 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
Users can change their passwords.
The current password must be entered first.
### MFA / FIDO Passkeys
Users can set up and delete a second factor and FIDO Passkeys (Passwordless).
Available authenticators are:
- Time-based one-time password (TOTP) (Which are Authenticator Apps like Google/Microsoft Authenticator, Authy, etc.)
- One-time password sent as E-Mail
- One-time password sent as SMS
- FIDO Universal Second Factor (U2F) (Security Keys, Device, etc.)
- FIDO2 WebAuthN (Passkeys)
### 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
Users can change their email address.
The user receives a one-time password and can verify control over the given email address.
### Phone Verification
Users can change their phone number.
The user receives a one-time password and can verify control over the given phone number.
### 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.
## Managers
It is important to note that a `Manager` 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:
- `Managers in isolation`: Granting administrative permissions within a single organization context.
- `Managers 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/managers#roles) is available with a description of permissions.
Managers can be assigned to both human users and service users, e.g., for managing certain tasks programmatically.
### Managers 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`).
### Managers 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](#managers-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 Managers of that organization to allow for self-service in a multi-tenancy scenario.
+20
View File
@@ -0,0 +1,20 @@
---
title: "Key Concepts"
description: "This part of our documentation contains ZITADEL specific or general concepts required to understand the system or our guides."
---
{/* THIS FILE IS AUTO-GENERATED FROM SIDEBAR-DATA.
ANY MANUAL CHANGES WILL BE OVERWRITTEN.
*/}
import { Card, Cards } from 'fumadocs-ui/components/card';
import { FileText, Folder, Link as LinkIcon } from 'lucide-react';
<Cards>
<Card title="Instance" href="/concepts/structure/instance" icon={<FileText />} />
<Card title="Organizations Overview" href="/guides/manage/console/organizations-overview" icon={<FileText />} />
<Card title="Projects Overview" href="/guides/manage/console/projects-overview" icon={<FileText />} />
<Card title="Applications Overview" href="/guides/manage/console/applications-overview" icon={<FileText />} />
<Card title="Users Overview" href="/guides/manage/console/users-overview" icon={<FileText />} />
<Card title="Managers" href="/concepts/structure/managers" icon={<FileText />} />
</Cards>
@@ -0,0 +1,7 @@
---
title: Knowledge
---
<Cards >
<Card href="/concepts/knowledge/opaque-tokens" title="Opaque Tokens" />
</Cards>
@@ -0,0 +1,70 @@
---
title: "Opaque Tokens in Zitadel: Enhancing Application Security"
sidebar_label: Opaque tokens
---
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?
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
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
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
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
- **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
- **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
- **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
- **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
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/)
+23
View File
@@ -0,0 +1,23 @@
---
title: 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 so solve complex problems outside of 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
@@ -0,0 +1,12 @@
Managers are [human users or service users](/guides/manage/console/users-overview) who have permission to manage resources within ZITADEL.
Manager permissions can be assigned to different levels in ZITADEL:
- **IAM Managers**: This is the highest level. Users with IAM Administrator roles are able to manage the whole [Instance](/concepts/structure/instance).
- **Org Managers**: Managers in the Organization Level are able to view or manage everything, according to their permissions, within the granted [Organization](/guides/manage/console/organizations-overview).
- **Project Mangers**: In this level the user is able to manage a [project](/guides/manage/console/projects-overview).
- **Project Grant Manager**: The project grant manager is for [granted projects](/concepts/structure/granted_projects) by another organization.
The scope of the managers is restricted based on their level.
Which means that a Manager, assigned to one organization, will have access only to the resources and configurations of that organization.
Only the Managers on the instance level can view resources, such as users, across all organizations.
@@ -0,0 +1,17 @@
ZITADEL is organized around the idea that:
* Multiple organizations can be managed within one [instance](/concepts/structure/instance).
* organizations can grant each other rights to self-manage certain aspects of the IAM (eg, roles for access management)
* organizations are vessels for users and projects
![](../../../public/img/concepts/objects/organization.png)
Organizations in ZITADEL are therefore comparable to tenants of a system or organizational units of a directory based system.
You can use projects within your organization to manage the security context of closely related components, such as roles, grants and authorizations for multiple clients. You can set up multiple projects within your organization.
ZITADEL allows you to give other organizations permission to manage certain aspects of a project within your organization on their own. This means you could set up a project with roles that should exist within your service/software, but allow another organization to allocate the roles to users within their own organization. As a service provider, you will find this feature useful, as it allows you to establish a self-service culture for your business customers.
![](../../../public/img/concepts/objects/organization_grants.png)
Each organization has its own pool of usernames, which includes human and service users, for its domain (`{username}@{OrganizationDomain}.$CUSTOM_DOMAIN`). A username is unique within your organization. You can configure ZITADEL to use your own domain, and simplify user experience (`{loginname}@{yourdomain.tld}`).
@@ -0,0 +1,10 @@
The idea of projects is to have a vessel for all components who are closely related to each other. Multiple projects can exist within an organization.
![](../../../public/img/concepts/objects/organization_grants.png)
All applications within a project share the same roles, grants, and authorizations:
- **Applications** is your software that initiates the authorization flow. This could be a web app and a mobile app that share the same roles.
- **Roles** are a means of managing user access rights for a project.
- **Authorizations** define which users have which roles. Authorizations are also called “user grants”.
- **Granted Organizations** can manage selected roles for your project on their own.
@@ -0,0 +1,29 @@
---
title: Configure applications for your frontend and backend services and clients
sidebar_label: Applications
---
import AppType from "../../guides/manage/console/_application-types.mdx";
Applications are the entry point to your project.
[Users](./users) either login into one of your clients and interact with them directly or use one of your APIs.
All applications share the roles and authorizations of their [project](./projects).
## Supported application types
ZITADEL supports the following client types:
<AppType components={props.components} />
## Security considerations
Ensure the configuration of application settings is limited to authorized users only.
You can configure applications in the [management console](/guides/manage/console/organizations-overview).
Read about our [managers](/concepts/structure/managers) and [granted projects](/concepts/structure/granted_projects)
concepts.
## References
- [Configure Applications in the Management Console](../../guides/manage/console/applications-overview)
- [ZITADEL API: Applications](/reference/api/application)
@@ -0,0 +1,14 @@
---
title: ZITADEL's Granted Projects
sidebar_label: Granted Projects
---
# Granted Project
![](../../../public/img/concepts/objects/organization_grants.png)
With ZITADEL you can grant selected roles within your project to an organization. The receiving organization can then create authorizations 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 setup Project grants, read this guide [here](../../guides/manage/console/projects-overview#granted-projects-b2b)
@@ -0,0 +1,13 @@
---
title: Structure
---
<Cards >
<Card href="/concepts/structure/instance" title="Instance" />
<Card href="/concepts/structure/organizations" title="Organizations" />
<Card href="/concepts/structure/projects" title="Projects" />
<Card href="/concepts/structure/applications" title="Applications" />
<Card href="/concepts/structure/users" title="Users" />
<Card href="/concepts/structure/managers" title="Managers" />
<Card href="/concepts/structure/policies" title="Policies" />
</Cards>
@@ -0,0 +1,32 @@
---
title: Instances
sidebar_label: Instances
---
## 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).
![](../../../public/img/concepts/objects/object_overview.png)
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
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.
![](../../../public/img/concepts/objects/instances.png)
@@ -0,0 +1,13 @@
---
title: Managers
sidebar_label: Managers
---
import ManagerDescription from "./_manager_description.mdx";
<ManagerDescription components={props.components} name="ManagerDescription" />
Notes:
- Read our [guide on Managers](../../guides/manage/console/managers) 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 Managers on the organization level
@@ -0,0 +1,10 @@
---
title: ZITADEL Organizations
sidebar_label: Organizations
---
import OrgDescription from './_org_description.mdx';
<OrgDescription components={props.components} name="OrgDescription" />
More about how to configure your organization read our [organization guide](../../guides/manage/console/organizations-overview).
@@ -0,0 +1,9 @@
---
title: ZITADEL Settings and Policies
sidebar_label: Setting and Policies
---
Settings and policies are configurations of all the different parts of the instance or an organization. For all parts we have a suitable default in the instance.
The default configuration 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).
@@ -0,0 +1,47 @@
---
title: Projects
sidebar_label: Projects
---
import ProjectDescription from './\_project_description.mdx';
<ProjectDescription components={props.components} name="ProjectDescription" />
To learn how to set up a project read this management console guide [here](../../guides/manage/console/projects-overview.mdx).
## Applications
Applications define the different clients, that share the same roles.
At the moment we support OIDC and almost every OAuth2 client. We'll be expanding this with SAML shortly.
Go to [Applications](./applications) for more details.
## Granted Organizations
To enable another organization to use a project, the organization needs a grant to the project.
Only the selected roles will be available to the granted organization.
If you are unsure on how to structure your organizations and projects, we recommend reading the guide on [solution architecture](/concepts/architecture/solution).
The granted organization will be able to manage the authorizations of their users for the granted project by themselves in their organization.
More about granted projects: [Granted Projects](./granted_projects)
## Roles
A role consists of different attributes. Only the key is relevant to the authorization and must therefore be unique.
The display name is only to provide a human-readable name if needed.
And the group should enable a better handling in ZITADEL management console, like give a user all the roles of a specific group. (Not implemented yet)
All applications in a project share the roles. Read more about roles [here](../../guides/manage/console/roles)
## Default Project
When creating a new ZITADEL instance you will find an automatically created project on the first created organization.
This default project does represent the ZITADEL project and is used to secure the different apps shipped with ZITADEL.
This includes the ZITADEL Management Console and APIs.
We do not recommend changing any settings or using this project for anything else, as this could influence the behavior of ZITADEL.
The default name of the project is "ZITADEL", this might differ on self-hosted instances, when you change the default name.
![](../../../public/img/guides/solution-scenarios/console-default-project.png)
@@ -0,0 +1,81 @@
---
title: ZITADEL Users
sidebar_label: Users
---
## Types of users
ZITADEL supports authentication and authorization for different user types.
We can mainly differentiate between Human Users and Machine Users.
We typically call human users simply "Users" and machine users "Service Users".
### Human users
Human users typically logon with an interactive login.
This means that an application redirects a user to a website ("login page") where the user can provide the credentials.
ZITADEL handles the authentication and provides the application with a token that verifies the authentication process.
Read more on how to [login users with ZITADEL](/guides/integrate/login/login-users).
### Service users
Service users are for machine-to-machine communication and you would use those typically to access secure backend services.
For example in ZITADEL you would require an authenticated Service User to access the Management API.
The main difference between human and machine users is the type of credentials that can be used for authentication: Human users typically logon via an login prompt, but Machine users require a non-interactive logon process.
Learn how to [use service users](/guides/integrate/service-users/authenticate-service-users) with ZITADEL.
### Managers
Any user, human or service user, can be given a [Manager](/concepts/structure/managers) role.
A user with an administrator role is not only an end-user of ZITADEL but can also manage certain aspects of ZITADEL itself.
### Federated users
Federated users are identities that are managed by a third-party identity provider.
Users can login via an external identity provider, using [identity brokering](../features/identity-brokering) ("Single-sign-on").
Federated user [accounts are linked](../features/account-linking) to internal users to be able to assign roles and keep an audit trail.
### External users
In a multi-tenancy architecture, you might use [organizations](./organizations) to separate user groups.
By using [external user grants](../features/external-user-grant) an organization is able to invite users from another organization.
These invited users are called external users.
## Considerations
### Uniqueness of users
Users can only exist within one [organization](/guides/manage/console/organizations-overview).
It is currently not possible to move users between organizations.
User accounts are uniquely identified by their `id` or `loginname` in combination of the `organization domain` (eg, `[email protected]`).
You can use the same email address for different user accounts.
### How to structure user pools
Consider this general recommendation as a starting point:
- Create one organization ("default organization") for your own company
- Configure projects and applications in the default organization
- Structure users in organizations based on common domains that are self-managed (eg, company)
- Grant your projects to the organizations, allow Managers to give granted roles to their users
You might want to adjust this general setup based on your [scenario](/guides/solution-scenarios).
One important consideration in the setup is that you can only have a domain once for an organization. If you have multiple teams working with the same email address, you might need to add them to one single organization that has the domain verified for the teams' domain.
For a CIAM / B2C setup, you might want to store all users in one organization and allow that organization to use a specific set of social logins.
In a multitenancy / B2B scenario, you might have thousands of smaller teams.
#### Hierarchy
There is no concept of hierarchies and inheritance based on users or organizations.
This is why we recommend to structure users along the smallest unit of groups.
You can use organization metadata or your own business logic to describe a hierarchy of organizations or user groups.
## References
- [Manage users in the Management Console](../../guides/manage/console/users-overview)
- [ZITADEL APIs: Users](/reference/api/user)
- [User onboarding and registration](/guides/integrate/onboarding)
@@ -0,0 +1,75 @@
---
title: 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
### Setup Application and get Keys
Before we can start building our application we have do do a few configuration steps in ZITADEL Console.
You will need to provide some information about your app. We recommend creating a new app to start from scratch. 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
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
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
### 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
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 configuration to the config:
```toml
oidc_groups_claim = "{your_actions_group_key}"
allowed_groups = ["list", "of", "allowed", "roles"]
```
## Completion
You have successfully integrated ZITADEL in your proxy!
### What next?
@@ -0,0 +1,53 @@
1. Go to your Project and click on the **New** button as shown below.
![](../../../public/img/examples/secure-api/app-jwt/1.png)
2. Give a name to your application (Test API is the name given below) and select type **API**.
![](../../../public/img/examples/secure-api/app-jwt/2.png)
3. Select **JWT** as the authentication method and click **Continue**.
![](../../../public/img/examples/secure-api/app-jwt/3.png)
4. Now review your configuration and click **Create**.
![](../../../public/img/examples/secure-api/app-jwt/4.png)
5. You will now see the API’s **Client ID**. You will not see a Client Secret because we are using a private JWT key.
![](../../../public/img/examples/secure-api/app-jwt/5.png)
6. Next, we must create the key pairs. Click on **New**.
![](../../../public/img/examples/secure-api/app-jwt/6.png)
7. Select **JSON** as the type of key. You can also set an expiration time for the key or leave it empty. Click on **Add**.
![](../../../public/img/examples/secure-api/app-jwt/7.png)
8. Download the created key by clicking the **Download** button and then click **Close**.
![](../../../public/img/examples/secure-api/app-jwt/8.png)
9. The key will be downloaded.
![](../../../public/img/examples/secure-api/app-jwt/9.png)
10. When you click on URLs on the left, you will see the relevant OIDC URLs. Note down the **issuer** URL, **token_endpoint** and **introspection_endpoint**.
![](../../../public/img/examples/secure-api/app-jwt/10.png)
11. The key that you downloaded will be of the following format.
```
{
"type":"application",
"keyId":"<YOUR_KEY_ID>",
"key":"-----BEGIN RSA PRIVATE KEY-----\<YOUR_PRIVATE_KEY>\n-----END RSA PRIVATE KEY-----\n",
"appId":"<YOUR_APP_ID>",
"clientId":"<YOUR_CLIENT_ID>"
}
```
12. Also note down the **Resource ID** of your project.
![](../../../public/img/examples/secure-api/app-jwt/11.png)
@@ -0,0 +1,42 @@
1. Go to the **Users** tab in your organization as shown below and click on the **Service Users** tab.
![](../../../public/img/examples/secure-api/service-user-jwt/1.png)
2. To add a service user, click on the **New** button.
![](../../../public/img/examples/secure-api/service-user-jwt/2.png)
3. Next, add the details of the service user and select either **Bearer** or **JWT** for **Access Token Type** and click on **Create**. For this example, we will select **JWT**.
![](../../../public/img/examples/secure-api/service-user-jwt/3.png)
4. Now you will see the saved details.
![](../../../public/img/examples/secure-api/service-user-jwt/4.png)
5. Next, we need to generate a private-public key pair in ZITADEL and you must get the private key to sign your JWT. Go to **Keys** and click on **New**.
![](../../../public/img/examples/secure-api/service-user-jwt/5.png)
6. Select type **JSON** and click **Add**.
![](../../../public/img/examples/secure-api/service-user-jwt/6.png)
7. Download the key by clicking **Download**. After the download, click **Close**.
![](../../../public/img/examples/secure-api/service-user-jwt/7.png)
8. You will see the following screen afterwards.
![](../../../public/img/examples/secure-api/service-user-jwt/8.png)
9. The downloaded key will be of the following format:
```
{
"type":"serviceaccount",
"keyId":"<YOUR_KEY_ID>",
"key":"-----BEGIN RSA PRIVATE KEY-----\n<YOUR_KEY>\n-----END RSA PRIVATE KEY-----\n",
"userId":"<YOUR_USER_ID>"
}
```
@@ -0,0 +1,36 @@
In order to access this route, you must create the role `read:messages` in your ZITADEL project and also create an authorization for the service user you created by adding the role to the user. Follow these steps to do so:
1. Go to your project and select **Roles**. Click **New**.
![](../../../public/img/examples/secure-api/service-user-jwt/scopes/1.png)
2. Add the `read:messages` role as shown below and click **Save**.
![](../../../public/img/examples/secure-api/service-user-jwt/scopes/2.png)
3. You will see the created role listed.
![](../../../public/img/examples/secure-api/service-user-jwt/scopes/3.png)
4. To assign this role to a user, click on **Authorizations**.
![](../../../public/img/examples/secure-api/service-user-jwt/scopes/4.png)
5. Select the user you want to assign the role to.
![](../../../public/img/examples/secure-api/service-user-jwt/scopes/5.png)
6. Select the project where this authorization is applicable.
![](../../../public/img/examples/secure-api/service-user-jwt/scopes/6.png)
7. Click **Continue**.
![](../../../public/img/examples/secure-api/service-user-jwt/scopes/7.png)
8. Select the role **read:messages** and click **Save**.
![](../../../public/img/examples/secure-api/service-user-jwt/scopes/8.png)
9. You will now see the your service user has been assigned the role **read:messages**.
![](../../../public/img/examples/secure-api/service-user-jwt/scopes/9.png)
@@ -0,0 +1,9 @@
To install Django:
```bash
python -m pip install Django
```
Then in your folder of choice call the following command to create a Django base:
```bash
django-admin startproject mysite .
```
@@ -0,0 +1,5 @@
As we use local environmental variables please install dotenv:
```bash
python -m pip install python-dotenv
```
@@ -0,0 +1 @@
You have to install Python as described in [their documentation](https://wiki.python.org/moin/BeginnersGuide/Download).

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