Salta ai contenuti

ZITADEL Cheat Sheet

Overview

ZITADEL is an open-source, cloud-native identity management platform built in Go. It provides authentication, authorization, and user management with built-in multi-tenancy, supporting OAuth 2.0, OpenID Connect, SAML, and LDAP. ZITADEL is designed for B2B and B2C use cases with organizations, projects, and fine-grained role-based access control.

ZITADEL features event sourcing architecture, a powerful actions system for custom logic, branded login pages, machine-to-machine authentication, and comprehensive audit logs. It can be self-hosted or used as a managed cloud service, with SDKs available for multiple languages and frameworks.

Installation

# Docker (quickest start)
docker run -d \
  --name zitadel \
  -p 8080:8080 \
  ghcr.io/zitadel/zitadel:latest start-from-init \
  --masterkey "MasterkeyNeedsToHave32Characters" \
  --tlsMode disabled

# Docker Compose
curl -fsSL https://raw.githubusercontent.com/zitadel/zitadel/main/docs/docs/self-hosting/deploy/docker-compose.yaml -o docker-compose.yaml
docker compose up -d

# Linux binary
curl -fsSL https://github.com/zitadel/zitadel/releases/latest/download/zitadel-linux-amd64.tar.gz | tar xz
./zitadel start-from-init --masterkey "MasterkeyNeedsToHave32Characters"

# Kubernetes via Helm
helm repo add zitadel https://charts.zitadel.com
helm install zitadel zitadel/zitadel

# Access console at http://localhost:8080/ui/console

ZITADEL CLI

# Initialize ZITADEL
./zitadel init --config defaults.yaml

# Start from init (first run)
./zitadel start-from-init \
  --masterkey "MasterkeyNeedsToHave32Characters" \
  --config defaults.yaml

# Start (subsequent runs)
./zitadel start --masterkey "MasterkeyNeedsToHave32Characters"

# Setup only (migration/initialization)
./zitadel setup --masterkey "MasterkeyNeedsToHave32Characters"

Management API

# Get a PAT (Personal Access Token) from the console first

# List users
curl -X POST https://your-instance.zitadel.cloud/v2/users \
  -H "Authorization: Bearer $PAT" \
  -H "Content-Type: application/json" \
  -d '{"queries": []}'

# Create a human user
curl -X POST https://your-instance.zitadel.cloud/v2/users/human \
  -H "Authorization: Bearer $PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "john.doe",
    "profile": {
      "givenName": "John",
      "familyName": "Doe",
      "displayName": "John Doe"
    },
    "email": {
      "email": "john@example.com",
      "isVerified": true
    },
    "password": {
      "password": "SecureP@ss123!",
      "changeRequired": false
    }
  }'

# Create a machine user (service account)
curl -X POST https://your-instance.zitadel.cloud/v2/users/machine \
  -H "Authorization: Bearer $PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "api-service",
    "name": "API Service Account",
    "description": "Service account for API access",
    "accessTokenType": "ACCESS_TOKEN_TYPE_JWT"
  }'

# Create an organization
curl -X POST https://your-instance.zitadel.cloud/management/v1/orgs \
  -H "Authorization: Bearer $PAT" \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme Corporation"}'

OAuth 2.0 / OIDC Integration

// Node.js with openid-client
const { Issuer } = require("openid-client");

const issuer = await Issuer.discover("https://your-instance.zitadel.cloud");
const client = new issuer.Client({
  client_id: "YOUR_CLIENT_ID",
  client_secret: "YOUR_CLIENT_SECRET",
  redirect_uris: ["http://localhost:3000/callback"],
  response_types: ["code"],
});

// Authorization URL
const authUrl = client.authorizationUrl({
  scope: "openid email profile",
  resource: "https://api.example.com",
});

// Exchange code for tokens
const tokenSet = await client.callback(
  "http://localhost:3000/callback",
  { code: req.query.code },
);

const userinfo = await client.userinfo(tokenSet.access_token);

React Integration

import { createBrowserRouter } from "react-router-dom";
import { UserManager } from "oidc-client-ts";

const userManager = new UserManager({
  authority: "https://your-instance.zitadel.cloud",
  client_id: "YOUR_CLIENT_ID",
  redirect_uri: "http://localhost:3000/callback",
  scope: "openid email profile",
  response_type: "code",
});

// Login
await userManager.signinRedirect();

// Callback
const user = await userManager.signinRedirectCallback();
console.log(user.profile);

// Logout
await userManager.signoutRedirect();

Projects and Applications

