mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-04 21:32:15 +00:00
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:
co-authored by
Federico Coppede
parent
7b6395c348
commit
d8ea041c56
@@ -0,0 +1,85 @@
|
||||
---
|
||||
title: Migrate to ZITADEL
|
||||
sidebar_label: Introduction
|
||||
---
|
||||
|
||||
This section of guides shows you how to migrate from your current auth system to ZITADEL.
|
||||
The individual guides in this section should give you an overview of things to consider before you start the migration.
|
||||
|
||||
When moving from a previous auth solution to ZITADEL, it is important to note that some decisions and features are unique to ZITADEL.
|
||||
Without duplicating too much content here are some important features and patterns to consider in terms of solution architecture.
|
||||
You can read more about the basic structure and important concepts of ZITADEL in our [concepts section](/concepts).
|
||||
|
||||
## Multi-tenancy architecture
|
||||
|
||||
Multi-tenancy in ZITADEL can be achieved through either [Instances](/concepts/structure/instance) or [Organizations](/concepts/structure/instance) or [Organizations](/guides/manage/console/organizations-overview).
|
||||
Where instances represent isolated ZITADEL instances, Organizations provide a more permeable approach to multi-tenancy.
|
||||
|
||||
In most cases, when you want to achieve multi-tenancy, you use Organizations. Each organization can have their own set of Settings (eg, Security Policies, IDPs, Branding), Managers, and Users.
|
||||
Please also consult our guide on [Solution Scenarios](/guides/solution-scenarios) for B2C and B2B for more details.
|
||||
|
||||
## Delegated access management
|
||||
|
||||
Some solutions, that offer multi-tenancy, require you to copy applications and settings to each tenant and manage changes individually.
|
||||
ZITADEL works differently by using [Granted Projects](/concepts/structure/granted_projects).
|
||||
|
||||
Projects can be granted to [Organization](/guides/manage/console/organizations-overview#organization-grants) or even to individual users.
|
||||
You can think of it as a logical link to a Project, which can be used by the receiving Organization or User as if it was their own project, except privileges to modify the Project itself.
|
||||
|
||||
Delegated access management is a great way of keeping the management overhead low and enabling [self-service](/concepts/features/selfservice#managers-in-delegation) for Organizations to manage their own Settings and Authorizations.
|
||||
|
||||
## Actions
|
||||
|
||||
ZITADEL [Actions](/guides/manage/console/actions-overview) is the key feature to customize and create workflows and change the default behavior of the platform.
|
||||
|
||||
You define custom code that should be run on a specific Trigger.
|
||||
A trigger could be the creation of a new user, getting profile information about a user, or a login attempt.
|
||||
|
||||
With the [HTTP module](/apis/actions/modules) you can even make calls to third party systems for example to receive additional user information from a backend system, or triggering a workflow in other systems (Webhook).
|
||||
|
||||
You can also create custom claims or manipulate tokens and information on the userinfo endpoint by using the [Complement Token Flow](/apis/actions/complement-token).
|
||||
This might be required, if an application expects roles/permissions in a certain format or additional attributes (eg, a backend user-id) as claims.
|
||||
|
||||
## Metadata
|
||||
|
||||
You can store arbitrary key-value pairs of data on objects such as Users or Organizations.
|
||||
Metadata could link a user to a specific backend user-id or represent an "organizational unit" for your business logic.
|
||||
Metadata can be access directly with the correct [scopes](/apis/openidoauth/scopes#reserved-scopes) or transformed to custom claims (see above).
|
||||
|
||||
## Migrating resources
|
||||
|
||||
### Migrating users
|
||||
|
||||
Migrating users with minimal impact on users can be a challenging task.
|
||||
We provide some more information on migrating users and secrets in [this guide](./users).
|
||||
|
||||
### Migrating clients / applications
|
||||
|
||||
After you have set up or imported your applications to ZITADEL, you need to update your client's configurations, such as issuer, clientID or credentials.
|
||||
It is not possible to create an application with a pre-defined clientID or import existing credentials.
|
||||
|
||||
## Technical considerations
|
||||
|
||||
### Batch migration
|
||||
|
||||
**Batch migration** is the easiest way, if you can afford some minimal downtime to move all users and applications over to ZITADEL.
|
||||
See the [User guide](./users) for batch migration of users.
|
||||
|
||||
### Just-in-time migration
|
||||
|
||||
In case all your applications depend on ZITADEL after the migration date, and ZITADEL is able to retrieve the required user information, including secrets, from the legacy system, then the recommended way is to let **ZITADEL orchestrate the user migration just-in-time**:
|
||||
|
||||
- Create a pre-authentication [Action](/guides/manage/console/actions-overview) to request user data from the legacy system and create a new user in ZITADEL.
|
||||
- Optionally, create a post-authentication Action to flag successfully migrated users in your legacy system
|
||||
|
||||
For all other cases, we recommend that the **legacy system orchestrates the migration** of users to ZITADEL for more flexibility:
|
||||
|
||||
- Update your legacy system to create a user in ZITADEL on their next login, if not already flagged as migrated, by using our APIs (you can set the password and a verified email)
|
||||
- Redirect migrated users with a login hint in the [auth request](https://zitadel.com/playgrounds/oidc) to ZITADEL to pre-select the user
|
||||
|
||||
In this case the migration can also be done as an import job or also allowing to create user session in both the legacy auth solution and ZITADEL in parallel with identity brokering:
|
||||
|
||||
- Setup ZITADEL to use your legacy system as external identity provider (note: you can also use JWT-IDP, if you only have a token).
|
||||
- Configure your app to use ZITADEL, which will redirect users automatically to the external identity provider to login.
|
||||
- A session will be created both on the legacy system and ZITADEL
|
||||
- If a user does not exist already in ZITADEL you can auto-register new users and use an Action to pull additional information (eg, Secrets) from your legacy system. Note: ZITADEL links external identity information to users, meaning you can have users use both a password and external identity providers to login with the same user.
|
||||
@@ -0,0 +1,218 @@
|
||||
---
|
||||
title: Migrate from Auth0 to ZITADEL (Including Password Hashes)
|
||||
sidebar_label: From Auth0
|
||||
---
|
||||
|
||||
## 1. Introduction
|
||||
|
||||
This guide will walk you through the steps to migrate users from Auth0 to ZITADEL, including password hashes (which requires Auth0's support assistance), so users don't need to reset their passwords.
|
||||
|
||||
**What you'll learn with this guide**
|
||||
- How to prepare your data from Auth0
|
||||
- Use of the ZITADEL migration tooling
|
||||
- Performing the user import via ZITADEL's API
|
||||
- Troubleshooting and validating the migration
|
||||
|
||||
---
|
||||
|
||||
## 2. Prerequisites
|
||||
|
||||
### 2.1. Install Go
|
||||
The migration tool is written in Go. Download and install the latest version of Go from the [official Go website](https://go.dev/doc/install).
|
||||
|
||||
### 2.2. Create a ZITADEL Instance and Organization
|
||||
You'll need a target organization in ZITADEL to import your users. You can create a new organization or use an existing one.
|
||||
|
||||
If you don't have a ZITADEL instance, you can [sign up for free here](https://zitadel.com) to create a new one for you.
|
||||
See: [Managing Organizations in ZITADEL](https://zitadel.com/docs/guides/manage/console/organizations-overview).
|
||||
|
||||
> **Note:** Copy your Organization ID (Resource ID) since you will use the id in the later steps.
|
||||
|
||||
---
|
||||
|
||||
## 3. Preparing Auth0 Data
|
||||
|
||||
### 3.1. Export hashed passwords
|
||||
Auth0 does not export hashed passwords as part of the bulk user export.
|
||||
You must create a support ticket to download password hashes and password-related information.
|
||||
Please also refer to the Auth0 guide on how to [Export Data](https://auth0.com/docs/troubleshoot/customer-support/manage-subscriptions/export-data#user-passwords).
|
||||
|
||||
<Callout>
|
||||
You can also import users into ZITADEL with an verified email but without the passwords.
|
||||
Users will be prompted to create a new password after they login for the first time after migration.
|
||||
</Callout>
|
||||
|
||||
1. Go to https://support.auth0.com/tickets and click on **Open Ticket**
|
||||
2. Issue Type: **I have a question regarding my Auth0 account**
|
||||
3. What can we help you with?: **I would like to obtain an export of my tenant password hashes**
|
||||
4. Fill out the form: Request password hashes as bcrypt
|
||||
5. **Submit ticket**
|
||||
|
||||
You will receive a JSON file including the password hashes.
|
||||
See this [community post](https://community.auth0.com/t/password-hashes-export-data-format/58730) for more information about the contents and format.
|
||||
|
||||
Reference: [Export hashed passwords from Auth0](https://zitadel.com/docs/guides/migrate/sources/auth0#export-hashed-passwords)
|
||||
|
||||
### 3.2. Export all user data
|
||||
Create a [bulk user export](https://auth0.com/docs/manage-users/user-migration/bulk-user-exports) from the Auth0 Management API, or use the [User Import/Export extension](https://auth0.com/docs/manage-users/user-migration/user-import-export-extension).
|
||||
You will receive a newline-delimited JSON with the requested user data.
|
||||
|
||||
This is an example request, we have included the user id, the email and the name of the user, but you can also export all the available user profile attributes. Make sure to export the users in a json format.
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url $AUTH0_DOMAIN/api/v2/jobs/users-exports \
|
||||
--header 'authorization: Bearer $TOKEN' \
|
||||
--header 'content-type: application/json' \
|
||||
--data '{
|
||||
"connection_id": "$CONNECTION_ID",
|
||||
"format": "json",
|
||||
"fields": [
|
||||
{"name": "user_id"},
|
||||
{"name": "email"},
|
||||
{"name": "name"},
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Running the ZITADEL Migration Tool
|
||||
We have developed a tool that combines your exported user data with their corresponding passwords to generate the import request body for ZITADEL.
|
||||
|
||||
### 4.1. Install the Migration Tool
|
||||
Follow the installation instructions to set up the ZITADEL migration tool from [ZITADEL Tools](https://github.com/zitadel/zitadel-tools?tab=readme-ov-file#installation).
|
||||
|
||||
### 4.2. Generate Import JSON
|
||||
Use the migration tool to convert the Auth0 export file to a ZITADEL-compatible JSON.
|
||||
Step-by-step instructions can be found here: [Migration Tool for Auth0](https://github.com/zitadel/zitadel-tools/blob/main/cmd/migration/auth0/readme.md)
|
||||
|
||||
Typical steps:
|
||||
- Run the migration tool with your exported Auth0 files as input.
|
||||
- The tool generates a JSON file ready for import into ZITADEL.
|
||||
|
||||
Example:
|
||||
After obtaining the 2 required input files (passwords and profile) in JSON lines format, you can run the following command:
|
||||
|
||||
Sample `passwords.ndjson` content, as obtained from the Auth0 Support team:
|
||||
```json
|
||||
{"_id":{"$oid":"emxdpVxozXeFb1HeEn5ThAK8"},"email_verified":true,"email":"[email protected]","passwordHash":"$2b$10$d.GvZhGwTllA7OdAmsA75uGGzqr/mhdQoU88M3zD.fX3Vb8Rcf33.","password_set_date":{"$date":"2025-06-30T00:00:00.000Z"},"tenant":"test","connection":"Username-Password-Authentication","_tmp_is_unique":true}
|
||||
```
|
||||
|
||||
Sample `profiles.json` content, as obtained from the Auth0 Management API:
|
||||
```json
|
||||
{"user_id":"auth0|emxdpVxozXeFb1HeEn5ThAK8","email_verified":true,"name":"Tommie Krajcik","email":"[email protected]"}
|
||||
```
|
||||
|
||||
Run the following command in your terminal (replace ORG_ID with your own organization ID):
|
||||
```bash
|
||||
zitadel-tools migrate auth0 --org=<ORG_ID> --users=./profiles.json --passwords=./passwords.ndjson --multiline --email-verified --output=./importBody.json --timeout=5m0s
|
||||
```
|
||||
|
||||
The tool will merge both objects into a single one in the importBody.json output, this will be used in the next step to complete the import process.
|
||||
|
||||
---
|
||||
|
||||
## 5. Importing Users into ZITADEL
|
||||
|
||||
### 5.1. Obtain Access Token (or PAT) for API Access
|
||||
|
||||
To call the ZITADEL Admin API, you need to authenticate using a **Service User** with the `IAM_OWNER` Manager permissions.
|
||||
|
||||
There are two recommended authentication methods:
|
||||
|
||||
- **Client Credentials Flow**
|
||||
[Learn how to authenticate with client credentials.](https://zitadel.com/docs/guides/integrate/service-users/client-credentials)
|
||||
|
||||
- **Personal Access Token (PAT)**
|
||||
[Learn how to create and use a PAT.](https://zitadel.com/docs/guides/integrate/service-users/authenticate-service-users#personal-access-token)
|
||||
|
||||
**Reference:** [Service Users & API Authentication](https://zitadel.com/docs/guides/integrate/service-users/authenticate-service-users#authentication-methods)
|
||||
|
||||
### 5.2. Import Data with the ZITADEL API
|
||||
|
||||
- Use your **access token** or **PAT** to authorize the request.
|
||||
- Call the [Admin API – Import Data](/reference/api/admin/zitadel.admin.v1.AdminService.ImportData) endpoint, passing your generated JSON file.
|
||||
- Verify that the users were imported successfully in the ZITADEL console.
|
||||
|
||||
**Import Endpoint:**
|
||||
|
||||
- `POST /admin/v1/import`
|
||||
- `Authorization: Bearer <token>`
|
||||
- **Body:** Generated in step 4.2
|
||||
|
||||
#### Example cURL request
|
||||
|
||||
```bash
|
||||
curl --location 'https://${CUSTOM_DOMAIN}/admin/v1/import' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'Accept: application/json' \
|
||||
--header 'Authorization: Bearer <access-token>' \
|
||||
--data-raw '{
|
||||
"dataOrgs": {
|
||||
"orgs": [
|
||||
{
|
||||
"orgId": "<your-org-id>",
|
||||
"humanUsers": [
|
||||
{
|
||||
"userId": "auth0|emxdpVxozXeFb1HeEn5ThAK8",
|
||||
"user": {
|
||||
"userName": "[email protected]",
|
||||
"profile": {
|
||||
"firstName": "Tommie Krajcik",
|
||||
"lastName": "Tommie Krajcik"
|
||||
},
|
||||
"email": {
|
||||
"email": "[email protected]",
|
||||
"isEmailVerified": true
|
||||
},
|
||||
"hashedPassword": {
|
||||
"value": "$2b$10$d.GvZhGwTllA7OdAmsA75uGGzqr/mhdQoU88M3zD.fX3Vb8Rcf33."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"timeout": "5m0s"
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Testing the Migration
|
||||
|
||||
### 6.1. Test User Login
|
||||
|
||||
Use the **ZITADEL login page** or your integrated app to test logging in with one of the imported users.
|
||||
|
||||
> **Password for the sample user:** `Password1!`
|
||||
|
||||
Confirm that the migrated password works as expected.
|
||||
|
||||
### 6.2. Troubleshooting
|
||||
|
||||
**Common issues:**
|
||||
|
||||
- Missing password hashes
|
||||
- Malformed JSON
|
||||
- Invalid or incomplete user data
|
||||
|
||||
The import endpoint returns an `errors` array which can help you identify any issues with the import.
|
||||
|
||||
#### Where to check logs and get help
|
||||
|
||||
You can also verify that a user was imported by calling the **events endpoint** and checking for the following event type:
|
||||
|
||||
```json
|
||||
"user.human.added"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Q&A and Further Resources
|
||||
|
||||
**Q:** What is the maximum number of users that can be imported in a single batch?
|
||||
**A:** There is no hard limit on the number of users. However, there is a **timeout**.
|
||||
For **ZITADEL Cloud deployments**, the timeout is **5 minutes**, which typically allows for importing around **5,000 users per batch**.
|
||||
@@ -0,0 +1,253 @@
|
||||
---
|
||||
|
||||
title: Migrate from Keycloak
|
||||
sidebar_label: From Keycloak
|
||||
---
|
||||
|
||||
## 1. Introduction
|
||||
|
||||
This guide will walk you through the steps to migrate users from **Keycloak** to **ZITADEL**, including password hashes, using the `zitadel-tools` CLI and the user import APIs.
|
||||
|
||||
**What you'll learn with this guide**
|
||||
|
||||
* How to export users from Keycloak
|
||||
* Use of the ZITADEL migration tooling
|
||||
* Performing the user import via ZITADEL's API
|
||||
* Troubleshooting and validating the migration
|
||||
|
||||
---
|
||||
|
||||
## 2. Prerequisites
|
||||
|
||||
### 2.1. Install Go
|
||||
|
||||
The migration tool is written in Go. Download and install the latest version of Go from the [official Go website](https://go.dev/doc/install).
|
||||
|
||||
### 2.2. Create a ZITADEL Instance and Organization
|
||||
|
||||
You'll need a target organization in ZITADEL to import your users. You can create a new organization or use an existing one.
|
||||
|
||||
If you don't have a ZITADEL instance, you can [sign up for free here](https://zitadel.com) to create a new one for you.
|
||||
See: [Managing Organizations in ZITADEL](https://zitadel.com/docs/guides/manage/console/organizations-overview).
|
||||
|
||||
> **Note:** Copy your Organization ID (Resource ID) since you will use the id in the later steps.
|
||||
|
||||
---
|
||||
|
||||
## 3. Exporting User Data from Keycloak
|
||||
|
||||
### 3.1. Set up Keycloak Locally (Optional)
|
||||
|
||||
To run a local development Keycloak instance, use the official Docker image:
|
||||
|
||||
```bash
|
||||
docker run -d -p 8081:8080 \
|
||||
-e KEYCLOAK_ADMIN=admin \
|
||||
-e KEYCLOAK_ADMIN_PASSWORD=admin \
|
||||
quay.io/keycloak/keycloak:22.0.1 start-dev
|
||||
```
|
||||
|
||||
### 3.2. Export Users from Keycloak
|
||||
|
||||
Run the following command inside the Keycloak container to export your realm and users:
|
||||
|
||||
```bash
|
||||
docker exec <container_name> \
|
||||
/opt/keycloak/bin/kc.sh export \
|
||||
--dir /tmp/export \
|
||||
--realm <your_realm_name> \
|
||||
--users realm_file
|
||||
```
|
||||
|
||||
Then copy the exported file to your host machine:
|
||||
|
||||
```bash
|
||||
docker cp <container_name>:/tmp/export/<your_realm_name>-realm.json .
|
||||
```
|
||||
|
||||
This creates a file such as:
|
||||
|
||||
```
|
||||
<your_realm_name>-realm.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Running the ZITADEL Migration Tool
|
||||
|
||||
### 4.1. Install the Migration Tool
|
||||
|
||||
Follow the installation instructions to set up the ZITADEL migration tool from [ZITADEL Tools](https://github.com/zitadel/zitadel-tools?tab=readme-ov-file#installation).
|
||||
|
||||
### 4.2. Generate Import JSON
|
||||
|
||||
Use the migration tool to convert the Keycloak realm export into a ZITADEL-compatible JSON file:
|
||||
|
||||
```bash
|
||||
zitadel-tools migrate keycloak \
|
||||
--org=<ORG_ID> \
|
||||
--realm=<your_realm_name>-realm.json \
|
||||
--output=./importBody.json \
|
||||
--timeout=5m0s \
|
||||
--multiline
|
||||
```
|
||||
|
||||
The tool will generate `importBody.json`, which is ready for importing into ZITADEL.
|
||||
|
||||
---
|
||||
|
||||
## 5. Importing Users into ZITADEL
|
||||
|
||||
### 5.1. Obtain Access Token (or PAT) for API Access
|
||||
|
||||
To call the ZITADEL Management API, you need to authenticate using a **Service User** with the `IAM_OWNER` Manager permissions.
|
||||
|
||||
There are two recommended authentication methods:
|
||||
|
||||
* **Client Credentials Flow**
|
||||
[Learn how to authenticate with client credentials.](https://zitadel.com/docs/guides/integrate/service-users/client-credentials)
|
||||
|
||||
* **Personal Access Token (PAT)**
|
||||
[Learn how to create and use a PAT.](https://zitadel.com/docs/guides/integrate/service-users/personal-access-token)
|
||||
|
||||
**Reference:** [Service Users & API Authentication](https://zitadel.com/docs/guides/integrate/service-users/authenticate-service-users#authentication-methods)
|
||||
|
||||
---
|
||||
|
||||
### 5.2. Import Data with the ZITADEL API
|
||||
|
||||
Use your **access token** or **PAT** to authenticate, then call the [Management API – Human User Import](/reference/api/management/zitadel.management.v1.ManagementService.ImportHumanUser) endpoint.
|
||||
|
||||
**Import Endpoint:**
|
||||
|
||||
* `POST /admin/v1/import`
|
||||
* `Authorization: Bearer <token>`
|
||||
* **Body:** Generated in step 4.2
|
||||
|
||||
#### Example cURL request
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url https://${CUSTOM_DOMAIN}/admin/v1/import \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'Authorization: Bearer <token>' \
|
||||
--data @importBody.json
|
||||
```
|
||||
|
||||
Successful Response:
|
||||
```bash
|
||||
{
|
||||
"success": {
|
||||
"orgs": [
|
||||
{
|
||||
"orgId": "318900732864567390",
|
||||
"humanUserIds": [
|
||||
"da72ac13-6994-4498-8b27-3ff9555661b2",
|
||||
"4e987a01-34db-4393-b61c-1ce753baf69c",
|
||||
"1041d710-8a89-48f8-85b5-1ab9656190f3",
|
||||
"7b23b799-4f0f-4964-bc6d-95c534787d2c",
|
||||
"6f2f1b2f-b292-4431-932b-620124e065ec",
|
||||
"2c65045a-9de8-4d28-b686-b27bf3a70fc3",
|
||||
"aca2dd3e-689c-4ab6-b446-0990127b1e0d",
|
||||
"18a23e01-f0fe-443f-9f1c-2a8135cd22c2",
|
||||
"c49af4bf-0dbb-4994-b453-b8dd0d5006ea"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"errors": [
|
||||
{
|
||||
"type": "org",
|
||||
"id": "318900732864567390",
|
||||
"message": "ID=ORG-lapo2m Message=Errors.Org.AlreadyExisting"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
ℹ️ Note: The above response indicates that the organization already existed, and users were successfully added. This is not an error, and you can consider the import successful as long as the HTTP status code is **200**.
|
||||
|
||||
---
|
||||
|
||||
## 6. Testing the Migration
|
||||
|
||||
### 6.1. Test User Login
|
||||
|
||||
Use the **ZITADEL login page** or your integrated app to test logging in with one of the imported users.
|
||||
|
||||
Confirm that the migrated password works as expected.
|
||||
|
||||
---
|
||||
|
||||
### 6.2. Troubleshooting
|
||||
|
||||
**Common issues:**
|
||||
|
||||
* Invalid Keycloak export format
|
||||
* Malformed JSON
|
||||
* Missing `orgId` or access token
|
||||
* Timeout exceeded during import
|
||||
|
||||
The import API returns a detailed response with any errors encountered during the process.
|
||||
|
||||
#### Where to check logs and get help
|
||||
|
||||
You can verify that users were imported successfully by querying the **events API** and looking for the `user.human.added` event type.
|
||||
|
||||
Use the following request:
|
||||
|
||||
```bash
|
||||
curl --location 'https://${CUSTOM_DOMAIN}/admin/v1/events/_search' \
|
||||
--header 'Authorization: Bearer <token>' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"asc": true,
|
||||
"limit": 1000,
|
||||
"event_types": [
|
||||
"user.human.added"
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
This will return a list of user creation events including details such as email, username, and hashed password to help you confirm the imported data.
|
||||
|
||||
Successful Response
|
||||
```bash
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"type": {
|
||||
"type": "user.human.added",
|
||||
"localized": {
|
||||
"key": "EventTypes.user.human.added",
|
||||
"localizedMessage": "Person added"
|
||||
}
|
||||
},
|
||||
"payload": {
|
||||
"displayName": "test user",
|
||||
"email": "[email protected]",
|
||||
"userName": "testuser"
|
||||
},
|
||||
"aggregate": {
|
||||
"id": "da72ac13-6994-4498-8b27-3ff9555661b2",
|
||||
"resourceOwner": "318900732864567390"
|
||||
},
|
||||
"creationDate": "2025-07-22T15:16:06.364302Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
ℹ️ Note: If you see entries with "type": "user.human.added" and correct payload data, the import was successful.
|
||||
|
||||
---
|
||||
|
||||
## 7. Q&A and Further Resources
|
||||
|
||||
### Real-World Scenarios & Common Questions
|
||||
|
||||
**Q:** What is the maximum number of users that can be imported in a single batch?
|
||||
**A:** There is no hard limit on the number of users. However, there is a **timeout**.
|
||||
For **ZITADEL Cloud deployments**, the timeout is **5 minutes**, which typically allows for importing around **5,000 users per batch**.
|
||||
|
||||
---
|
||||
@@ -0,0 +1,196 @@
|
||||
---
|
||||
title: Migrate from ZITADEL
|
||||
sidebar_label: From ZITADEL
|
||||
---
|
||||
|
||||
This guide explains how to migrate from ZITADEL, this includes
|
||||
|
||||
- ZITADEL Cloud to self-hosted
|
||||
- ZITADEL self-hosted to ZITADEL Cloud
|
||||
- ZITADEL v1 (deprecated) to ZITADEL v2.x
|
||||
|
||||
## Considerations
|
||||
|
||||
The following scripts don't include:
|
||||
|
||||
- Global policies
|
||||
- IAM members
|
||||
- Global IDPs
|
||||
- Global second factor / multi-factors
|
||||
- Machine keys
|
||||
- Personal Access Tokens
|
||||
- Application keys
|
||||
- Passkey authentication
|
||||
|
||||
Which results in that if you want to import, and you have no defined organization-specific custom policies, the experience for your users will not be exactly like in your old instance.
|
||||
|
||||
<Callout>
|
||||
Note that the resources will be migrated without the event stream. This means that you will not have the audit trail for the imported objects.
|
||||
</Callout>
|
||||
|
||||
## Authorization
|
||||
|
||||
You need a PAT from a service user with IAM Owner permissions in both the source and target system.
|
||||
|
||||
### Source system
|
||||
|
||||
1. Go to your default organization
|
||||
2. Create a service user "import_user" with Access Token Type "Bearer"
|
||||
3. Create a [personal access token](/guides/integrate/service-users/personal-access-token)
|
||||
4. Go to the Default settings
|
||||
5. Add the import_user as [manager](/guides/manage/console/managers) with the role "IAM Owner"
|
||||
|
||||
Save the PAT to the environment variable `PAT_EXPORT_TOKEN` and the source domain as `ZITADEL_EXPORT_DOMAIN` to run the following scripts.
|
||||
|
||||
### Target system
|
||||
|
||||
1. Go to your default organization
|
||||
2. Create a service user "export_user" with Access Token Type "Bearer"
|
||||
3. Create a [personal access token](/guides/integrate/service-users/personal-access-token)
|
||||
4. Go to the Default settings
|
||||
5. Add the export_user as [manager](/guides/manage/console/managers) with the role "IAM Owner"
|
||||
|
||||
Save the PAT to the environment variable `PAT_IMPORT_TOKEN` and the source domain as `ZITADEL_IMPORT_DOMAIN` to run the following scripts.
|
||||
|
||||
<Callout title="Clean-up">
|
||||
You should let the PAT expire as soon as possible.
|
||||
Make sure to delete the created users after you are done with the migration.
|
||||
</Callout>
|
||||
|
||||
## Use file
|
||||
|
||||
### Export to file
|
||||
|
||||
To export all necessary data you only have to use one request, as an example:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url $ZITADEL_EXPORT_DOMAIN/admin/v1/export \
|
||||
--header "Authorization: Bearer $PAT_EXPORT_TOKEN" \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"org_ids": [ ],
|
||||
"excluded_org_ids": [ ],
|
||||
"with_passwords": true,
|
||||
"with_otp": true,
|
||||
"timeout": "30s",
|
||||
"response_output": true
|
||||
}' -o export.json
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
| ---------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| org_ids | list of strings | provide a list of Organization IDs to select which organizations should be exported (eg, `[ "70669144072186707", "70671105999825752" ]`); leave empty to export all |
|
||||
| excluded_org_ids | list of strings | to exclude several organization, if for example no organizations are selected |
|
||||
| with_passwords | bool | to include the hashed_passwords of the users in the export |
|
||||
| with_otp | bool | to include the OTP-code of the users in the export |
|
||||
| timeout | duration string | timeout of the call to export the data |
|
||||
| response_output | bool | to output the export as response to the call |
|
||||
|
||||
### Import from file
|
||||
|
||||
<Callout>
|
||||
To import the exported data into you new instance, you have to have an already existing instance on a ZITADEL, with all desired configuration and global resources.
|
||||
</Callout>
|
||||
|
||||
Then as an example you can use one request for the import:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url $ZITADEL_IMPORT_DOMAIN/admin/v1/import \
|
||||
--header "Authorization: Bearer $PAT_IMPORT_TOKEN" \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"timeout": "10m",
|
||||
"data_orgsv1": '"$(cat export.json)"'
|
||||
}'
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----------- | --------------- | --------------------------------------- |
|
||||
| timeout | duration string | timeout of the call to import the data |
|
||||
| data_orgsv1 | string | data which was exported from ZITADEL V1 |
|
||||
|
||||
## Use Google Cloud Storage
|
||||
|
||||
### Export to GCS
|
||||
|
||||
<Callout>
|
||||
To use this requests you have to have an access token with enough permissions to export and import.
|
||||
The used serviceaccount has to have at least the role "Storage Object Creator" to create objects on GCS
|
||||
</Callout>
|
||||
|
||||
To export all necessary data you only have to use one request which results in a file in your GCS, as an example:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url $ZITADEL_EXPORT_DOMAIN/admin/v1/export \
|
||||
--header "Authorization: Bearer $PAT_EXPORT_TOKEN" \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"org_ids": [ ],
|
||||
"excluded_org_ids": [ ],
|
||||
"with_passwords": true,
|
||||
"with_otp": true,
|
||||
"timeout": "30s",
|
||||
"gcs_output": {
|
||||
"path": "export.json",
|
||||
"bucket": "caos-zitadel-exports",
|
||||
"serviceaccount_json": "XXXX"
|
||||
}
|
||||
}' -o export.json
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
| ---------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| org_ids | list of strings | provide a list of Organization IDs to select which organizations should be exported (eg, `[ "70669144072186707", "70671105999825752" ]`); leave empty to export all |
|
||||
| excluded_org_ids | list of strings | to exclude several organization, if for example no organizations are selected |
|
||||
| with_passwords | bool | to include the hashed_passwords of the users in the export |
|
||||
| with_otp | bool | to include the OTP-code of the users in the export |
|
||||
| timeout | duration string | timeout of the call to export the data |
|
||||
| gcs_output | object(data_orgsv1_gcs) | to write a file into GCS as output to the call |
|
||||
|
||||
data_orgsv1_gcs object:
|
||||
|
||||
| Field | Type | Description |
|
||||
| ------------------- | ------ | ----------------------------------------------------------------- |
|
||||
| path | string | path to the output file on GCS |
|
||||
| bucket | string | used bucket for output on GCS |
|
||||
| serviceaccount_json | string | base64-encoded serviceaccount.json used to output the file on GCS |
|
||||
|
||||
### Import to GCS
|
||||
|
||||
<Callout>
|
||||
To import the exported data into you new instance, you have to have an already existing instance on a ZITADEL, with all desired configuration and global resources.
|
||||
The used serviceaccount has to have at least the role "Storage Object Viewer" to read objects from GCS
|
||||
</Callout>
|
||||
|
||||
Then as an example you can use one request for the import:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url $ZITADEL_IMPORT_DOMAIN/admin/v1/import \
|
||||
--header "Authorization: Bearer $PAT_IMPORT_TOKEN" \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"timeout": "10m",
|
||||
"data_orgsv1_gcs": {
|
||||
"path": "export.json",
|
||||
"bucket": "caos-zitadel-exports",
|
||||
"serviceaccount_json": "XXXX"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
| --------------- | ----------------------- | -------------------------------------- |
|
||||
| timeout | duration string | timeout of the call to import the data |
|
||||
| data_orgsv1_gcs | object(data_orgsv1_gcs) | to read the export from GCS directly |
|
||||
|
||||
data_orgsv1_gcs object:
|
||||
|
||||
| Field | Type | Description |
|
||||
| ------------------- | ------ | ----------------------------------------------------------------- |
|
||||
| path | string | path to the exported file on GCS |
|
||||
| bucket | string | used bucket to read from GCS |
|
||||
| serviceaccount_json | string | base64-encoded serviceaccount.json used to read the file from GCS |
|
||||
@@ -0,0 +1,253 @@
|
||||
---
|
||||
title: Migrate Users
|
||||
sidebar_label: Users
|
||||
---
|
||||
|
||||
Migrating users from an existing system, while minimizing impact on said users, can be a challenging task.
|
||||
|
||||
## Individual Users
|
||||
|
||||
Creating individual users can be done with this endpoint: [ImportHumanUser](/reference/api/management/zitadel.management.v1.ManagementService.ImportHumanUser).
|
||||
Please also consult our [guide](/guides/manage/user/reg-create-user) on how to create users.
|
||||
|
||||
```json
|
||||
{
|
||||
"userName": "test9@test9",
|
||||
"profile": {
|
||||
"firstName": "Road",
|
||||
"lastName": "Runner",
|
||||
"displayName": "Road Runner",
|
||||
"preferredLanguage": "en"
|
||||
},
|
||||
"email": {
|
||||
"email": "[email protected]",
|
||||
"isEmailVerified": false
|
||||
},
|
||||
"hashedPassword": {
|
||||
"value": "$2a$14$aPbwhMVJSVrRRW2NoM/5.esSJO6o/EIGzGxWiM5SAEZlGqCsr9DAK",
|
||||
"algorithm": "bcrypt"
|
||||
},
|
||||
"passwordChangeRequired": false,
|
||||
"otpCode": "testotp",
|
||||
"requestPasswordlessRegistration": false,
|
||||
"idps": [
|
||||
{
|
||||
"configId": "124425861423228496",
|
||||
"externalUserId": "[email protected]",
|
||||
"displayName": "name"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Bulk import
|
||||
|
||||
For bulk import use the [import endpoint](/reference/api/admin/zitadel.admin.v1.AdminService.ImportData) on the admin API:
|
||||
|
||||
```json
|
||||
{
|
||||
"timeout": "10m",
|
||||
"data_orgs": {
|
||||
"orgs": [
|
||||
{
|
||||
"orgId": "104133391254874632",
|
||||
"org": {
|
||||
"name": "ACME"
|
||||
},
|
||||
"humanUsers": [
|
||||
{
|
||||
"userId": "104133391271651848",
|
||||
"user": {
|
||||
"userName": "test9@test9",
|
||||
"profile": {
|
||||
"firstName": "Road",
|
||||
"lastName": "Runner",
|
||||
"displayName": "Road Runner",
|
||||
"preferredLanguage": "de"
|
||||
},
|
||||
"email": {
|
||||
"email": "[email protected]",
|
||||
"isEmailVerified": true
|
||||
},
|
||||
"hashedPassword": {
|
||||
"value": "$2a$14$aPbwhMVJSVrRRW2NoM/5.esSJO6o/EIGzGxWiM5SAEZlGqCsr9DAK",
|
||||
"algorithm": "bcrypt"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"userId": "120080115081209416",
|
||||
"user": {
|
||||
"userName": "testuser",
|
||||
"profile": {
|
||||
"firstName": "Test",
|
||||
"lastName": "User",
|
||||
"displayName": "Test User",
|
||||
"preferredLanguage": "und"
|
||||
},
|
||||
"email": {
|
||||
"email": "[email protected]",
|
||||
"isEmailVerified": true
|
||||
},
|
||||
"hashedPassword": {
|
||||
"value": "$2a$14$785Fcdbpo9rn5L7E21nIAOJvGCPgWFrZhIAIfDonYXzWuZIKRAQkO",
|
||||
"algorithm": "bcrypt"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"userId": "145195347319252359",
|
||||
"user": {
|
||||
"userName": "wile@test9",
|
||||
"profile": {
|
||||
"firstName": "Wile E.",
|
||||
"lastName": "Coyote",
|
||||
"displayName": "Wile E. Coyote",
|
||||
"preferredLanguage": "en"
|
||||
},
|
||||
"email": {
|
||||
"email": "[email protected]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<Callout>
|
||||
We will improve the bulk import interface for users in the future.
|
||||
You can show your interest or join the discussion on [this issue](https://github.com/zitadel/zitadel/issues/5524).
|
||||
</Callout>
|
||||
|
||||
## Migrate secrets
|
||||
|
||||
Besides user data you need to migrate secrets, such as password hashes, OTP seeds, and public keys for passkeys (FIDO2).
|
||||
The snippets in the sections below are parts from the bulk import endpoint, to clarify how the different objects can be imported.
|
||||
|
||||
### Passwords
|
||||
|
||||
ZITADEL stores passwords only as irreversible hashes, never in clear text.
|
||||
Existing password hashes can be imported if they use a supported [hash algorithm](/concepts/architecture/secrets#hashed-secrets).
|
||||
|
||||
Import password hashes using the import API (snippet from [bulk-import](#bulk-import)):
|
||||
```json
|
||||
{
|
||||
"userName": "test9@test9",
|
||||
...,
|
||||
"hashedPassword": {
|
||||
"value": "$2a$14$aPbwhMVJSVrRRW2NoM/5.esSJO6o/EIGzGxWiM5SAEZlGqCsr9DAK",
|
||||
"algorithm": "bcrypt"
|
||||
},
|
||||
"passwordChangeRequired": false,
|
||||
...,
|
||||
}
|
||||
```
|
||||
|
||||
Upon initial login, ZITADEL validates the imported password using the appropriate verifier.
|
||||
|
||||
<Callout title="Verifiers">
|
||||
In ZITADEL, a password verifier checks the validity of a password hash created with an algorithm different from the currently configured one.
|
||||
It acts as a translator, allowing ZITADEL to understand and validate hashes made with older algorithms like MD5 even when the system has transitioned to newer ones like Argon2.
|
||||
This is crucial during migrations or when importing user data.
|
||||
Essentially, a verifier ensures ZITADEL can work with passwords hashed using various algorithms, maintaining security while transitioning to stronger hashing methods.
|
||||
</Callout>
|
||||
|
||||
Regardless of the `passwordChangeRequired` setting, the password is rehashed using the configured hasher algorithm and stored.
|
||||
This ensures consistency and allows for automatic updates even when hasher configurations are changed, such as increasing salt cost for bcrypt.
|
||||
|
||||
To configure the default hasher for new user passwords, set the `Algorithm` of the `PasswordHasher` in the [runtime configuration file](/self-hosting/manage/configure/configure#runtime-configuration-file)
|
||||
or by the environment variable `ZITADEL_SYSTEMDEFAULTS_PASSWORDHASHER_HASHER_ALGORITHM`, for example:
|
||||
|
||||
```
|
||||
ZITADEL_SYSTEMDEFAULTS_PASSWORDHASHER_HASHER_ALGORITHM='pbkdf2'
|
||||
```
|
||||
|
||||
Hasher configuration updates will automatically rehash existing passwords when they are validated or changed.
|
||||
|
||||
In case the hashes can't be transferred directly, you always have the option to create a user in ZITADEL without password and prompt users to create a new password.
|
||||
|
||||
If your legacy system receives the passwords in clear text (eg, login form) you could also directly create users via ZITADEL API.
|
||||
We will explain this pattern in more detail in this guide.
|
||||
|
||||
### One-time passwords (OTP)
|
||||
|
||||
You can pass the OTP secret when creating users:
|
||||
|
||||
_snippet from [bulk-import](#bulk-import) example:_
|
||||
```json
|
||||
{
|
||||
"userName": "test9@test9",
|
||||
...,
|
||||
"otpCode": "testotp",
|
||||
...,
|
||||
}
|
||||
```
|
||||
|
||||
### Passkeys
|
||||
|
||||
When creating new users, you can trigger a workflow that prompts the users to setup a passkey authenticator.
|
||||
|
||||
_snippet from [bulk-import](#bulk-import) example:_
|
||||
```json
|
||||
{
|
||||
"userName": "test9@test9",
|
||||
...,
|
||||
"requestPasswordlessRegistration": false,
|
||||
...,
|
||||
}
|
||||
```
|
||||
|
||||
For passkeys to work on the new system you need to make sure that the new auth server has the same domain as the legacy auth server.
|
||||
|
||||
<Callout>
|
||||
Currently it is not possible to migrate passkeys directly from another system.
|
||||
</Callout>
|
||||
|
||||
## Users linked to an external IDP
|
||||
|
||||
A users `sub` is bound to the external [IDP's Client ID](/guides/manage/console/default-settings#identity-providers).
|
||||
This means that the IDP Client ID configured in ZITADEL must be the same ID as in the legacy system.
|
||||
|
||||
Users should be imported with their `externalUserId`.
|
||||
|
||||
_snippet from [bulk-import](#bulk-import) example:_
|
||||
```json
|
||||
{
|
||||
"userName": "test9@test9",
|
||||
...,
|
||||
"idps": [
|
||||
{
|
||||
"configId": "124425861423228496",
|
||||
"externalUserId": "[email protected]",
|
||||
"displayName": "name"
|
||||
}
|
||||
...,
|
||||
}
|
||||
```
|
||||
|
||||
You can use an Action with [post-creation flow](/apis/actions/external-authentication#post-creation) to pull information such as roles from the old system and apply them to the user in ZITADEL.
|
||||
|
||||
## Metadata
|
||||
|
||||
You can store arbitrary key-value information on a user (or Organization) in ZITADEL.
|
||||
Use metadata to store additional attributes of the users, such as organizational unit, backend-id, etc.
|
||||
|
||||
<Callout>
|
||||
Metadata must be added to users after the users were created. Currently metadata can't be added during user creation.
|
||||
[API reference: User Metadata](/reference/api/user)
|
||||
</Callout>
|
||||
|
||||
Request metadata from the userinfo endpoint by passing the required [reserved scope](/apis/openidoauth/scopes#reserved-scopes) in your auth request.
|
||||
With the [complement token flow](/apis/actions/complement-token), you can also transform metadata (or roles) to custom claims.
|
||||
|
||||
## Authorizations / Roles
|
||||
|
||||
You can assign roles from owned or granted projects to a user.
|
||||
|
||||
<Callout>
|
||||
Authorizations must be added to users after the users were created. Currently metadata can't be added during user creation.
|
||||
[API reference: User Authorization / Grants](/reference/api/authorization)
|
||||
</Callout>
|
||||
Reference in New Issue
Block a user