Hardening instance administrators
Instance administrators (IAM_* roles) can manage resources across every organization on an instance.
Organization administrators (ORG_* roles) manage users, projects, and policies within a single organization.
When both kinds of principals share the same organization, their authorization scope and login experience overlap in ways that can potentially lead to unexpected account takeover.
This guide describes a practical layout that keeps instance administration separate from customer or application organizations, and how to harden the login client and break-glass access.
Why use a dedicated organization for instance administrators
Create a dedicated organization reserved for instance administrators and automation—not for end users or application teams.
| Concern | When instance administrators share an application organization | With a dedicated organization for instance administrators |
|---|---|---|
| Authorization scope | Organization roles such as ORG_USER_MANAGER apply within the same organization where instance administrators may also exist. | Instance roles remain on the instance; organization roles in application organizations cannot reach instance-administrator accounts. |
| Login Behavior and Security | Login Behavior and Security can be overridden per organization. Organization Administrators may change MFA, passkeys, external IdPs, or self-registration for the organization where instance-administrator accounts live. | You define strict Organization Login Settings only for instance administrators—without coupling them to product organization settings. |
| Password and lockout settings | Password complexity, password expiry, and lockout follow the organization context of the user. | Instance-administrator accounts inherit the Organization Settings you chose for that dedicated organization, independent of customer organizations. |
| Operational clarity | Auditing who is an Administrator mixes customers and instance administrators in one user list. | Instance administrators and service accounts are easy to inventory. |
Instance default settings act as fallbacks for organizations. Organization-level settings and policies still override instance defaults for users in that organization. Placing human instance owners or shared break-glass accounts in a customer organization means that organization's administrators can shape how those users authenticate and how local settings behave.
The default organization created at setup is often used for the Management Console and internal projects. Treat it as the dedicated organization for instance administrators only if it does not host customer users and no Organization Administrators manage it on behalf of customer organizations. Otherwise, create a separate organization for instance administration.
Recommended layout
Instance
├── Dedicated organization (instance administrators only)
│ ├── Break-glass IAM_OWNER service account (PAT, offline storage)
│ ├── Login client service account (IAM_LOGIN_CLIENT only)
│ └── Optional: other instance automation service accounts
├── Customer / application organization A
│ └── End users, Organization Administrators (ORG_* only)
└── Customer / application organization B
└── End users, Organization Administrators (ORG_* only)Guidelines:
- Do not assign
ORG_USER_MANAGER,ORG_OWNER, or similar organization roles to instance administrators or service accounts in the dedicated organization unless they truly manage that organization only. - Do not add customer end users to the dedicated organization: users in customer organizations inherit that organization's login and lockout settings, and Organization Administrators can grant organization roles that blur instance-administrator and customer boundaries.
- Grant instance roles only on the instance administrator list, not as a substitute for organization roles on application resources. For instance service accounts (login client, break-glass), follow Login client (
IAM_LOGIN_CLIENT) and review critical permissions in the administrator permission matrix—for exampleiam.member.write,org.member.write,user.write,user.credential.write, anduser.passkey.write. Session permissions (session.write,session.link) are also granted by default and are required for the login UI—do not remove them without testing the full authentication flow. In self-hosted deployments, defaults can be customized viaInternalAuthZ.RolePermissionMappings.
Login settings for the dedicated organization
Configure Login Behavior and Security for the dedicated organization explicitly—do not rely on whatever Organization Login Settings happen to apply to a busy application organization.
- Open the dedicated organization in the Management Console.
- Review Login Behavior and Security (or set Instance Settings first, then override only where needed).
- For instance-administrator accounts, consider:
- Enforcing MFA for local authentication.
- Restricting external IdPs if instance administrators should only use credentials you control.
- Disabling self-registration if the organization is not meant for public signup.
- Tight lockout and password complexity aligned with your instance-administrator baseline.
- A default redirect URI that does not send instance administrators to a customer application by mistake.
Instance-level and organization-level identity providers are separate from the Organization Login Settings that control whether external login is allowed and which providers are linked for a given organization.
An instance IdP can be used by users in an organization only when the effective Organization Login Settings for that organization allow external IdPs (External IDP allowed) and the provider is linked to those settings—or when the organization inherits Instance Settings, which includes the default IdP links.
Organizations with custom Organization Login Settings must enable external IdPs themselves and explicitly add instance or organization IdPs to their provider list; see Let users login with preferred identity provider.
Document which IdPs instance administrators may use in the dedicated organization.
More context on settings and policy inheritance: Settings and policies.
Login client (IAM_LOGIN_CLIENT)
The self-hosted Login UI authenticates to ZITADEL with a service account that holds the Instance Login Client role (IAM_LOGIN_CLIENT).
Hardening steps:
- Create the login client user in the dedicated organization for instance administrators, not in a customer or application organization.
- Use a dedicated service account per login deployment; do not reuse administrator or application service account PATs.
- Grant only
IAM_LOGIN_CLIENTon the instance unless you have a documented exception. - Store the PAT in a secrets manager; rotate when the login stack is redeployed or team access changes.
- Do not use the login client PAT for management automation, CI, or customer integrations.
Setup reference: Create a Login Client and Adopt Login V2.
The default IAM_LOGIN_CLIENT permission set is broad because the login UI finalizes auth requests, manages users during registration, and may synchronize instance roles when a ZITADEL identity provider grants instance roles from token claims.
Review the administrator permission matrix before customizing roles in self-hosted deployments.
Break-glass instance owner access
Before changing login routes, features, or Instance Settings, keep a recovery path that does not depend on the interactive login UI.
Avoid lockout
Create a service account in the dedicated organization with the Instance Owner role (IAM_OWNER) and a PAT.
Store the PAT offline according to your incident-access procedures.
If the login UI or Management Console becomes unreachable, you can still call the Admin API to revert settings.
- Prefer a service account for break-glass access; limit human
IAM_OWNERaccounts. - Enroll multiple MFA methods for any human instance owners.
- Invite a second trusted administrator on the instance so access is not tied to a single person.
Self-hosted custom role mappings
If you override InternalAuthZ.RolePermissionMappings in runtime configuration:
- Diff your mappings against the shipped defaults in
cmd/defaults.yaml. - Remove permissions that your automation does not need.
- If you use ZITADEL-as-IdP instance role sync, the login client still needs permission to manage instance administrators for users logging in through that IdP; do not remove those capabilities without testing the support or federation flow you rely on.
Periodic review
Review instance administrators on a schedule and after major organization or login changes.
List instance administrators (Internal Permission Service v2):
curl -sS -X POST \
"https://${CUSTOM_DOMAIN}/zitadel.internal_permission.v2.InternalPermissionService/ListAdministrators" \
-H "Authorization: Bearer ${IAM_OWNER_PAT}" \
-H "Content-Type: application/json" \
-H "Connect-Protocol-Version: 1" \
-d '{
"pagination": { "offset": 0, "limit": 100, "asc": true },
"filters": [
{ "resource": { "instance": true } }
]
}'The response includes each user's organizationId and granted roles, which helps verify placement in the dedicated organization.
The Admin API POST /admin/v1/members/_search endpoint is deprecated; prefer ListAdministrators above.
Questions to answer:
- Does every
IAM_*holder belong to the dedicated organization for instance administrators (or a documented exception)? - Does the login client service account hold only
IAM_LOGIN_CLIENT? - Are there human
IAM_OWNERaccounts in application organizations? - Did any instance administrator change since the last review? Use the Event API or stream audit logs to external systems for change history.
Detect grants with Actions v2
Use Actions v2 to get notified when instance administrators change, instead of relying only on scheduled API reviews. Each webhook receives the stored event, including the affected user and role list.
Prerequisites
- An access token for a user with
IAM_OWNER(or another role that can manage Actions targets and executions). - A reachable HTTPS endpoint (or Webhook.site for testing).
- Familiarity with the Actions event guide and payload format.
1. Create a webhook target
curl -sS -X POST "https://${CUSTOM_DOMAIN}/v2/actions/targets" \
-H "Authorization: Bearer ${IAM_OWNER_PAT}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "instance-administrator-monitor",
"restWebhook": { "interruptOnError": false },
"endpoint": "https://your-ops-endpoint.example/webhook/zitadel-instance-members",
"timeout": "10s"
}'Save the returned target ID. Verify inbound requests with the target signing key in production.
2. Register executions for instance member events
Create one execution per event type you want to monitor:
| Event | When it fires |
|---|---|
instance.member.added | A user is granted instance administrator roles for the first time |
instance.member.changed | Instance administrator roles for a user are updated |
instance.member.removed | Instance administrator roles are removed |
Example for new grants:
curl -sS -X PUT "https://${CUSTOM_DOMAIN}/v2/actions/executions" \
-H "Authorization: Bearer ${IAM_OWNER_PAT}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"condition": {
"event": {
"event": "instance.member.added"
}
},
"targets": ["<TARGET_ID>"]
}'Repeat for instance.member.changed and instance.member.removed if you want alerts on updates and removals as well.
3. Interpret the webhook payload
The webhook body follows the Actions event payload. For instance member events, relevant fields include:
event_type— e.g.instance.member.addedaggregateType—instanceuserID— actor who performed the change (when available)event_payload— JSON with at leastuserId(administrator) androles(granted instance roles)
Example decoded event_payload for a grant:
{
"userId": "382586560962387915",
"roles": ["IAM_LOGIN_CLIENT", "IAM_OWNER"]
}4. What to flag in your handler
Combine real-time events with the periodic ListAdministrators review above:
- Any
IAM_OWNERorIAM_USER_MANAGERgrant outside your dedicated organization for instance administrators (compareuserIdto your allow-list or resolve organization via API). - Login client service account holding roles other than
IAM_LOGIN_CLIENT. instance.member.changedwhererolesincludes high-privilegeIAM_*roles you did not expect.- Grants performed by users or service accounts that are not on your approved administrator list (
userIDin the outer payload).
Post alerts to your operations channel, SIEM, or ticket system. On ZITADEL Cloud, Actions v2 webhooks are supported; self-hosted installations can use the same API.
See also: Actions v2 examples and streaming audit logs.
Monitor organization administrators in the dedicated organization
Instance IAM_* roles are not the only risk in a mixed layout: organization roles inside the dedicated organization (ORG_USER_MANAGER, ORG_OWNER, …) apply to users in that same organization, including break-glass accounts and service accounts.
Monitor Organization Administrators for the dedicated organization alongside instance administrators.
Periodic review (API)
List Organization Administrators with ListAdministrators, filtered to your dedicated organization:
curl -sS -X POST \
"https://${CUSTOM_DOMAIN}/zitadel.internal_permission.v2.InternalPermissionService/ListAdministrators" \
-H "Authorization: Bearer ${IAM_OWNER_PAT}" \
-H "Content-Type: application/json" \
-H "Connect-Protocol-Version: 1" \
-d '{
"pagination": { "offset": 0, "limit": 100, "asc": true },
"filters": [
{ "resource": { "organizationId": "${PLATFORM_ORGANIZATION_ID}" } }
]
}'Questions to answer:
- Are there
ORG_USER_MANAGERorORG_OWNERgrants on the dedicated organization you did not expect? - Do any instance administrators or service accounts hold Organization Administrator roles in the dedicated organization without an operational reason?
- Are customer Organization Administrators accidentally granted roles in the dedicated organization?
In the Management Console, open the dedicated organization and review ADMINISTRATORS in the organization details panel (same UI pattern as ZITADEL Administrators).
Organization member events (Actions v2)
| Event | When it fires |
|---|---|
org.member.added | A user is granted organization administrator roles |
org.member.changed | Organization administrator roles are updated |
org.member.removed | Organization administrator roles are removed |
Use the same target setup as for instance member events, then add executions:
curl -sS -X PUT "https://${CUSTOM_DOMAIN}/v2/actions/executions" \
-H "Authorization: Bearer ${IAM_OWNER_PAT}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"condition": {
"event": {
"event": "org.member.added"
}
},
"targets": ["<TARGET_ID>"]
}'Repeat for org.member.changed and org.member.removed as needed.
The webhook payload uses the same event structure. For org.member.* events, aggregateType is org and aggregateID is the organization ID. Filter in your handler so you only alert when aggregateID equals your ${PLATFORM_ORGANIZATION_ID}. The event_payload contains userId and roles (for example ORG_USER_MANAGER, ORG_OWNER).
Flag grants such as unexpected ORG_USER_MANAGER on the dedicated organization, or any Organization Administrator role on users that should be service accounts only.
Review changes with the Event API
If you already pull audit data into a SIEM or batch job, the Event API is an alternative to Actions v2 webhooks.
Poll on a schedule (for example every few minutes) and process new rows since the last sequence.
Requires IAM_OWNER or IAM_OWNER_VIEWER.
Instance administrator events
curl -sS -X POST "https://${CUSTOM_DOMAIN}/admin/v1/events/_search" \
-H "Authorization: Bearer ${IAM_OWNER_PAT}" \
-H "Content-Type: application/json" \
-d '{
"asc": true,
"limit": 1000,
"sequence": 0,
"aggregate_types": ["instance"],
"event_types": [
"instance.member.added",
"instance.member.changed",
"instance.member.removed"
]
}'Increase sequence on subsequent runs to the highest sequence returned previously so you only fetch new events.
Dedicated organization administrator events
Filter by your dedicated organization ID as resource_owner and restrict to organization member events:
curl -sS -X POST "https://${CUSTOM_DOMAIN}/admin/v1/events/_search" \
-H "Authorization: Bearer ${IAM_OWNER_PAT}" \
-H "Content-Type: application/json" \
-d '{
"asc": true,
"limit": 1000,
"sequence": 0,
"resource_owner": "${PLATFORM_ORGANIZATION_ID}",
"aggregate_types": ["org"],
"event_types": [
"org.member.added",
"org.member.changed",
"org.member.removed"
]
}'Parse each event payload for userId and roles, then apply the same allow-list checks as in the Actions handlers above.
Actions v2 vs Event API: Actions push events to your endpoint in near real time with minimal setup. The Event API fits centralized log pipelines, replay, and environments where outbound webhooks are undesirable. You can use both.
Checklist
- Dedicated organization for instance administrators exists; customer users are not members.
- Login Behavior and Security for the dedicated organization enforces your instance-administrator authentication baseline (MFA, IdPs, lockout).
- Login client service account lives in the dedicated organization with a dedicated PAT.
- Break-glass
IAM_OWNERPAT is stored offline and tested periodically. - Custom
RolePermissionMappings(if any) are reviewed against least privilege. - Organization Administrators (
ORG_*) on the dedicated organization are reviewed and monitored (org.member.*events or Event API). - Instance administrators are reviewed regularly and Actions v2 monitors
instance.member.*events (or changes are pulled via the Event API / SIEM).
Related guides
Was this page helpful?