Identity Patterns for AI: Securing Agents with ZITADEL
This guide provides a comprehensive framework for securing AI agents using the ZITADEL identity platform. By leveraging ZITADEL's native multi-tenancy and Service Users, you can ensure that autonomous agents operate with granular control, isolated identities, and full auditability.
Key Takeaways:
- Treat autonomous agents as ZITADEL Service Users secured by Private Key JWTs.
- Use Auth Code + PKCE for user-facing assistants to delegate permissions safely.
- Implement Token Exchange to strictly scope access for downstream MCP tools.
- Enforce least privilege using granular Project Roles instead of global admin keys.
- Isolate B2B data using native multi-tenancy and audit logs for full traceability.
Service Users
In ZITADEL, an autonomous AI Agent is treated as a Service User. Unlike a human employee who uses a username and password, a Service User is a dedicated identity for a machine.
- No Human Credentials: It does not use a password or MFA (Multi-Factor Authentication).
- Machine-Native Authentication: It authenticates using Private Key JWT (Recommended) or Client Credentials.
- API Managed: It is created and managed programmatically via the ZITADEL API or Console.
Important Note: While ZITADEL supports Personal Access Tokens (PATs) for service users, avoid using them for production AI agents. PATs are long-lived static secrets (like a password that never changes). If leaked, they pose a security risk. Instead, use Private Key JWTs, which allow for short-lived, self-rotating credentials.
Why use Service Users for AI Agents? Creating a dedicated Service User for your agent—rather than running it under a developer's account—provides critical benefits:
- Independence & Stability
- Service Users are segregated identities. They exist independently of your staff. An agent running as a Service User will keep working regardless of personnel changes.
- Auditing
- If your logs show that "John Smith" deleted a file, you don't know if John did it manually or if an AI agent using John's key did it.
- Service Users provide segregated authorization. Your audit logs will clearly distinguish between human actions and machine actions (e.g., "Event: File Deleted by FinOps_Agent_01"). This is essential for compliance and troubleshooting.
- Security & Rotation
- By using Private Key JWTs, your agent can use complex cryptographic keys that are "rotated" (changed) automatically every few days. This means you never have to hardcode a permanent "password" inside your application code, significantly reducing the risk of a leak.
Architecture Mapping
To secure an agentic workflow, map your components to standard Identity elements:
|
Component |
Identity Role |
Implementation |
|
AI Agent (MCP Client) |
Service User |
ZITADEL Service User |
|
MCP Server (The Tool) |
Resource Server |
API / Gateway |
|
Identity Provider |
IdP |
ZITADEL Instance |
- The AI Agent: This is the autonomous bot that needs to do the work. Just like a new employee, it needs a verified identity and a "badge" to operate. In ZITADEL, we call this a Service User to distinguish it from a human.
- The MCP Server (Resource Server): This is the destination holding the data or capability (like a JIRA integration or a Database). It doesn't care who the agent is, only that the agent holds a valid access badge (token) proving it is allowed to enter.
- ZITADEL (Identity Provider): This is the central authority. It checks the Agent's credentials (private key) and issues the temporary access tokens. It sits in the middle, ensuring that the Agent only gets access to the specific MCP Servers it is authorized to use.
Recommended Authentication Flows
|
Scenario |
ZITADEL Flow |
Implementation |
|
Autonomous Agent |
Private Key JWT |
The agent signs a request with its private key. ZITADEL verifies it and issues a token. (Most Secure) |
|
User Assistant |
Auth Code + PKCE |
The user logs in, and the agent receives a token representing that specific user. |
|
Chained Agents |
Token Exchange |
Agent A swaps its token for a new, specific token to talk to Agent B. |
- Autonomous Agent (Private Key JWT): Use this for backend processes, cron jobs, or data processing agents that run without any human supervision.
- It is the most secure method for machine-to-machine communication. The agent uses a cryptographic key (which can be rotated automatically) rather than a password, and the identity belongs entirely to the bot.
- User Assistant (Auth Code + PKCE): Use this for chat interfaces or desktop apps where the AI is helping a specific human user.
- You don't want the agent to have "God Mode." By using PKCE (Proof Key for Code Exchange), the agent logs in as the user. It can only see what that specific user is allowed to see (e.g., "Show my emails"), ensuring privacy and compliance.
- Chained Agents (Token Exchange): Use this when one AI agent needs to call another AI agent or a third-party tool.
- This enforces "Least Privilege." If Agent A has "Full Admin" rights but needs to ask Agent B to "Summarize a document," it shouldn't pass its Admin token to Agent B. Instead, it "exchanges" its token for a new, weaker token just for that one task. If Agent B is compromised, the attacker gets nothing valuable.
Controlling Access with Roles
Use ZITADEL's Project structure to enforce Least Privilege.
- Create a Project
Create a ZITADEL Project (e.g., AI_Operations) to define what resources the agent can access. - Define Roles
- agent.reader: Can read data but not modify it.
- agent.executor: Can trigger specific actions.
- Avoid: Generic admin roles.
- Assign Roles
Grant the Service User only the roles strictly necessary. When the agent authenticates, its Access Token will contain these roles in the urn:zitadel:iam:org:project:{id}:roles claim.
How Does Enforcement Work?
Assigning a role in ZITADEL doesn't stop the agent from trying to do something. It stops the Destination (MCP Server) from accepting the request.
When the Agent calls an API, it sends its Access Token. The API (Resource Server) must inspect the token's Claims to decide if the request is allowed.
Example: Decoded Access Token If an agent tries to restart a server, the MCP Tool checks for the agent.executor role in the token:
{ "sub": "123456789 (Service User ID)", "aud": ["https://my-mcp-server.com"], "urn:zitadel:iam:org:project:123:roles": { "agent.reader": { ... }, "agent.executor": { ... } // <--- The API checks for THIS }}
- If the claim exists: The API runs the command.
- If the claim is missing: The API rejects the request with 403 Forbidden.
This moves security logic out of your AI's code. Even if the AI tries to delete a database, the API will simply block it because the token lacks the database.delete role.
Reference: https://zitadel.com/docs/guides/manage/console/roles
Token Exchange & MCP (RFC 8693)
When an Agent (MCP Host) needs to connect to multiple, distinct MCP Servers, you should never share a single "Super Token" with everyone. Instead, use Token Exchange.
What is Token Exchange?
Token Exchange is a security pattern that allows an agent to trade a broad parent token for a new, short-lived token with restricted permissions.
- Purpose-Specific: Instead of one key that opens every door, the agent gets a specific key just for the "JIRA Room" and another just for the "Slack Room."
- Downstream Safety: It prevents downstream systems from inheriting unnecessary permissions. If you send a token to a JIRA tool, that tool should not be able to use that same token to read your Slack messages.
The Workflow:
- User Request: User asks Agent: "Check my JIRA tickets and update Slack."
- Initial Auth: User passes their main Access Token to the Agent.
- Exchange 1 (JIRA): The Agent sends the User Token to ZITADEL requesting access specifically for the JIRA MCP Server.
- ZITADEL Check: "Does this user have jira:read?" $\rightarrow$ Issues Token A (Audience: JIRA).
- Exchange 2 (Slack): The Agent sends the User Token to ZITADEL requesting access specifically for the Slack MCP Server.
- ZITADEL Check: "Does this user have slack:write?" $\rightarrow$ Issues Token B (Audience: Slack).
- Execution: The Agent connects to the MCP Servers using the specific, scoped tokens (Token A for JIRA, Token B for Slack).
Why do this?
If the JIRA MCP Server is compromised, the attacker only finds "Token A." Because Token A is strictly scoped to JIRA, the attacker cannot use it to access Slack or impersonate the user elsewhere. This keeps the "blast radius" of any breach as small as possible.
Reference: https://zitadel.com/docs/guides/integrate/token-exchange
Audit & Compliance: The Event Store Advantage
Because ZITADEL is built on the Event Sourcing pattern, it offers a level of traceability that standard Identity Providers cannot match.
Unlike traditional databases that overwrite data (storing only the current state), ZITADEL stores every change as an immutable event in an append-only log.
Why this is critical for AI Agents: AI Agents are non-deterministic—they might perform an action today that they wouldn't perform tomorrow. If an incident occurs, you need more than just a simple log; you need forensics.
- Immutable History: Every token issuance, permission change, and key rotation is a permanent entry in the ledger. You can verify exactly which key pair was used to sign a request, even if that key has since been deleted.
- Debugging: You can reconstruct the exact state of your system at any past moment.
- An agent deleted a production table last Tuesday.
- You can revert the Identity view to "Last Tuesday" to verify: "Did the agent actually have the db.admin role at that specific minute, or was it a privilege escalation exploit?"
Reference:
- https://zitadel.com/docs/concepts/features/audit-trail
- https://zitadel.com/docs/concepts/eventstore/overview
B2B Context: Native Multi-Tenancy
For B2B applications, ZITADEL excels by anchoring AI Agents within specific customer Organizations.
- Isolation: An agent for "Company A" is a Service User inside Org A. It effectively cannot access data from "Company B".
- Policy: You can enforce different policies per organization without changing your application code.
Common Mistakes to Avoid
- The "Super Token" Error
-
- Passing the user's initial Access Token to every downstream tool (JIRA, Slack, Database).
- Always use Token Exchange to issue specific, limited tokens for each step.
- Using PATs in Production
-
- PATs are static. If one leaks, it works forever until you manually notice and delete it.
- Use Private Key JWT. It requires a bit more setup code, but the tokens auto-expire, and the keys can be rotated programmatically.
- "God Mode" Permissions
-
- Assigning the IAM_OWNER or generic ADMIN role to an agent just to get it working quickly.
- Create specific roles like agent.read_only or agent.invoice_processor and assign only those.
- Shared Agents for Multiple Tenants
-
- Creating one global "AI Service User" that handles requests for all your customers.
- One Agent = One Service User. Create a distinct Service User for every customer organization to enforce strict data isolation at the Identity level.
- Logging Sensitive Context
-
- Configure your logger to redact patterns that look like JWTs or PII (strings starting with ey...) before writing to disk.