Architecture¶
Component topology, deployment patterns, data model, and API surface of Zitadel.
Component Topology¶
flowchart TB
subgraph Clients["Client Applications"]
Browser["Browser\n(Login UI)"]
Console["Console\n(Angular Admin UI)"]
Apps["Your Applications\n(Web, Mobile, API)"]
end
subgraph Zitadel["Zitadel Server (Go Binary)"]
direction TB
APIGW["API Gateway\n(connectRPC / gRPC / REST)"]
subgraph Services["Domain Services"]
AuthSvc["Authentication\nService"]
UserSvc["User\nService"]
OrgSvc["Organization\nService"]
ProjSvc["Project\nService"]
SessSvc["Session\nService"]
ActSvc["Actions\nService"]
end
subgraph Core["Core Engine"]
Cmd["Command Handlers\n(Write Path)"]
Query["Query Handlers\n(Read Path)"]
ES["Eventstore\n(Events2 Table)"]
ProjW["Projection\nWorkers"]
end
subgraph Ext["Extensibility"]
Actions["JS Actions\nRuntime"]
Hooks["Webhook\nDispatcher"]
SCIM["SCIM 2.0\nServer"]
end
end
subgraph Data["Data Layer"]
PG["PostgreSQL\n(Events + Projections)"]
Redis["Redis\n(Query Cache)"]
end
subgraph External["External Systems"]
IdP["Identity Providers\n(Google, GitHub, Azure AD)"]
SMTP["SMTP / SMS\n(Notifications)"]
OTel["OpenTelemetry\nCollector"]
end
Browser --> APIGW
Console --> APIGW
Apps --> APIGW
APIGW --> Services
Services --> Core
Cmd --> ES
ProjW --> ES
Query --> Redis
ProjW --> PG
ES --> PG
Query --> PG
APIGW --> Ext
Actions --> Hooks
SCIM --> UserSvc
AuthSvc --> IdP
ActSvc --> SMTP
APIGW --> OTel
style Zitadel fill:#1565c0,color:#fff
style Data fill:#2e7d32,color:#fff
style External fill:#e65100,color:#fff
Monolithic Binary, Modular Internals¶
Zitadel ships as a single Go binary with modular internal packages:
| Package | Purpose |
|---|---|
internal/eventstore/ |
Event store abstraction, PostgreSQL implementation, push/pop logic |
internal/command/ |
Write-side command handlers, business rule validation |
internal/query/ |
Read-side query handlers, projection consumers |
internal/auth/ |
OIDC, OAuth 2.0, SAML 2.0 protocol implementations |
internal/authz/ |
Authorization resolution, permission checking |
internal/api/ |
gRPC/REST API layer, connectRPC integration |
internal/actions/ |
JavaScript runtime for custom auth flows |
internal/crypto/ |
Encryption, hashing, key management |
internal/webauthn/ |
FIDO2/WebAuthn authentication |
internal/notification/ |
Email, SMS delivery pipeline |
internal/idp/ |
External identity provider integrations |
internal/user/ |
User domain logic (human + machine) |
internal/org/ |
Organization domain logic |
internal/project/ |
Project, application, role management |
internal/i18n/ |
Internationalization |
internal/cache/ |
Redis caching abstraction |
API Surface¶
Zitadel exposes a dual-versioned API surface:
V1 APIs (Legacy, Context-Based)¶
| API | Scope | Base Path | Purpose |
|---|---|---|---|
| Auth API | Per-user | /auth/v1/ |
User-specific ops (profile, tokens, MFA) |
| Management API | Per-org | /management/v1/ |
Org admin ops (users, projects, apps) |
| Admin API | Instance-wide | /admin/v1/ |
Instance-level configuration |
| System API | Self-hosted | /system/v1/ |
Superordinate control |
V2 APIs (Current, Resource-Oriented)¶
| API | Base Path | Purpose |
|---|---|---|
| User Service | /v2/users/ |
User management (preferred for new integrations) |
| Session Service | /v2/sessions/ |
Session lifecycle management |
| Organization Service | /v2/orgs/ |
Organization management |
| Project Service | /v2/projects/ |
Projects, apps, roles |
| Action Service | /v2/actions/ |
Actions and webhooks |
| Feature Service | /v2/features/ |
Feature flag management |
| Settings Service | /v2/settings/ |
Login, password, branding policies |
| Authorization Service | /v2/authorizations/ |
User authorization (grants) |
| OIDC Service | Standard OIDC endpoints | OpenID Connect operations |
| SAML Service | Standard SAML endpoints | SAML 2.0 operations |
| SCIM 2.0 | /scim/v2/{orgID}/ |
User provisioning |
| Web Key Service | /v2/webkeys/ |
JWT signing key management |
API Conventions¶
| Operation | Method | Pattern |
|---|---|---|
| Create | POST | /v2/<resource> |
| Update | POST | /v2/<resource>/<id> |
| Delete | DELETE | /v2/<resource>/<id> |
| Set | PUT | /v2/<resource> |
| Get | GET | /v2/<resource>/<id> |
| Search | POST | /v2/<resource>/search |
All APIs are generated from Protocol Buffer definitions in proto/zitadel/. The server supports connectRPC, standard gRPC, and HTTP/1.1 simultaneously.
Data Model¶
Hierarchical Resource Model¶
erDiagram
INSTANCE ||--o{ ORGANIZATION : contains
ORGANIZATION ||--o{ USER : manages
ORGANIZATION ||--o{ PROJECT : owns
ORGANIZATION ||--o{ IDP_CONFIG : configures
ORGANIZATION ||--o{ POLICY : enforces
PROJECT ||--o{ APPLICATION : registers
PROJECT ||--o{ ROLE : defines
PROJECT ||--o{ PROJECT_GRANT : shares
USER ||--o{ USER_GRANT : receives
USER ||--o{ SESSION : creates
USER ||--o{ IDP_LINK : links
PROJECT_GRANT }o--|| ORGANIZATION : "granted to"
USER_GRANT }o--|| PROJECT : "scoped to"
USER_GRANT }o--|| USER : "assigned to"
ROLE }o--|| USER_GRANT : "included in"
INSTANCE {
string instance_id PK
string domain
}
ORGANIZATION {
string org_id PK
string name
string state
}
USER {
string user_id PK
string org_id FK
string type
string state
string email
string phone
}
PROJECT {
string project_id PK
string org_id FK
string name
string state
}
APPLICATION {
string app_id PK
string project_id FK
string name
string type
}
ROLE {
string role_key PK
string project_id FK
string display_name
string group
}
PROJECT_GRANT {
string grant_id PK
string project_id FK
string granted_org_id FK
string state
}
USER_GRANT {
string grant_id PK
string user_id FK
string project_id FK
string org_id FK
}
Eventstore Schema¶
The eventstore uses a custom PostgreSQL schema optimized for append-only operations:
| Table | Purpose |
|---|---|
eventstore.events2 |
Immutable event log with composite PK (aggregate_id, sequence) |
eventstore.unique_constraints |
Enforces uniqueness across events (for example, unique email per instance) |
projections.* |
Materialized read-model tables rebuilt from events |
Events are inserted atomically via the eventstore.push PostgreSQL function, which handles sequence assignment and unique constraint checking in a single transaction.
Deployment Topologies¶
Single Instance (Development)¶
flowchart LR
subgraph Host["Docker Host"]
ZA["Zitadel\n:8080"]
PG["PostgreSQL 17\n:5432"]
end
ZA --> PG
Kubernetes Production (HA)¶
flowchart TB
subgraph LB["Load Balancer"]
NGINX["NGINX Ingress\n(TLS Termination)"]
end
subgraph K8s["Kubernetes Cluster"]
subgraph ZA["Zitadel Pods (HPA)"]
Z1["zitadel-7d4f8"]
Z2["zitadel-a3b2c"]
Z3["zitadel-e9f1g"]
end
Init["Init Job\n(DB Migrations)"]
Setup["Setup Job\n(Schema + Defaults)"]
subgraph Data["Data Plane"]
PGHA["PostgreSQL HA\n(Patroni / CloudSQL / RDS)"]
Redis["Redis\n(Cache)"]
end
end
NGINX --> Z1
NGINX --> Z2
NGINX --> Z3
Z1 --> PGHA
Z2 --> PGHA
Z3 --> PGHA
Z1 --> Redis
Z2 --> Redis
Z3 --> Redis
style K8s fill:#1565c0,color:#fff
style Data fill:#2e7d32,color:#fff
Multi-Cluster / SaaS¶
flowchart TB
subgraph Cloud["Zitadel Cloud"]
ZC["Managed Zitadel\nInstance"]
PG_C["Managed PostgreSQL"]
end
subgraph Remote1["Remote App Cluster 1"]
App1["Application A"]
end
subgraph Remote2["Remote App Cluster 2"]
App2["Application B"]
end
App1 -->|"OIDC / SAML"| ZC
App2 -->|"OIDC / SAML"| ZC
ZC --> PG_C
style Cloud fill:#1565c0,color:#fff
Sources¶
How It Works¶
How Zitadel's event-sourced CQRS engine processes commands, projects read models, handles authentication sessions, and triggers extensibility actions.
Event Sourcing Pipeline¶
Zitadel's core architecture is built on Command Query Responsibility Segregation (CQRS) with event sourcing. Every state mutation flows through a strict pipeline:
flowchart LR
subgraph Write["Write Path"]
API["API Layer\n(gRPC / REST)"]
Cmd["Command\nValidation"]
ES["Eventstore\n(PostgreSQL)"]
end
subgraph Project["Projection Workers"]
Sub["Event\nSubscriber"]
RM["Read Model\nTables"]
end
subgraph Read["Read Path"]
Query["Query\nHandlers"]
Cache["Redis\nCache"]
end
API --> Cmd --> ES
ES --> Sub --> RM
Query --> Cache --> RM
Write Path — Command Processing¶
- API layer receives a gRPC or REST request, authenticates the caller via Bearer token
- Authorization check — the proto-annotated permission (for example,
user.write) is validated against the caller's memberships - Command handler validates business rules (unique constraints, state preconditions, policy compliance)
- Event creation — the handler produces one or more domain events (for example,
user.human.added,user.human.password.changed) - Atomic persistence — events are appended to the
eventstore.events2table in PostgreSQL using the customeventstore.pushfunction - Unique constraint enforcement — PostgreSQL advisory locks (
pg_advisory_xact_lock_shared) prevent concurrent creation of duplicate resources
Event Structure¶
Every event in the store contains:
| Field | Purpose |
|---|---|
aggregate_type |
Entity category (user, org, project, and more) |
aggregate_id |
Unique entity identifier |
event_type |
Specific mutation (for example, user.human.added) |
sequence |
Per-aggregate monotonically increasing counter |
position |
Global ordering across all aggregates |
instance_id |
Tenant isolation key |
creation_date |
Timestamp |
payload |
Protocol Buffer encoded event data |
Read Path — Projection Workers¶
- Background projections subscribe to the event stream
- Each projection builds an optimized read-model table (for example,
projections.users,projections.orgs,projections.project_roles) - Projections track their last processed position. This enables idempotent replay
- Query handlers serve API reads from projection tables
- Redis cache (optional) reduces database load for hot queries
Authentication Session Lifecycle¶
OIDC Authorization Code Flow (with PKCE)¶
sequenceDiagram
participant User as Browser
participant App as Application
participant ZA as Zitadel
participant ES as Eventstore
User->>App: Click "Login"
App->>ZA: GET /oauth/v2/authorize?client_id=...&redirect_uri=...&code_challenge=...
ZA->>User: Show Login UI (passkey / password / IdP)
User->>ZA: Authenticate (WebAuthn / password / MFA)
ZA->>ES: Append session.created, auth.succeeded events
ZA->>App: 302 Redirect with authorization code
App->>ZA: POST /oauth/v2/token (code + code_verifier)
ZA->>ES: Append token.issued event
ZA->>App: Access token + ID token + refresh token
App->>ZA: GET /api (Authorization: Bearer <access_token>)
ZA->>App: JSON response
Session Model (V2 API)¶
Zitadel's V2 Session API provides fine-grained session management:
- Session creation —
POST /v2/sessionscreates a new session with an optional initial check (password, passkey, or IdP) - Session updates —
PUT /v2/sessions/{id}adds further checks (MFA step, TOTP verification) - Session token — returned after successful authentication, used for stateless session tracking
- Session termination —
DELETE /v2/sessions/{id}invalidates the session
Passkey Authentication Flow¶
sequenceDiagram
participant Browser as Browser
participant ZA as Zitadel Server
participant Auth as Authenticator\n(Platform/Roaming)
Browser->>ZA: POST /v2/sessions (user identifier)
ZA->>ZA: Generate WebAuthn challenge
ZA->>Browser: Return challenge + allowed credentials
Browser->>Auth: navigator.credentials.get({challenge, ...})
Auth->>Auth: User verification (biometric / PIN)
Auth->>Browser: Signed assertion
Browser->>ZA: POST /v2/sessions/{id} (assertion response)
ZA->>ZA: Verify signature against stored public key
ZA->>ZA: Append session.checked.webauthn event
ZA->>Browser: Session token
Authorization Resolution¶
When an API request arrives, Zitadel resolves permissions through:
- Token introspection — extract user ID and scope from the Bearer token
- Membership lookup — query the user's memberships across organizations, projects, and IAM scopes
- Role resolution — for each membership, resolve the assigned roles and their associated permissions
- Context filtering — if the permission requires a context (for example,
project.write:project-123), verify the target resource matches - Grant policy check — verify the request passes the organization's policy constraints
flowchart TB
Req["Incoming API Request"]
Token["Token Introspection"]
Member["Membership\nLookup"]
Roles["Role\nResolution"]
Perms["Permission\nExpansion"]
Check["Context\nCheck"]
Allow{"Allowed?"}
Req --> Token --> Member --> Roles --> Perms --> Check --> Allow
Allow -->|Yes| Handler["API Handler"]
Allow -->|No| Deny["403 Permission Denied"]
Actions & Webhooks Pipeline¶
Zitadel's extensibility layer intercepts authentication and registration flows:
- Trigger point — a defined event (for example,
pre creation,post creation,pre userinfo,post authentication) fires - Action execution — JavaScript functions (V2 Actions) or HTTP targets are invoked
- Request modification — actions can modify tokens, add claims, block requests, or redirect users
- Webhook delivery — events are POSTed to configured HTTP endpoints with full event payload
- Error handling — failed actions can be configured to block or allow the flow
Action Execution Context¶
// Example V2 Action: Add custom claims to token
function addAction(token, api) {
// token contains current JWT claims
// api provides methods to modify the response
api.v1.user.setMetadata("custom_role", "premium");
// Block the request if condition not met
if (!isAuthorized(token.user)) {
api.v1.blockRequest("User not authorized");
}
}
Multi-Tenancy Hierarchy¶
Zitadel enforces a strict hierarchy for resource isolation:
flowchart TB
Instance["Instance\n(Self-hosted deployment\nor Zitadel Cloud tenant)"]
Org1["Organization A\n(Tenant)"]
Org2["Organization B\n(Tenant)"]
Proj1A["Project: Billing API"]
Proj1B["Project: User Portal"]
Proj2A["Project: Partner Portal"]
App1["App: Web Client\n(OIDC)"]
App2["App: Mobile App\n(OIDC)"]
App3["App: API Service\n(SAML)"]
Roles1["Roles: admin, editor, viewer"]
Grant1["Project Grant\n→ Org B"]
Instance --> Org1
Instance --> Org2
Org1 --> Proj1A
Org1 --> Proj1B
Org2 --> Proj2A
Proj1A --> App1
Proj1A --> App2
Proj1B --> App3
Proj1A --> Roles1
Proj1A -.->|"Delegates\nadmin, editor"| Grant1
Grant1 -.-> Org2
Project Grants enable cross-organization authorization: Organization A defines roles on a project and grants a subset to Organization B. Organization B can then self-manage user assignments within the delegated roles.
Projection Rebuilding¶
Because all state is derived from events, read models can be rebuilt from scratch:
- Truncate the projection table
- The projection worker replays all events from position 0
- The read model is reconstructed to match the current event log
- This enables zero-downtime schema migrations and data repairs
Sources¶
Security¶
Authentication mechanisms, authorization model, encryption, audit capabilities, and threat considerations for Zitadel.
Authentication Mechanisms¶
Protocol Support Matrix¶
| Protocol | Type | Status | Use Case |
|---|---|---|---|
| OpenID Connect | Standard | Certified | Web and mobile SSO, API authentication |
| OAuth 2.0 | Standard | Full | API authorization, machine-to-machine |
| SAML 2.0 | Standard | Full | Enterprise SSO, legacy application integration |
| WebAuthn / FIDO2 | Standard | First-class | Passwordless authentication, passkeys |
| LDAP | Standard | Supported | Active Directory / LDAP integration |
OIDC Configuration¶
Zitadel implements a full OpenID Provider with these endpoints:
| Endpoint | Path |
|---|---|
| Discovery | /.well-known/openid-configuration |
| Authorization | /oauth/v2/authorize |
| Token | /oauth/v2/token |
| Userinfo | /oauth/v2/userinfo |
| Keys (JWKS) | /oauth/v2/keys |
| Revocation | /oauth/v2/revoke |
| Device Authorization | /oauth/v2/device |
| End Session | /oidc/v1/end_session |
Supported grant types: Authorization Code (with PKCE), Implicit, Client Credentials, Device Code, Resource Owner Password, Token Exchange.
Multi-Factor Authentication¶
| Method | Type | Implementation |
|---|---|---|
| TOTP (OTP) | Time-based | Standard RFC 6238, compatible with Google Authenticator, Authy |
| WebAuthn (U2F) | Hardware key | FIDO U2F security keys (YubiKey, and more) |
| Email OTP | Out-of-band | One-time code sent to registered email |
| SMS OTP | Out-of-band | One-time code sent via SMS |
Passkeys / Passwordless¶
Zitadel treats FIDO2/WebAuthn as a primary authentication method:
- Platform authenticators (Touch ID, Windows Hello, Android fingerprint)
- Roaming authenticators (YubiKey, Titan Key)
- Cloud-synced passkeys (iCloud Keychain, Google Password Manager)
- User verification configurable:
required,preferred,discouraged - Username + passkey login eliminates password entry entirely
External Identity Providers¶
Pre-built templates for federated authentication:
| Category | Providers |
|---|---|
| Social | Google, GitHub, GitLab, Apple |
| Enterprise | Azure AD, Generic OIDC, Generic SAML, LDAP |
| Custom | Any OIDC or SAML 2.0 compliant IdP |
Machine-to-Machine Authentication¶
| Method | Mechanism | Use Case |
|---|---|---|
| JWT Profile | Private key JWT (RSA/EC) | Service accounts with signed JWTs |
| Personal Access Tokens | Static Bearer token | Developer API access, CI/CD pipelines |
| Client Credentials | Client ID + secret | Simple M2M (less secure than JWT Profile) |
Authorization Model¶
Hierarchical RBAC¶
Zitadel implements multi-tenant RBAC across three levels:
Permission Resolution¶
Permissions follow a dual-mode pattern:
| Mode | Syntax | Meaning |
|---|---|---|
| Context-scoped | project.write:project-123 |
Permission applies to a specific resource |
| Global | project.write |
Permission applies to all resources of that type |
Resolution flow: 1. Extract user ID from Bearer token 2. Load user's memberships (org, project, IAM level) 3. Expand the roles of each membership into permissions 4. Apply context filtering for resource-scoped permissions 5. Deny if no matching permission found
Project Grants (Cross-Org Authorization)¶
Project grants enable B2B multi-tenant scenarios:
- Project owner (Organization A) defines roles on a project
- Grant creation — owner selects a subset of roles and grants them to Organization B
- Self-management — The admins of Organization B assign users to the granted roles
- Revocation — owner can revoke the grant at any time, immediately removing access
This is the core mechanism for SaaS providers offering customer-specific access control.
Membership Scopes¶
| Member Type | Scope | Example |
|---|---|---|
IAM |
Instance-wide | IAM_OWNER, IAM_ORG_MANAGER |
ORG |
Organization | ORG_OWNER, ORG_USER_MANAGER |
PROJECT |
Project | PROJECT_OWNER, PROJECT_OWNER_VIEWER |
PROJECT_GRANT |
Granted project | Self-managed by grantee org |
Encryption & Key Management¶
Data Encryption¶
| Layer | Mechanism |
|---|---|
| At rest | PostgreSQL encryption (TDE or disk-level) |
| In transit | TLS 1.2+ for all connections (API, database, LDAP) |
| Secrets | AES-256 encryption for stored secrets using master key |
| Passwords | bcrypt hashing with configurable cost |
| Master key | 32-byte key passed via --masterkey flag or ZITADEL_MASTERKEY env var |
JWT Signing Keys¶
- RSA and EC key pairs generated automatically
- Key rotation supported via the Web Key Service API (
/v2/webkeys/) - JWKS endpoint publishes public keys at
/oauth/v2/keys - Keys stored encrypted in the eventstore
Audit Trail¶
Because Zitadel uses event sourcing, every state mutation produces an immutable event:
| Capability | Detail |
|---|---|
| Complete audit log | Every create, update, delete on any entity is an event |
| Temporal queries | Reconstruct state at any point in time by replaying events |
| Export | Events API for programmatic access. Log store senders for SIEM |
| Non-repudiation | Events cannot be modified after creation (append-only) |
| Actor tracking | Each event records which user/service did the action |
SOC/SIEM Integration¶
Events can be forwarded to external security systems:
- Log store senders — configure webhook targets for real-time event streaming
- OpenTelemetry — traces and metrics for observability correlation
- GCP Error Reporting — via zitadel/sloggcp package
Threat Model¶
Attack Vectors & Mitigations¶
| Threat | Mitigation |
|---|---|
| Credential theft | Passkeys (phishing-resistant), MFA enforcement, token rotation |
| Token replay | Short-lived access tokens, refresh token rotation, token binding |
| Privilege escalation | Hierarchical RBAC, context-scoped permissions, membership auditing |
| Data exfiltration | Encryption at rest, TLS in transit, instance isolation |
| SQL injection | Parameterized queries via Go database/sql, proto-generated API |
| XSS | Login UI is Next.js with CSP headers. Admin console is Angular |
| CSRF | OIDC state parameter, PKCE enforcement for public clients |
| Brute force | Rate limiting, account lockout policies, password complexity rules |
| Phishing | FIDO2/WebAuthn as primary auth (phishing-resistant by design) |
| Insider threat | Immutable audit trail, separation of duties via role hierarchy |
Security Policies¶
Administrators can enforce policies at the instance or organization level:
| Policy | Scope | Controls |
|---|---|---|
| Login policy | Instance / Org | Allowed auth methods, MFA requirement, IdP allowlist |
| Password policy | Instance / Org | Min length, complexity, expiry, lockout |
| Privacy policy | Instance / Org | TOS URL, privacy policy URL, support email |
| Branding policy | Org | Logo, colors, font, theme for login UI |
| Domain policy | Instance | User login must match org domain, domain verification |
SCIM 2.0 User Provisioning¶
Built-in SCIM 2.0 server for enterprise user lifecycle management:
| Operation | Method | Endpoint |
|---|---|---|
| Create user | POST | /scim/v2/{orgID}/Users |
| Get user | GET | /scim/v2/{orgID}/Users/{id} |
| List users | GET | /scim/v2/{orgID}/Users |
| Replace user | PUT | /scim/v2/{orgID}/Users/{id} |
| Update user | PATCH | /scim/v2/{orgID}/Users/{id} |
| Delete user | DELETE | /scim/v2/{orgID}/Users/{id} |
Permissions are mapped to Zitadel's internal RBAC: User Write, User Read, User Delete.