# Create a project
curl -X POST https://your-instance.zitadel.cloud/management/v1/projects \
  -H "Authorization: Bearer $PAT" \
  -H "Content-Type: application/json" \
  -d '{"name": "My Web Platform", "projectRoleAssertion": true}'

# Create an OIDC application
curl -X POST https://your-instance.zitadel.cloud/management/v1/projects/PROJECT_ID/apps/oidc \
  -H "Authorization: Bearer $PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Web App",
    "redirectUris": ["http://localhost:3000/callback"],
    "postLogoutRedirectUris": ["http://localhost:3000"],
    "responseTypes": ["OIDC_RESPONSE_TYPE_CODE"],
    "grantTypes": ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE"],
    "appType": "OIDC_APP_TYPE_WEB",
    "authMethodType": "OIDC_AUTH_METHOD_TYPE_BASIC"
  }'

# Add project roles
curl -X POST https://your-instance.zitadel.cloud/management/v1/projects/PROJECT_ID/roles/_bulk \
  -H "Authorization: Bearer $PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "roles": [
      {"key": "admin", "displayName": "Administrator"},
      {"key": "editor", "displayName": "Editor"},
      {"key": "viewer", "displayName": "Viewer"}
    ]
  }'

Actions (Custom Logic)

// Action: Add custom claims to tokens
function addCustomClaims(ctx, api) {
  if (ctx.v1.user.grants) {
    var roles = [];
    ctx.v1.user.grants.grants.forEach(function(grant) {
      grant.roles.forEach(function(role) {
        roles.push(role);
      });
    });
    api.v1.claims.setClaim("custom:roles", roles);
  }
  api.v1.claims.setClaim("custom:org", ctx.v1.user.org.name);
}

// Action: Block specific email domains
function blockDomain(ctx, api) {
  var dominated = ["disposable.com", "tempmail.com"];
  var email = ctx.v1.user.human.email;
  dominated.forEach(function(domain) {
    if (email.endsWith("@" + domain)) {
      api.v1.deny("Registration from this email domain is not allowed");
    }
  });
}

Advanced Usage

Machine-to-Machine (JWT Profile)

const { createRemoteJWKSet, jwtVerify } = require("jose");

// Verify ZITADEL access tokens
const JWKS = createRemoteJWKSet(
  new URL("https://your-instance.zitadel.cloud/oauth/v2/keys")
);

const { payload } = await jwtVerify(token, JWKS, {
  issuer: "https://your-instance.zitadel.cloud",
  audience: "YOUR_PROJECT_ID",
});

SAML Identity Provider

curl -X POST https://your-instance.zitadel.cloud/admin/v1/idps/saml \
  -H "Authorization: Bearer $PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Corporate SSO",
    "metadataXml": "<EntityDescriptor>...</EntityDescriptor>",
    "binding": "SAML_BINDING_POST",
    "nameIdFormat": "SAML_NAME_ID_FORMAT_PERSISTENT"
  }'

Custom Login Branding

curl -X POST https://your-instance.zitadel.cloud/management/v1/orgs/me/policies/label \
  -H "Authorization: Bearer $PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "primaryColor": "#5469d4",
    "backgroundColor": "#fafafa",
    "fontColor": "#1a1a1a",
    "hideLoginNameSuffix": true,
    "disableWatermark": true
  }'

Configuration

# defaults.yaml
Log:
  Level: info
  Formatter:
    Format: text

Port: 8080

ExternalDomain: localhost
ExternalPort: 8080
ExternalSecure: false

Database:
  postgres:
    Host: localhost
    Port: 5432
    Database: zitadel
    User:
      Username: zitadel
      Password: zitadel
    Admin:
      Username: postgres
      Password: postgres
# Environment variables
ZITADEL_MASTERKEY="MasterkeyNeedsToHave32Characters"
ZITADEL_EXTERNALDOMAIN="your-instance.example.com"
ZITADEL_EXTERNALSECURE=true
ZITADEL_DATABASE_POSTGRES_HOST=db.example.com
ZITADEL_DATABASE_POSTGRES_PORT=5432

Troubleshooting

IssueSolution
Console not loadingCheck ExternalDomain and ExternalPort configuration
Master key errorKey must be exactly 32 characters long
OIDC discovery failsVerify ExternalDomain matches the URL used for discovery
Token verification failsCheck audience matches project ID, not client ID
Actions not executingVerify action is assigned to the correct flow and trigger
CORS errorsConfigure allowed origins in application settings
Database migration failsEnsure admin database credentials have CREATE permissions
Login branding not showingActivate the label policy after configuration