Data as of Aug 25, 2026 · Based on 38 AI responses from ChatGPT Search and Google AI Mode · See how Parse measures this
Your brand can be here too.
The safest mental model is: an AI agent should have its own workload identity, and every tool call should be authorized as a specific agent + user/delegator + action + resource—not as a generic application API key.
A good architecture looks like this:
User
│
│ delegates task
▼
Agent Runtime
│
│ agent identity + delegated identity
▼
Policy / Authorization Layer
│
│ short-lived, scoped token
▼
Tool Gateway / MCP Server
│
├── GitHub
├── Database
├── Salesforce
└── Internal APIs
Don't identify agents with a shared API key such as:
Authorization: Bearer COMPANY_AGENT_KEY
Instead, register each agent/workload as a principal:
agent_id = agent:customer-support-v3
The identity should be cryptographically verifiable, ideally through your existing workload-identity system, OIDC, or another identity provider.
This lets you answer questions like:
For autonomous backends, OAuth's client-credentials pattern is appropriate when the agent is acting as an application rather than a human. MCP's authorization spec explicitly distinguishes this case from authorization-code flows used when acting on behalf of a user.
This is one of the most important design decisions.
Suppose Alice tells an agent:
"Find my invoices and summarize the overdue ones." You ideally want the downstream authorization context to represent:
agent = invoice-agent
subject = alice
action = invoices.read
resource = alice/invoices
rather than simply:
agent = invoice-agent
Otherwise, you end up giving the agent a broad service identity that can potentially access everybody's invoices.
Think of authorization as:
"Agent X is allowed to perform Y on behalf of user Z against resource R."
Don't give the agent a long-lived credential with 50 permissions.
Instead, have it obtain a token for the particular tool/resource it needs:
aud = https://billing.example.com
scope = invoices.read
sub = alice
agent_id = invoice-agent
exp = 2026-08-27T03:20:00Z
The important pieces are:
aud) — which service may accept the token.Current MCP authorization guidance specifically requires resource/audience binding and says the MCP server must reject tokens that weren't issued for it. It also recommends short-lived access tokens.
A tool should not simply be:
database.query = allowed
Prefer something like:
database:
invoices:
read: allowed
write: denied
customers:
read:
condition: subject == authenticated_user
And potentially constrain the arguments:
invoice.search(
customer_id = authenticated_user.id
)
This is important because an LLM can be authorized to invoke a tool while still being prevented from using that tool against arbitrary resources.
In other words:
Authorize the operation, not merely the function.
Don't rely on the model to respect permissions.
The model might receive:
Available tool:
delete_customer(customer_id)
Your enforcement layer should independently decide:
agent = support-agent
user = alice
operation = delete_customer
resource = customer/123
→ DENY
The LLM's tool-selection decision is not an authorization decision.
A useful policy model is:
allow(
principal = agent,
delegated_user = user,
action = tool.action,
resource = tool.resource,
context = request_context
)
OPA/Rego, Cedar, Zanzibar-style authorization, or a conventional RBAC/ABAC engine can all implement variants of this.
If you're building around Model Context Protocol, its current authorization model maps nicely onto this architecture.
An MCP client obtains an OAuth access token and sends it to the MCP server. The MCP server validates that token and, importantly, verifies that it was issued for that MCP server as the intended resource.
The MCP server should then perform its own authorization before executing the tool.
For example:
Agent
│
│ OAuth token
▼
MCP server
│
├─ validate signature
├─ validate issuer
├─ validate expiration
├─ validate audience
├─ validate scopes
├─ evaluate agent/user policy
│
▼
Tool execution
MCP also explicitly warns against simply passing the incoming token through to downstream services. If the MCP server calls another API, it should obtain/use a separate token intended for that downstream API. This prevents token confusion and confused-deputy problems.
Imagine:
User → Agent → MCP server → Salesforce
Don't necessarily do:
User token
↓
Agent
↓
MCP
↓
Salesforce
Instead:
User delegates
↓
Agent identity
↓
MCP authorization
↓
short-lived Salesforce-specific credential
↓
Salesforce
That gives each hop an independently verifiable security boundary.
The resulting tokens might conceptually look like:
Token A
aud = mcp://sales-tools
scope = contacts.read
Token B
aud = https://salesforce.example
scope = contacts.read
The MCP server never forwards Token A to Salesforce.
For read-only operations, you can often make authorization automatic.
For:
use an explicit approval boundary:
Agent proposes:
delete customer 123
Policy:
requires human approval
User:
APPROVE
Authorization service:
issue one-time token
Tool:
execute
This is substantially safer than giving the agent a permanent delete:* permission.
Every tool invocation should produce an audit record roughly like:
{
"agent": "customer-support-v3",
"user": "alice",
"tool": "crm.get_customer",
"resource": "customer/123",
"decision": "allow",
"policy": "customer-read-v4",
"timestamp": "...",
"request_id": "..."
}
This gives you non-repudiable attribution:
Alice asked agent X to do Y, agent X requested Z, policy P authorized it, and tool T executed it. That's much more useful than logs saying
api-key-7f92 called CRM.
For a production system, I'd converge on:
┌────────────────────┐
│ Identity Provider │
└─────────┬──────────┘
│
agent identity / user
│
▼
┌────────┐ ┌───────────────┐
│ User │────────────▶│ Agent Runtime │
└────────┘ └───────┬───────┘
│
request tool
│
▼
┌──────────────┐
│ AuthZ / PDP │
│ │
│ agent │
│ user │
│ scope │
│ resource │
│ context │
└──────┬───────┘
│ allow
short-lived token
│
▼
┌──────────────┐
│ Tool Gateway │
│ / MCP Server │
└──────┬───────┘
│
separate downstream
credentials
│
┌─────────┴─────────┐
▼ ▼
GitHub Database
If you're implementing this specifically with MCP + OAuth/OIDC, the current MCP authorization specification is a particularly useful starting point; it defines resource-server authorization, discovery, scopes, audience binding, PKCE, and token-handling requirements.
use an explicit approval boundary:
Agent proposes:
delete customer 123
Policy:
requires human approval
User:
APPROVE
Authorization service:
issue one-time token
Tool:
execute
This is substantially safer than giving the agent a permanent delete:* permission.
Every tool invocation should produce an audit record roughly like:
{
"agent": "customer-support-v3",
"user": "alice",
"tool": "crm.get_customer",
"resource": "customer/123",
"decision": "allow",
"policy": "customer-read-v4",
"timestamp": "...",
"request_id": "..."
}
This gives you non-repudiable attribution:
Alice asked agent X to do Y, agent X requested Z, policy P authorized it, and tool T executed it. That's much more useful than logs saying
api-key-7f92 called CRM.
For a production system, I'd converge on:
┌────────────────────┐
│ Identity Provider │
└─────────┬──────────┘
│
agent identity / user
│
▼
┌────────┐ ┌───────────────┐
│ User │────────────▶│ Agent Runtime │
└────────┘ └───────┬───────┘
│
request tool
│
▼
┌──────────────┐
│ AuthZ / PDP │
│ │
│ agent │
│ user │
│ scope │
│ resource │
│ context │
└──────┬───────┘
│ allow
short-lived token
│
▼
┌──────────────┐
│ Tool Gateway │
│ / MCP Server │
└──────┬───────┘
│
separate downstream
credentials
│
┌─────────┴─────────┐
▼ ▼
GitHub Database
"Agent X is allowed to perform Y on behalf of user Z against resource R."
Don't give the agent a long-lived credential with 50 permissions.
Giving AI agents scoped identity and authentication to access external tools requires moving away from static master API keys and toward delegated, least-privilege, and cryptographically verifiable token patterns.
The industry standard approach converges on modern OAuth 2.1 / Token Exchange frameworks paired with emerging structures like the Model Context Protocol (MCP).
Step 1: Establish Dual Identity (User + Agent Chaining)
When an agent acts, downstream tools need to know who requested the action and which agent is executing it. Avoid full user impersonation (where the agent completely steals or mirrors a user context and bypasses checks).
obo or user subject) from the executing agent (sub ). This allows tool APIs to audit the chain of custody and enforce granular access control.Step 2: Implement Scoped Authorization & Least Privilege
Never hand an agent broad permissions. Tools should only receive tokens restricted to exact operational parameters.
calendar:read instead of full-access).Step 3: Handle Dynamic Client Registration (DCR)
AI environments are often dynamic—agents spin up task-specific micro-agents or sub-routines on the fly. Static client IDs and secrets break this workflow.
Step 4: Enforce Token Lifetime and Revocation Controls
If you'd like, tell me:
I can give you a step-by-step implementation architecture or a sample JWT claim schema tailored to your stack.
To give AI agents a scoped identity and secure authentication to access tools, you must isolate the Large Language Model (LLM) from managing credentials directly and utilize an intermediary architecture that issues short-lived, highly restricted capability tokens.
The industry standard for agentic security relies on a decoupled architecture where the agent acts as an authenticated workload, executing tools via standard protocols like the Model Context Protocol (MCP) or OAuth 2.1.
🛠️ The 4-Layer Architecture for Agentic Auth
To prevent an agent from hijacking or misusing tools, implement this architectural pattern:
[ Human User ] ──(Auth Code + PKCE)──> [ Agent App Layer ]
│
(Token Exchange RFC 8693)
▼
[ External Tool ] <──(Short-lived JWT)── [ Secure Intermediary / Vault ]
Never allow AI agents to share standard human credentials or static developer API keys.
Rule : The AI model should never see, hold, or pass raw API keys or access tokens.
Because an LLM's outputs are non-deterministic, you must strictly bound its capabilities.
mcp:slack:read scope. Never bundle it with broad write permissions like mcp:slack:delete . Use Fine-Grained Access Control (FGA) to restrict access at the document level for RAG systems.For destructive actions or financial transactions (e.g., transferring funds, deleting databases), implement Client-Initiated Backchannel Authentication (CIBA).
📝 Step-by-Step Implementation Checklist
To help tailor this architecture to your system, let me know:
The cleanest pattern is to treat an AI agent as a distinct non-human principal, then give it short-lived, least-privilege credentials at the moment it invokes a tool—not a broad API key that effectively becomes its permanent identity. Current guidance from Microsoft and AWS converges on this model.
User
│
│ authenticated session
▼
Agent runtime
│
│ agent identity + user context
▼
Authorization / policy gateway
│
│ issue short-lived, down-scoped token
▼
Tool / API
│
└── resource-level authorization
There are three separate questions:
Don't let the LLM itself answer #3. Your deterministic policy layer should make that decision. Microsoft explicitly recommends keeping application, identity, and policy checks around tool execution rather than allowing the agent to decide its own authorization.
For example:
agent_id = "customer-support-agent"
tenant = "acme"
Authenticate that identity using something like:
Avoid:
ALL_AGENTS_API_KEY = "..."
A separate identity lets you revoke one agent, restrict it independently, and audit exactly which agent performed an action.
If Alice asks:
"Find my invoices and email me the latest one." the downstream system should be able to establish:
user = Alice
agent = billing-agent
tool = email
action = send
The agent should not simply receive Alice's long-lived credentials.
Instead, use OAuth delegation/token exchange to obtain a new token for the specific downstream service. OAuth 2.0 Token Exchange (RFC 8693) is designed for exactly this sort of delegation and can produce a token with a different audience and narrower scopes.
Conceptually:
Alice's session
+
billing-agent identity
+
requested scope
+
target audience
│
▼
Authorization Server
│
▼
short-lived token:
sub = Alice
actor = billing-agent
aud = email-api
scope = email.send
That gives the API enough information to distinguish "Alice did this" from "the billing agent did this on Alice's behalf."
Don't give an agent:
github.*
Give it capabilities such as:
github.repo.read
github.issue.create
And ideally distinguish:
read_customer
update_customer
delete_customer
rather than one generic customer_admin.
Even better, authorization can incorporate the resource and arguments:
ALLOW
agent: support-agent
user: alice
tool: customer.update
customer_id: alice
fields: ["phone"]
DENY
agent: support-agent
tool: customer.delete
This is important because OAuth scopes alone are often too coarse for agentic systems. The actual policy decision should happen when the tool is invoked.
A particularly useful pattern is:
Agent wants to call Tool X
│
▼
Policy engine:
Is this agent allowed?
Is this user allowed?
Is this tool allowed?
Is this resource allowed?
Are these parameters allowed?
│
YES
▼
Short-lived token
│
▼
Tool X
The token might live for minutes rather than days and be restricted to:
audience = tool-x
scope = records.read
resource = /customers/123
Token exchange is particularly useful here because the downstream token can be narrower than the credential the agent started with.
For an MCP-based system, for example:
┌───────────────┐
│ Policy Engine │
└───────┬───────┘
│
Agent ──► MCP Gateway ──────┼────► GitHub
├────► Salesforce
├────► Database
└────► Email
The gateway can enforce things such as:
agent: support-agent
tools:
customer.read:
resources: ["customers/*"]
email.send:
allowed_domains: ["acme.com"]
customer.delete:
require_approval: true
This is safer than relying on the model's tool descriptions to enforce security.
For actions such as:
make the policy something like:
agent → request action
→ policy check
→ human approval
→ short-lived credential
→ tool
This creates a capability boundary, rather than giving the model unrestricted authority.
Every tool invocation should answer:
Who initiated it?
Which agent acted?
Which tool?
Which resource?
What operation?
What authorization decision?
Which policy version?
When?
Was human approval required?
For example:
{
"user": "alice",
"agent": "support-agent",
"tool": "customer.update",
"resource": "customer/123",
"action": "update_phone",
"decision": "allow",
"policy": "support-v17",
"timestamp": "..."
}
AWS similarly recommends propagating user context as signed claims while keeping the agent identity distinct, and maintaining audit trails that unambiguously identify both actors.
I'd boil the whole thing down to:
Authenticate the agent, preserve the user's identity when acting on their behalf, authorize every tool call independently, and issue the narrowest short-lived credential possible. In other words:
Identity ≠ Permission ≠ Intent
An agent's identity tells you who it is. Its delegated identity tells you on whose behalf it acts. The policy engine determines what this particular invocation is allowed to do.
That separation gives you a much smaller blast radius if an agent is manipulated, compromised, or simply makes a bad decision. Recent agent-security guidance is increasingly emphasizing exactly this distinction.
read_customer
update_customer
delete_customer
rather than one generic customer_admin.
Even better, authorization can incorporate the resource and arguments:
ALLOW
agent: support-agent
user: alice
tool: customer.update
customer_id: alice
fields: ["phone"]
DENY
agent: support-agent
tool: customer.delete
This is important because OAuth scopes alone are often too coarse for agentic systems. The actual policy decision should happen when the tool is invoked.
A particularly useful pattern is:
Agent wants to call Tool X
│
▼
Policy engine:
Is this agent allowed?
Is this user allowed?
Is this tool allowed?
Is this resource allowed?
Are these parameters allowed?
│
YES
▼
Short-lived token
│
▼
Tool X
The token might live for minutes rather than days and be restricted to:
audience = tool-x
scope = records.read
resource = /customers/123
Token exchange is particularly useful here because the downstream token can be narrower than the credential the agent started with.
For an MCP-based system, for example:
┌───────────────┐
│ Policy Engine │
└───────┬───────┘
│
Agent ──► MCP Gateway ──────┼────► GitHub
├────► Salesforce
├────► Database
└────► Email
The gateway can enforce things such as:
agent: support-agent
tools:
customer.read:
resources: ["customers/*"]
email.send:
allowed_domains: ["acme.com"]
customer.delete:
require_approval: true
This is safer than relying on the model's tool descriptions to enforce security.
For actions such as:
make the policy something like:
agent → request action
→ policy check
→ human approval
→ short-lived credential
→ tool
This creates a capability boundary, rather than giving the model unrestricted authority.
Every tool invocation should answer:
Who initiated it?
Which agent acted?
Which tool?
Which resource?
What operation?
What authorization decision?
Which policy version?
When?
Was human approval required?
For example:
{
"user": "alice",
"agent": "support-agent",
"tool": "customer.update",
"resource": "customer/123",
"action": "update_phone",
"decision": "allow",
"policy": "support-v17",
"timestamp": "..."
}
AWS similarly recommends propagating user context as signed claims while keeping the agent identity distinct, and maintaining audit trails that unambiguously identify both actors.
The cleanest pattern is to treat an AI agent like a workload with its own identity, not like a user holding a bag of API keys.
A useful architecture is:
Human identity → Agent identity → Scoped token → Tool/resource
Create a distinct identity for each agent or agent class:
sales-agentsupport-agentfinance-agentdeploy-agentAvoid giving every agent the same service account or API key. Your identity system should be able to answer:
“Which agent made this call, on whose behalf, and in which session?”
Cloud IAM/workload identity systems are increasingly designed around this model; for example, AWS explicitly recommends restricting which workload identities can retrieve which credential providers.
Authentication answers who is this?
Authorization answers what may it do?
For user-driven agents, a good model is:
User authenticates with IdP
↓
User authorizes agent
↓
Authorization server issues scoped token
↓
Agent calls tool
↓
Tool validates token + policy
OAuth/OIDC is a natural fit. OIDC establishes the user's identity, while OAuth provides delegated, scoped access. AWS's current agent identity architecture uses this distinction and binds scoped tokens to individual user sessions.
For autonomous agents that aren't acting for a human, use a workload/service identity and something analogous to OAuth client credentials, rather than impersonating a human. MCP's authorization specification explicitly distinguishes authorization-code flows for user delegation from client-credentials flows for application-to-application access.
Don't give an agent:
github:*
database:*
email:*
Prefer permissions such as:
github:issues:read
github:issues:comment
orders:read
orders:update
And ideally constrain the resource, not merely the operation:
orders:read
tenant=acme
region=us-east
So the authorization decision becomes something like:
ALLOW if:
agent == "support-agent"
AND user == current_user
AND tool == "orders.get"
AND action == "read"
AND tenant == user's tenant
This is much stronger than relying on the agent to behave correctly.
A token issued to call your GitHub tool shouldn't be reusable against your database tool.
For example:
access_token
aud = https://tools.example.com/github
sub = agent:support-agent
actor = user:123
scope = github:issues:read
session = abc123
exp = 10 minutes
The tool validates:
Current MCP authorization guidance specifically requires resource/audience binding and says MCP servers must reject tokens that weren't issued for that resource. It also recommends least-privilege scopes.
This is important.
Don't rely on:
Agent → LLM prompt → "please don't delete anything"
Instead:
Agent
↓
Tool gateway
↓
Identity + policy enforcement
↓
Actual API
The gateway/tool server should enforce authorization every time the tool is invoked.
That way, even if the model is manipulated by prompt injection, it cannot turn:
orders.read
into:
orders.delete
unless the identity actually has that permission.
If your tools are exposed through Model Context Protocol (MCP), its HTTP authorization model is built around OAuth 2.1, protected-resource metadata, authorization-server discovery, resource indicators, and scoped access tokens.
You can also authorize at different granularities. MCP tooling can protect an entire server or require authorization only for particular tools.
A practical architecture is therefore:
┌──────────────┐
│ User │
└──────┬───────┘
│ OIDC
▼
┌──────────────┐
│ Auth Server │
└──────┬───────┘
│ scoped token
▼
┌─────────────┐ ┌──────────────┐
│ AI Agent │────▶│ Tool Gateway │
│ identity: A │ │ / MCP Server │
└─────────────┘ └──────┬───────┘
│ policy
▼
┌──────────────┐
│ Tool / API │
└──────────────┘
The agent should ideally never see long-lived secrets.
Instead of:
Agent → "Here's the Salesforce API key"
use:
Agent → credential broker → short-lived token → Salesforce
This gives you revocation, rotation, auditing, and much smaller blast radius.
For every tool invocation, record something equivalent to:
{
"user": "user-123",
"agent": "support-agent",
"session": "sess-456",
"tool": "orders.update",
"resource": "order-789",
"scope": "orders:update",
"decision": "allow",
"timestamp": "..."
}
That creates an auditable answer to:
Who caused this action?
rather than merely:
Which API key was used?
I'd design the system around three identities:
User identity — who requested the work?
Agent/workload identity — which autonomous component performed it?
Resource identity — what exactly is it allowed to touch?
Then use short-lived, scoped, audience-bound credentials between them.
If you're building this today, a strong default stack is OIDC + OAuth 2.1 + workload identity/IAM + a policy engine + MCP for tool transport, with the tool server enforcing authorization rather than trusting the model.
Giving AI agents scoped identity and authentication to access tools requires a zero-trust, least-privilege architecture . Because AI agents can dynamically decide which tools to call, standard user login flows don't work well. Instead, you need a system where the agent acts under a tightly controlled, delegative identity.
Here is the step-by-step approach to implementing scoped identity and authentication for AI agents:
Do not let an AI agent run under the master credentials of your application or a generic service account.
If the AI agent acts on behalf of a human user, the agent should not inherit the user's full permissions.
Define precise, granular permissions rather than broad tool access.
database:write access, scope it to specific operations, such as database:update:user_preferences where it can only modify specific fields.Treat every tool as an independent microservice that validates the caller.
If you'd like, let me know:
I can provide a more specific architectural pattern or code snippet for your stack.
Giving AI agents scoped identity and authentication to access tools is best approached like giving a service account to a human employee: each agent gets a verifiable identity, short-lived credentials, and narrowly defined permissions. Avoid giving agents shared API keys or broad admin tokens.
A typical architecture looks like this:
User
|
v
Agent runtime
|
| (agent identity + policy)
v
Authorization layer
|
| (scoped token)
v
Tool/API
Create an identity for the agent instance or agent class:
Examples:
agent://customer-support/prod/refund-agent
agent://data-team/staging/report-generator
agent://engineering/code-review-agent
The identity should answer:
For infrastructure-level workload identity, systems such as SPIFFE provide cryptographic workload identities (SVIDs) that workloads can use for authentication.
Authentication answers:
"Who is calling?"
Authorization answers:
"What may this caller do?"
Do not encode permissions only in the agent prompt or application code. Put them in a policy system.
Example policy:
principal:
id: agent://support/refund-agent
permissions:
- tool: payments.refund
action:
- create
constraints:
max_amount: 100
currency:
- USD
- tool: customer.lookup
action:
- read
deny:
- customer.delete
- database.admin
The tool server enforces this policy.
Avoid:
AGENT_API_KEY=super-secret-never-expires
Prefer:
Agent identity
|
v
Identity provider
|
v
10-minute access token
|
v
Tool API
Good patterns:
Token claims might look like:
{
"sub": "agent://support/refund-agent",
"aud": "payments-api",
"scope": [
"refund:create"
],
"exp": 1780000000
}
The tool checks:
Do not trust the orchestrator alone.
Weak:
Agent -> Orchestrator -> Tool
"trust me, this is the refund agent"
Better:
Agent
|
| signed identity token
v
Tool
|
| policy decision
v
Allow / deny
The tool should be able to independently verify the caller.
Instead of:
"This agent can access the customer database"
give:
"This agent has this specific capability"
Example:
Capability:
tool: CRM
operation: read_customer
customer_scope: assigned_tickets_only
expires: 15 minutes
Capabilities reduce blast radius.
There are usually two identities:
Agent identity
User identity
Example:
Agent:
refund-agent-v2
User:
[email protected]
Request:
Refund order #1234
The downstream tool may require both:
agent_id = refund-agent-v2
user_id = [email protected]
permission = refund:create
This prevents an agent from silently acting as a different user.
A good tool gateway checks:
| Check | Example |
|---|---|
| Identity | Is this the approved agent? |
| Environment | Production or staging? |
| Scope | Does it have this permission? |
| Context | Is the customer in scope? |
| Risk | Does this need approval? |
| Audit | Should this action be logged? |
Example:
refund.create
|
+-- identity: refund-agent-v2 ✓
+-- scope: refund:create ✓
+-- amount < $100 ✓
+-- customer ownership ✓
+-- audit log ✓
Agents should assume credentials are temporary.
Have:
If an agent is compromised, you want:
revoke identity
|
v
all future tool calls fail
not:
search database for leaked API keys
A common production setup:
Identity
Authorization
Secrets
Tool gateway
A mature flow:
Agent starts
|
v
Gets workload identity
|
v
Requests scoped tool token
|
v
Calls tool
|
v
Tool verifies:
identity
scope
context
expiration
|
v
Execute + audit
The key design principle is: agents should never have "access"; they should have temporary, verifiable capabilities to perform specific actions.
Giving AI agents scoped identity and authentication to access tools requires moving away from dangerous anti-patterns like hardcoded API keys or shared master service accounts . The modern production standard relies on a two-identity delegation model —combining the agent's unique workload identity with the user's constrained authorization context—enforced via standards like OAuth 2.1 and the Model Context Protocol (MCP).
Follow these core steps to implement scoped, secure tool access for your AI agents:
If you'd like to dive deeper, let me know:
I can provide a tailored architecture blueprint or code pattern for your setup.
The cleanest pattern is to treat an AI agent as a non-human workload identity, then put an authorization layer between the agent and every tool. The model should request an action; it should never be the component that decides whether that action is authorized.
Human / application
│
│ delegated authority
▼
┌──────────────────┐
│ Agent runtime │
│ Agent ID: A-123 │
└────────┬─────────┘
│ short-lived token
▼
┌─────────────────────────────┐
│ Agent authorization gateway │
│ │
│ identity + scopes │
│ tool allowlist │
│ argument-level policy │
│ approval policy │
│ audit logging │
└────────┬────────────────────┘
│
┌────┴─────┬─────────┐
▼ ▼ ▼
GitHub CRM Database
The important principle is identity ≠ permission.
An agent might have identity agent:invoice-bot-42, but its permission could be:
tools:
invoices.read
resources:
invoices:tenant_17/*
actions:
GET
limits:
max_results=100
It should not automatically receive everything the underlying service account can do.
Use a workload/service identity rather than an API key shared by multiple agents.
For example:
agent_id = agent:customer-support
owner = team:support
environment = production
Ideally the credential is short-lived and issued through your existing identity infrastructure—OIDC/workload identity, OAuth, cloud IAM, etc.
Microsoft's current guidance similarly frames agent identity, explicit scope, tool access, and auditability as core least-privilege requirements.
For user-facing agents, you usually want a delegation chain:
human:alice
│
└── delegated to
│
└── agent:support-bot
│
└── calls crm.read
Then an authorization token can conceptually carry:
{
"sub": "user:alice",
"actor": "agent:support-bot",
"aud": "crm-api",
"scope": "crm.contacts.read",
"tenant": "acme",
"exp": 1786550000
}
This lets you answer both:
Which agent made this call?
and
On whose authority did it act?
That's much better than simply giving the agent Alice's OAuth token.
For non-user-initiated automation, use the agent's workload identity directly rather than pretending a human authorized it.
Don't give an agent:
crm.*
Prefer:
crm.contacts.read
crm.tickets.read
crm.tickets.update
And go one level further when necessary:
crm.tickets.update
where tenant == "acme"
and status transition in ["open" → "pending"]
For particularly sensitive tools, authorization should inspect the arguments, not merely the tool name.
For example:
tool: transfer_money
allowed:
currency = USD
amount <= $500
destination ∈ approved_accounts
That makes your authorization system substantially more robust against prompt injection: even if the model is tricked into requesting a dangerous operation, the external policy engine can reject it.
Avoid:
AGENT_API_KEY=permanent-super-secret-key
Prefer:
agent → authorization server
→ short-lived token
→ specific tool/resource
OAuth 2.1 is a good foundation. For MCP specifically, the current authorization specification requires audience binding and token validation, and recommends short-lived access tokens.
The MCP guidance also explicitly warns against implementing token validation yourself and recommends established security libraries.
This is probably the most important architectural decision.
Don't rely on:
System prompt:
"You are not allowed to delete customers."
The LLM can still produce:
delete_customer(id=123)
Instead:
LLM
│
│ delete_customer(123)
▼
Policy enforcement point
│
├── Is this agent authenticated? ✓
├── Is this tool allowed? ✓
├── Is this resource in scope? ✓
├── Is this action allowed? ✗
│
└── DENY
The model is allowed to propose an action. Only your trusted enforcement layer gets to authorize it.
Use risk tiers:
| Risk | Example | Control |
|---|---|---|
| Low | Search documentation | Agent can execute |
| Medium | Update CRM record | Scoped authorization |
| High | Delete data | Approval required |
| Very high | Wire money / deploy prod | Explicit approval + narrow policy |
You can make approval itself a capability:
agent requests:
deploy(production, version=abc)
policy:
requires approval from ops-oncall
human approves
gateway issues:
one-time capability
That is preferable to giving the agent permanent production deployment rights.
Every tool call should produce an event roughly like:
{
"request_id": "req_8f31",
"human": "alice",
"agent": "support-bot",
"tool": "crm.update_ticket",
"resource": "ticket:9182",
"decision": "allow",
"policy": "support-agent-v3",
"timestamp": "...",
"approval": null
}
If agents can call other agents, preserve the entire chain:
Alice
└─ agent:orchestrator
└─ agent:billing
└─ stripe.update_customer
Otherwise incident investigation becomes extremely difficult.
MCP maps nicely onto this architecture. Its authorization model uses OAuth 2.1 conventions; authorization-code flows are appropriate when an agent acts on behalf of a human, while client-credentials-style flows fit application-to-application scenarios.
For an HTTP MCP server, I'd structure it as:
Agent
│
│ OAuth access token
▼
MCP server / authorization gateway
│
├─ validate issuer
├─ validate signature
├─ validate audience
├─ validate expiry
├─ check scopes
├─ check agent identity
├─ check delegated user
├─ evaluate tool arguments
└─ audit
│
▼
actual API
MCP's security guidance specifically emphasizes token validation, audience binding, short-lived credentials, secure token storage, and PKCE where applicable.
Think of an agent authorization token as:
"This particular agent, acting under this particular authority, may perform these particular operations on these particular resources, until this particular time."
Not:
"This agent has access to the CRM."
That shift—from role-based access toward identity + delegation + resource + action + context—is what makes agent permissions manageable as agents become more autonomous.
If you're building this yourself, a strong minimal stack is OIDC/OAuth + workload identities + short-lived tokens + an external policy engine + tool gateway + immutable audit trail. For MCP-based agents, use the protocol's OAuth authorization mechanisms rather than inventing a parallel credential scheme.
Giving AI agents scoped identity and authentication to access tools requires moving away from static, long-lived API keys or shared service accounts . Instead, the modern security consensus is built on a two-identity delegation model using open standards like OAuth 2.1, OpenID Connect (OIDC), and the Model Context Protocol (MCP).
Here is a practical blueprint for implementing scoped identity and authentication for your AI agents:
Do not let agents share human user accounts or broad administrative service credentials.
When an agent calls a tool on behalf of a human, the downstream request must carry two distinct contexts : the human user's identity and the agent's workload identity.
Tools and MCP servers should not blindly trust an incoming connection just because it came from an authenticated agent.
slack:read:messages instead of a broad slack:admin token).aud ), expiration (exp typically set to 15–60 minutes), and specific action claims.For high-risk, destructive, or financial tool calls (e.g., database deletions, money transfers), autonomous execution must pause.
If you're building this out, tell me:
I can provide a more tailored architecture sequence or code pattern for your setup.