From Overview to Implementation#
In Part 2 of this series I introduced the four access patterns that Okta for AI Agents (O4AA) provides โ XAA, STS, PSK, and Service Account โ and the strategic framework for choosing between them. That article answered which pattern and why.
This deep dive answers how. For each pattern I walk through the protocol flow, the sequence diagrams, the token structure (where applicable), the audit signals you should expect in Okta system logs, the use cases where it fits, and the concrete configuration steps in the Okta admin console. If you are an architect or solution engineer about to implement one of these patterns, this is the reference companion to keep open in a second tab.
Jump directly to the pattern you are implementing โ each section is self-contained:
- Cross App Access (XAA) โ the strategic, ID-JAG-based pattern
- Secure Token Service (STS) โ OAuth bridge for SaaS that hasn’t yet adopted ID-JAG
- Pre-Shared Key (PSK) โ vaulted secrets for legacy API-key services
- Service Account โ username/password for systems that support nothing else
- From Theory to Practice โ Okta admin console configuration walkthrough
Cross App Access (XAA)#
Cross App Access (XAA) is the flagship access pattern for Okta for AI Agents. It implements user-delegated, user-context-aware access through the Identity Assertion JWT Authorization Grant (ID-JAG), an emerging IETF standard for secure delegation1.

Why XAA Matters#
XAA is the first access pattern to embed full identity context at every hop in an agent workflow. Every token carries both:
sub: The user identity (who authorized this action)act.sub: The agent identity (which agent is acting on their behalf)
This dual-identity structure enables capabilities impossible with traditional service accounts:
- Per-user audit attribution: Every agent action is traceable to the specific user who authorized it
- Surgical revocation: Disable one user’s access to one agent without affecting other users or agents
- Scope narrowing: The token’s effective permissions are the narrowest overlap of what the agent is allowed, what the user is entitled to, and what the specific task requires
- Regulatory compliance: Complete audit trails satisfying NIST SP 800-63-42, EU AI Act3, NIS24, and DORA5 traceability requirements
How XAA Works: The ID-JAG Flow#
The XAA flow involves a token exchange sequence where Okta vouches for the user’s identity to the target resource’s authorization server.
sequenceDiagram
autonumber
actor User
participant Client as ๐ค Client
(AI Agent)
box rgba(243, 242, 247,0.3) Okta Cloud
participant IdP as ๐ IdP AS
(Okta Org AS)
participant AS as ๐ก๏ธ Resource AS
(Custom AS)
end
participant RS as ๐ฆ Resource Server
(Protected API)
Note over User, RS: Step 1: User Authentication (OIDC SSO)
User->>Client: Initiate action
Client->>IdP: Auth Code + PKCE
IdP->>User: Authenticate (MFA)
User-->>IdP: Credentials
IdP-->>Client: ID Token + Refresh Token
Note left of IdP: sub:user
aud:client
Note over User, RS: Step 2: Token Exchange (RFC 8693)
Client->>IdP: Token Exchange Request
Note right of Client: grant_type:token-exchange
subject_token:ID_Token
requested_token_type:id-jag
audience:Resource AS
IdP->>IdP: Validate ID Token
Note right of IdP: private_key_jwt
managed connection policy
IdP->>IdP: Generate ID-JAG
IdP-->>Client: ID-JAG (Identity Assertion)
Note left of IdP: sub:user
aud:Resource AS
client_id:agent
Note over User, RS: Step 3: JWT Bearer Grant (RFC 7523)
Client->>AS: JWT Bearer Request
Note right of Client: grant_type:jwt-bearer
assertion:ID-JAG
scope:orders:read
AS->>AS: Validate ID-JAG
Note left of AS: aud match
client_id match
signature (JWKS)
AS->>AS: Apply Authorization Policy
AS-->>Client: Access Token
Note left of AS: sub + act.sub + scope
ephemeral
Note over User, RS: Step 4: API Call
Client->>RS: API call (with Access Token)
RS->>RS: Validate Token (JWKS)
RS-->>Client: Response data
Step-by-step breakdown#
- User authentication (OIDC SSO): The user initiates an action through the client application. The client redirects to Okta’s Org Authorization Server using Authorization Code + PKCE. The user authenticates (including MFA if configured), and Okta issues an ID Token and optionally a Refresh Token. This ID Token becomes the
subject_tokenfor the next step. - Token exchange at Okta (RFC 8693): The agent authenticates with its workload credentials (
private_key_jwt) and requests a token exchange (grant_type: token-exchange,subject_token: ID Token,requested_token_type: id-jag,audience: Custom AS). Okta validates the token, checks the managed connection policy, and generates an ID-JAG โ a signed JWT containingsub(user),aud(Custom AS), andclient_id(agent). - JWT Bearer grant at Custom AS (RFC 7523): The agent presents the ID-JAG to the Custom AS’s token endpoint (
grant_type: jwt-bearer,assertion: ID-JAG,scope: orders:read). The Custom AS validates the ID-JAG signature via JWKS, confirmsaudandclient_idmatch, applies its local authorization policy, and mints an ephemeral access token withsub+act.sub. - API call: The agent calls the protected resource with the scoped access token. The resource validates the token via JWKS and returns data.
This diagram uses the role names from the ID-JAG specification. Here’s how each IETF role maps to Okta’s implementation:
| IETF Role | Okta Counterpart |
|---|---|
| Client | The AI Agent (or its frontend/orchestration layer) โ the workload principal registered in Okta |
| IdP AS | Okta’s Org Authorization Server, backed by Universal Directory and Okta SSO |
| Resource AS | A Custom Authorization Server in Okta, protecting downstream APIs |
| Resource Server (RS) | The protected resource โ an internal API, MCP server, or third-party API |
In practice, the “Client” box represents multiple components working together: a frontend application (chat UI, web app) handling user authentication via OIDC, an orchestration layer managing conversation state and tool calls, and one or more backend agents (workload principals) performing the token exchanges. I collapsed them into a single box to keep the focus on the protocol flow rather than internal architecture.
User Presence and Consent Model#
A key advantage of XAA is that it eliminates the interactive consent step at the Resource Authorization Server. Unlike traditional OAuth flows where each resource requests user consent separately, in XAA the IdP evaluates administrator-defined policies and delegates authorization authority. The Resource AS trusts the IdP’s assertion without prompting the user โ this is what oauth.net describes as enabling access “without any user interaction” at the resource level.
However, XAA does require user authentication to bootstrap the flow. The user must authenticate via OIDC SSO (Step 1) so the agent obtains an ID Token as subject_token for the token exchange. Every agent action is scoped to a specific authenticated user’s identity and permissions.
The ID-JAG specification defines an optional (MAY) capability where implementations can accept Refresh Tokens as subject_token, allowing agents to obtain fresh ID-JAGs without re-authenticating the user. This would enable truly autonomous agent operation for scheduled or event-driven tasks.
However, this capability is not yet documented in Okta’s XAA implementation or on xaa.dev. Today, Okta’s XAA flow requires an ID Token obtained from an active user authentication as the subject_token. For agent scenarios that require background operation without user presence, consider combining XAA (when user context is available) with other patterns such as PSK or Service Accounts for autonomous operations.
The ID-JAG Standard#
ID-JAG (Identity Assertion JWT Authorization Grant) is based on several IETF standards:
- RFC 8693: OAuth 2.0 Token Exchange
- RFC 7523: JWT Profile for OAuth 2.0 Client Authentication
- IETF ID-JAG Draft: Identity Assertion JWT Authorization Grant specification (active IETF working group draft)
For a comprehensive overview of Cross App Access and how it fits into the OAuth ecosystem, see the OAuth.net Cross App Access reference.
The key innovation is the clean separation of user identity (sub) and agent identity (client_id) as distinct required claims in the ID-JAG token. The IdP signs an assertion that says “user X has authorized agent Y to act on their behalf for resource Z” โ all in a single, verifiable JWT. Downstream, the Resource AS can mint access tokens with the act claim (per RFC 8693 ยง4.4) to propagate this separation to resource servers, enabling a clear audit trail of “who is this action for” versus “what system is performing the action.”
Token Structure#
The XAA flow produces two distinct tokens. Understanding the difference is important for implementation and audit.
The ID-JAG Token (issued by the IdP)#
The ID-JAG is a signed JWT issued by the IdP Authorization Server during the token exchange (Step 1). It asserts the user’s identity and the agent’s authorization to act on their behalf. Its JWT header must use the type oauth-id-jag+jwt.
Header: { "typ": "oauth-id-jag+jwt", "alg": "RS256" }{
"iss": "https://acme.okta.com",
"sub": "john@example.com",
"aud": "https://crm.example.com/oauth2",
"client_id": "sales-representative-agent",
"jti": "9e43f81b64a33f20116179",
"exp": 1735571612,
"iat": 1735571312,
"resource": "https://crm.example.com/api",
"scope": "orders:read accounts:read",
"auth_time": 1735571200,
"amr": ["mfa", "pwd"]
}| Claim | Status | Purpose |
|---|---|---|
iss | REQUIRED | IdP Authorization Server identifier |
sub | REQUIRED | User identity โ same identifier used in SSO |
aud | REQUIRED | Resource Authorization Server identifier |
client_id | REQUIRED | Agent identity โ the OAuth client registered at the Resource AS |
jti | REQUIRED | Unique token identifier (for replay prevention and audit correlation) |
exp | REQUIRED | Expiration time |
iat | REQUIRED | Issued at time |
resource | OPTIONAL | Target Resource Server URI(s) |
scope | OPTIONAL | Requested scopes (may be narrowed by IdP policy) |
auth_time | OPTIONAL | When the user last authenticated |
amr | OPTIONAL | Authentication methods used (e.g. mfa, pwd, hwk) |
The ID-JAG does not contain an act claim. The user/agent separation is expressed through sub (user) and client_id (agent). The act claim appears only in the downstream access token.
The Access Token (issued by the Resource AS)#
The Resource AS validates the ID-JAG (Step 2) and issues a scoped access token. This token propagates the delegation via the act claim (RFC 8693 ยง4.4), making both identities visible to the resource server:
{
"sub": "john@example.com",
"act": {
"sub": "sales-representative-agent",
"aud": "okta.com"
},
"aud": "crm-orders-api",
"scope": "orders:read accounts:read",
"jti": "s2r3d4x3m6a0q1l",
"iat": 1735571312,
"nbf": 1735571312,
"exp": 1735571468
}| Claim | Purpose | Audit/Compliance Value |
|---|---|---|
sub | User identity | Answers: “Which user authorized this action?” |
act.sub | Agent identity | Answers: “Which agent performed this action?” |
aud | Target resource | Answers: “What system was accessed?” |
scope | Granted permissions | Answers: “What operations were permitted?” |
jti | Transaction ID | Unique identifier for correlation across systems |
exp | Expiration | Ensures ephemeral access; limits blast radius |
Token Lifetime#
The ID-JAG specification requires the exp (expiration) and iat (issued at) claims in every token, but it does not mandate a specific lifetime. The "expires_in": 300 (5 minutes) that appears in the spec is marked as RECOMMENDED in the token exchange response โ it is an example, not a fixed requirement.
In practice, the IdP administrator configures the token lifetime based on the deployment’s security posture. A shorter lifetime (e.g. 5 minutes) limits the blast radius if a token is compromised; a longer lifetime reduces the frequency of token exchanges for agents performing multi-step workflows. The right value depends on your risk profile, but the design intent is clear: ID-JAG tokens should be short-lived and re-issued frequently, not cached for hours.
An agent running for a sales manager might have access to orders:read, orders:write, and accounts:read. But when processing a specific task (“list my open deals”), the request can be narrowed to just orders:read. The effective permission is always the intersection of what the agent can do, what the user can do, and what this specific request needs.
Audit and Compliance Benefits#
When using XAA, Okta’s system logs capture rich context for every access event, including both the user and agent identities, the scopes requested, the target resource, and a unique transaction ID for correlation. The EventType app.oauth2.token.grant.id_jag specifically indicates when an ID-JAG exchange occurred, providing a clear audit trail for agent actions.
Let’s see an example of an actual XAA event log entry from Okta’s system logs:
Full Okta System Log entry for an ID-JAG exchange (click to expand)
{
"actor": {
"id": "wlpxnnu00000B6vxs1d7",
"type": "PublicClientApp",
"alternateId": "unknown",
"displayName": "Pricing Agent",
"detailEntry": null
},
"client": {
"userAgent": {
"rawUserAgent": "python-requests/2.33.1",
"os": "Unknown",
"browser": "UNKNOWN"
},
"zone": "null",
"device": "Unknown",
"id": null,
"ipAddress": "x.x.x.x",
"geographicalContext": {
"city": "Ashburn",
"state": "Virginia",
"country": "United States",
"postalCode": "20149",
"geolocation": {
"lat": 39.0469,
"lon": -77.4903
}
}
},
"displayMessage": "OAuth 2.0 Identity Assertion Authorization Grant is granted",
"eventType": "app.oauth2.token.grant.id_jag",
"outcome": {
"result": "SUCCESS",
"reason": null
},
"published": "2026-04-16T19:27:49.919Z",
"securityContext": {
"asNumber": 14618,
"asOrg": "amazon.com inc.",
"isp": "amazon.com inc.",
"domain": "amazonaws.com",
"isProxy": false,
"ipDetails": {
"asNumber": 14618,
"asOrg": "amazon.com inc.",
"isp": "amazon.com inc.",
"domain": "amazonaws.com"
}
},
"severity": "INFO",
"debugContext": {
"debugData": {
"clientAuthType": "private_key_jwt",
"grantedScopes": "pricing:margin",
"subjectTokenIssuer": "https://xxx.oktapreview.com",
"subjectTokenType": "urn:ietf:params:oauth:token-type:id_token",
"clientId": "wlpxnnu00000B6vxs1d7",
"responseTime": "213",
"requestedTokenType": "urn:ietf:params:oauth:token-type:id-jag",
"authorizationServerAudience": "https://xxx.oktapreview.com/oauth2/ausxnnacdm60nV7vv1d7",
"requestUri": "/oauth2/v1/token",
"requestedScopes": "pricing:margin",
"tokenExchangeType": "Agent ID Assertion",
"url": "/oauth2/v1/token?",
"managedConnectionId": "mcnxnodu9t2FRZO5R1d7",
"subjectTokenId": "ID.kiJ9Ch3aBer2ftX2CobkLuSSPqBMdMsZaxwRMZQjr44",
"subjectTokenClientId": "0oaxnnekviMmWpyBK2d3",
"requestId": "5eaf3d96ff3dbb684e1099bf8cecdd53",
"dtHash": "96dec87ffaebc55e64ab62eca30303c555d589e0f9686d55827963c51032ef7f",
"threatSuspected": "false",
"grantType": "urn:ietf:params:oauth:grant-type:token-exchange"
}
},
"legacyEventType": null,
"transaction": {
"type": "WEB",
"id": "5eaf3d96ff3dbb684e1099bf8cecdd53",
"detail": {}
},
"uuid": "5b10a39b-39ca-11f1-bbfb-f341db467931",
"version": "0",
"request": {
"ipChain": [
{
"ip": "x.x.x.x",
"geographicalContext": {
"city": "Ashburn",
"state": "Virginia",
"country": "United States",
"postalCode": "20149",
"geolocation": {
"lat": 39.0469,
"lon": -77.4903
}
},
"version": "V4",
"source": null,
"ipDetails": {
"asNumber": 14618,
"asOrg": "amazon.com inc.",
"isp": "amazon.com inc.",
"domain": "amazonaws.com"
}
}
]
},
"target": [
{
"id": "ID.kiJ9Ch3aBer2ftX2CobkLuSSPqBMdMsZaxwRMZQjr44",
"type": "id_token",
"alternateId": null,
"displayName": "ID Token",
"detailEntry": {
"audience": "0oaxnnekviMmWpyBK2d3",
"expires": "2026-04-16T20:07:29.000Z",
"subject": "00uxnn7b13YEcYj2V6a7",
"hash": "iMQM1uCzsH07nFhkRPUNvg"
}
},
{
"id": "00uxnn7b13YEcYj2V6a7",
"type": "User",
"alternateId": "fabio.grasso@example.com",
"displayName": "Fabio Grasso",
"detailEntry": null
},
{
"id": "IDAAG.5ZazR5P-fmm8zuG1CwuqT3EenUx1pFfN00jToEE0s6A",
"type": "id_jag",
"alternateId": null,
"displayName": "Identity Assertion JWT Authorization Grant",
"detailEntry": {
"audience": "https://xxx.oktapreview.com/oauth2/ausxnnacdm60nV7vv1d7",
"expires": "2026-04-16T19:32:49.000Z",
"subject": "00uxnn7b13YEcYj2V6a7",
"scope": "pricing:margin",
"hash": "Tq7wccRtqpRvZvEXKYK9nA"
}
}
]
}As you can see, not only the agent that made the request (Pricing Agent) but also the user that authorized the action (fabio.grasso@example.com). The grantedScopes field shows the effective permissions granted to the agent for this request, which is the intersection of what the agent can do and what the user is entitled to. The jti claim in the token becomes the transaction.id in the logs, allowing you to correlate this event with downstream events in your resource’s logs if they also log the transaction ID.
XAA Use Cases#
Let’s see some concrete examples of how XAA can be used to secure different types of resources:
- Internal API via MCP Server - A customer support agent needs to access the internal order database to answer customer queries.
- Agent registered in Okta Universal Directory
- MCP Server protected by Okta Custom Authorization Server
- XAA flow: Agent exchanges user’s ID Token for scoped access to
orders:read - Audit: Every query logged with user ID, agent ID, and transaction ID
- First-Party SaaS (ISV-Enabled) - A sales assistant agent accessing Salesforce opportunities on behalf of a sales rep.
- Salesforce implements ID-JAG validation
- Agent presents ID-JAG to Salesforce authorization endpoint
- Salesforce issues scoped token for the user’s data only
- No static credentials; no shared service accounts
- Multi-Step Agent Workflows - A finance agent executes an approval workflow:
- Step 1: Read expense report (
read:expensesscope) - Step 2: Check budget availability (
read:budgetsscope) - Step 3: Submit approval (
write:approvalsscope) - Each step can request different scopes. If the agent is compromised mid-workflow, revocation stops only that user’s session without affecting others.
- Step 1: Read expense report (
XAA is fully functional today for Okta Custom Authorization Servers (connection type: “Authorization Server”) and MCP Servers (connection type: “MCP Server”). ISV adoption for third-party SaaS integrations is currently in progress. Major vendors are actively working on ID-JAG support.
Start building on XAA now so you’re ready to extend to SaaS integrations as soon as ISVs complete their implementations. Until then, use STS (connection type: “Application”) for third-party SaaS that supports OAuth but not yet ID-JAG.
Testing XAA: The xaa.dev Playground#
Okta provides an interactive testing environment at xaa.dev where you can experiment with XAA flows without infrastructure setup6.
Features:
- Browser-based testing (no Docker or local configuration)
- Pre-configured components: Requesting App, Resource App, IdP
- Get started in under 60 seconds
- Register your own custom clients for testing
The playground includes a sample flow where “Bob Tables” creates and manages tasks through an AI agent, demonstrating the full ID-JAG exchange.
ID-JAG and XAA: A Note on Naming#
You’ve likely seen “ID-JAG” and “XAA” used interchangeably across documentation, blog posts, and conference talks. They’re closely related โ but not the same thing.
Think of it like calling any adhesive bandage a “Band-Aid” regardless of the manufacturer: the leading product becomes the generic term for the category. Something similar is happening here.
ID-JAG (Identity Assertion JWT Authorization Grant) is the OAuth extension being standardized through the IETF. It defines precisely how identity providers issue assertion tokens that applications can exchange for access tokens. Any IdP can implement ID-JAG โ it’s an open standard, and the spec belongs to the whole industry.
XAA (Cross-App Access) is Okta’s product name for their implementation of ID-JAG. Okta has been a real force behind both the IETF standardization effort and early industry adoption โ their engineering investment and developer advocacy have made “XAA” the term most people encounter first, and the one that tends to stick as shorthand for the extension.
The practical distinction matters: XAA is one implementation of an open standard. When you read Okta documentation, “XAA” refers to Okta’s product. When you read the IETF draft or a third-party integration guide, “ID-JAG” refers to the underlying specification that any organization can implement.
XAA Limitations#
ISV adoption in progress: XAA is GA on the Okta side, but third-party SaaS integrations require ISV support. Major vendors are actively implementing ID-JAG, building on XAA today ensures you’re ready when they ship.
Token lifetime vs. long-running sessions: Tokens are intentionally short-lived (lifetime is configurable by the IdP administrator, not fixed by the spec). Agents performing multi-step workflows need to re-exchange tokens as they expire. This is by design (limits blast radius) but requires agents to handle token refresh gracefully.
Not suitable for: Agents that must operate offline or cache credentials indefinitely. For these scenarios, consider STS with careful credential lifecycle management.
Secure Token Service (STS)#
Secure Token Service (STS) enables agents to access third-party SaaS applications that support OAuth but haven’t yet adopted XAA. Okta acts as a secure broker, vaulting user-consented tokens in Okta Privileged Access (OPA) and providing short-lived federated access at runtime.
When to Use STS#
- Third-party SaaS with OAuth support but no ID-JAG support yet (e.g., as today, GitHub, Google Workspace, Slack)
- User-level context is required for audit and compliance
Regularly check with your SaaS vendors about their roadmap for ID-JAG support. STS is a bridge, not a destination. The goal is to migrate to XAA as soon as the vendor supports it.
How STS Works#
sequenceDiagram
autonumber
actor User
participant Client as ๐ค Client
(AI Agent)
box rgba(243, 242, 247,0.3) Okta Cloud
participant IdP as ๐ IdP AS
(Okta Org AS)
participant Vault as ๐ OPA
(Token Vault)
end
participant SaaS as ๐ฑ Third-Party SaaS
Note over User, SaaS: Phase 1 โ One-Time Consent (user-initiated)
Note over User, SaaS: Step 1: SSO Authentication
User->>Client: Login via SSO (OIDC)
Client->>IdP: OIDC authentication
IdP-->>Client: ID Token (sub: user@example.com)
Note over User, SaaS: Step 2: OAuth Consent
Client->>IdP: Request managed connection token
IdP-->>Client: Consent URL (SaaS OAuth)
Client-->>User: Redirect to SaaS consent
User->>SaaS: Login and approve access
Note over User, SaaS: Step 3: Token Vaulting
SaaS-->>IdP: Authorization Code (callback)
IdP->>SaaS: Exchange code for tokens
SaaS-->>IdP: Access + Refresh Tokens
IdP->>Vault: Store tokens (bound to user context)
Note over Client, SaaS: Phase 2 โ Runtime Access (agent-autonomous, RFC 8693)
Note over Client, SaaS: Step 4: Token Exchange (RFC 8693)
Client->>IdP: Token Exchange Request
Note right of Client: grant_type:token-exchange
subject_token: (vaulted user token)
actor: workload identity
audience: Third-Party SaaS
auth: private_key_jwt
IdP->>IdP: Validate managed connection policy
IdP->>Vault: Retrieve user-consented tokens
Vault-->>IdP: Access + Refresh Tokens
IdP->>SaaS: Refresh token if expired
SaaS-->>IdP: Fresh Access Token
IdP-->>Client: Short-lived federated token
Note left of IdP: scope: connection-scoped
token_type: Bearer
expires_in: short-lived
Note over Client, SaaS: Step 5: API Call
Client->>SaaS: API call with token
SaaS-->>Client: Response
Step-by-step breakdown#
- User authenticates (SSO): The user logs in through the Client via OIDC. Okta issues an ID Token with the user identity (
sub: user@example.com). - Consent redirect: The Client requests a managed connection token. Okta returns a consent URL; the Client redirects the user to the SaaS’s standard OAuth consent screen.
- User approves: The user logs in to the third-party SaaS and approves the requested scopes. The SaaS returns an authorization code to Okta’s callback endpoint.
- Token vaulting: Okta exchanges the authorization code for access and refresh tokens and stores them in Okta Privileged Access (OPA), bound to the user’s identity context.
- Runtime access (agent-autonomous โ no live user session required): The agent authenticates to Okta with its workload identity (
private_key_jwt) and requests access via the managed connection policy. - Token retrieval: Okta retrieves the vaulted tokens from OPA, refreshes them with the SaaS if expired, and issues a short-lived federated token to the agent.
- API call: The agent calls the third-party SaaS with the short-lived token. User credentials never touch the agent.
The User appears in Phase 1 because STS requires explicit user consent โ the user must authenticate to the third-party SaaS and approve the OAuth scopes. This is a one-time operation. Once consent is granted and tokens are vaulted, Phase 2 runs entirely autonomously: the agent retrieves and uses tokens without any user interaction. The user may not even be online when the agent accesses the SaaS.
STS Use Cases#
- GitHub โ A coding assistant agent reads repositories and creates pull requests on behalf of developers. The developer consents once via GitHub’s OAuth screen; Okta vaults the resulting tokens. At runtime, the agent receives a short-lived federated token to call GitHub’s API โ it never holds long-lived GitHub credentials.
- Google Workspace โ A scheduling agent manages calendar events and drafts emails for a user. User context is preserved through the consent flow, so Google’s audit logs capture which user authorized the agent’s actions.
- Jira/Confluence โ A project management agent creates tickets from Slack conversations and updates documentation pages. The agent authenticates through Okta’s brokered token, never storing Atlassian credentials directly.
Key difference from XAA#
- Consent flow: STS requires the user to go through the third-party’s OAuth consent screen; Okta then stores and manages those tokens on behalf of the agent. XAA skips the consent screen entirely. Delegation happens at agent registration time in Okta.
- Audit trail: STS captures user consent and agent access in Okta logs, but the third-party service may only see the agent identity. XAA provides full user+agent context to the resource.
- Token lifetime: The federated token issued to the agent is short-lived (minutes) to limit risk, but the underlying access/refresh tokens in Okta may have longer lifetimes depending on the third-party service’s policies.
- Revocation: Revoking access requires deleting the vault entry in Okta, which may affect all agents using that connection. No per-user revocation within the third-party service.
- Scope narrowing: The agent receives whatever scopes the user consented to during the OAuth flow. Okta can’t dynamically narrow scopes per request like XAA.
Unlike Pre-Shared Key, STS preserves user context through the consent flow. Audit logs capture both the user who consented and the agent that accessed the resource.
STS is explicitly a transitional bridge pattern. Once an ISV or SaaS vendor ships ID-JAG support, you can upgrade that connection from STS to XAA without re-architecting. Until then, STS gives you user-level context that Pre-Shared Key and Service Account simply can’t provide.
Pre-Shared Key (PSK) or Vaulted Secret#
Pre-Shared Key (PSK) stores static credentials (API keys, bearer tokens, webhook secrets) in Okta Privileged Access (OPA). The agent retrieves secrets at runtime rather than embedding them in code or configuration.
When to Use Pre-Shared Key#
- Legacy REST APIs that only support API key authentication
- Webhook endpoints requiring bearer tokens
- Third-party integrations with static token auth
- Early-phase services that will eventually add OAuth
How Pre-Shared Key Works#
sequenceDiagram
autonumber
actor Admin as Admin
participant Client as ๐ค Client
(AI Agent)
box rgba(243, 242, 247,0.3) Okta Cloud
participant IdP as ๐ IdP AS
(Okta Org AS)
participant Vault as ๐ OPA
(Secret Vault)
end
participant RS as ๐ฆ Resource Server
(Protected API)
Note over Admin, Vault: Phase 1 โ Configuration (admin-initiated)
Admin->>Vault: Create & vault secret (API key)
Admin->>IdP: Create managed connection on agent (type: Secret)
Note over Client, RS: Phase 2 โ Runtime Access (agent-autonomous)
Client->>IdP: Authenticate (workload identity)
Note right of Client: private_key_jwt
managed connection policy
IdP->>IdP: Validate managed connection policy
IdP->>Vault: Retrieve vaulted secret
Vault-->>IdP: API key / bearer token
IdP-->>Client: Release secret
Client->>RS: API call with secret
RS-->>Client: Response
Step-by-step breakdown#
- Admin vaults the secret: An administrator stores the API key or bearer token in Okta Privileged Access and creates a managed connection on the agent (type: Secret)
- Agent authenticates: At runtime, the agent authenticates to Okta with its workload principal identity
- Policy check: Okta validates the agent’s identity and checks the managed connection policy
- Secret released: Okta releases the vaulted credential to the agent
- Direct resource access: The agent uses the static credential to call the downstream resource directly โ no token exchange, no user context
When possible (i.e. for supported SaaS Services) configure the vault to automatically rotate credentials. This reduces risk if a credential is compromised, but requires the downstream service to support credential updates without downtime.
Pre-Shared Key Limitations#
- No user context: Resource sees agent identity only, not the user
- No scope narrowing: Agent has full access granted to the credential
- Limited audit trail: Logs show agent access, not user attribution
Pre-Shared Key has limited audit trail and no user context. Plan migration to XAA or STS as downstream services add OAuth support. This pattern should be treated as temporary for legacy integrations.
Service Account#
Service Account uses username and password credentials linked to a shared service identity. This is the legacy pattern that Okta explicitly recommends migrating away from.
How Service Account Works#
sequenceDiagram
autonumber
actor Admin as Admin
participant ClientA as ๐ค Client A
(AI Agent)
participant ClientB as ๐ค Client B
(AI Agent)
box rgba(243, 242, 247,0.3) Okta Cloud
participant IdP as ๐ IdP AS
(Okta Org AS)
participant Vault as ๐ OPA
(Credential Vault)
end
participant RS as ๐ฆ Resource Server
(Protected API)
Note over Admin, RS: Phase 1 โ Configuration (admin-initiated)
Admin->>Vault: Create service account (svc_agent@example)
Admin->>ClientA: Register with managed connection
Admin->>ClientB: Register with managed connection
Note over ClientA, RS: Phase 2 โ Runtime Access (agent-autonomous)
ClientA->>IdP: Request credentials
ClientB->>IdP: Request credentials
Note right of ClientA: private_key_jwt
managed connection
IdP->>IdP: Validate managed connection policy
IdP->>Vault: Retrieve vaulted credentials
Vault-->>IdP: Username/Password (svc_agent@example)
IdP-->>ClientA: Username/Password
IdP-->>ClientB: Username/Password
ClientA->>RS: Authenticate as svc_agent@example
ClientB->>RS: Authenticate as svc_agent@example
Note over RS: Both appear as same identity!
Step-by-step breakdown#
- Admin provisions service account: An administrator creates a service account in OPA (Credential Vault) and registers managed connections on one or more agents
- Agents request credentials: At runtime, each agent authenticates to the IdP AS with its workload identity (
private_key_jwt) and requests the service account credentials via the managed connection - Okta releases username/password: The IdP AS retrieves the vaulted credentials from OPA, validates the agent’s identity, checks policy, and hands over the shared credentials
- Agent authenticates to resource: The agent logs into the Resource Server as the service account โ indistinguishable from any other agent using the same credentials
Service Accounts Limitations#
Service Accounts present four critical security gaps:
- Invisible agents: Multiple agents share a single identity, making it impossible to tell which one performed an action
- Excessive permissions: Service accounts typically carry broad, static privileges that far exceed what any single task requires
- Missing user attribution: Compliance audits can’t answer “which user triggered this data access?”
- All-or-nothing revocation: Deactivating the service account kills access for every agent and system that depends on it
Service Accounts are not as secure as the authorization server or vaulted secret resource types. New integrations should never default to this pattern.
When Service Account is Unavoidable#
- Legacy systems requiring username/password authentication (on-prem databases, mainframes, LDAP-backed services)
- Batch processing with no end-user context
- Systems that have not adopted any modern authentication method
Service Account is acceptable only for batch processes with no user context, with a documented sunset plan.
Both patterns lack user context and lack scope narrowing, but they differ in the type of credential stored and the security posture:
PSK vaults a machine-native credential (API key) that is typically unique per integration, so rotation and revocation stay surgical. Credentials are scoped to one integration, so you can have multiple PSKs for different agents, to guarantee at least agent-level attribution (audit) and isolation (i.e. for revocation). Okta Privileged Access can automatically rotate PSKs if the downstream service supports it, which is a huge security win.
Service Account borrows a human-shaped credential (username/password) to impersonate a shared identity: multiple agents share the same login, which is exactly what breaks per-agent attribution and forces all-or-nothing revocation. Passwords are typically long-lived and require manual rotation, which is a risk if the credential is compromised. Service accounts are often over-provisioned with broad permissions to avoid breaking dependent agents, which amplifies risk further.
That is why moving from Service Account to PSK is the first defensible step on the migration ladder, even though neither pattern carries user context.
From Theory to Practice: Configuring Access Patterns in Okta#
In Okta’s Universal Directory, you can register AI agents as workload identities and create Managed Connections that define how they access downstream resources. Each connection type corresponds to one of the access patterns I’ve discussed above.
| AI Agents List | AI Agent Detail |
|---|---|
![]() | ![]() |
When you create a managed connection, you’ll see five resource types. They map to the four access patterns covered in this article:

| Connection Type | Access Pattern | What It Does |
|---|---|---|
| Authorization Server | XAA (Cross App Access) | Agent gets scoped tokens from an Okta Custom Authorization Server via ID-JAG exchange |
| Secret | Pre-Shared Key (PSK) | Agent retrieves a vaulted API key or bearer token from Okta Privileged Access |
| Service Account | Service Account | Agent retrieves username/password credentials for a service identity vaulted in Okta Privileged Access |
| Application | STS (Secure Token Service) | Agent accesses an OIN app or custom resource server through Okta-brokered OAuth token exchange |
| MCP Server | XAA (Cross App Access) | Same ID-JAG exchange as Authorization Server, specialized for the MCP protocol surface |
The MCP Server connection type is not a separate access pattern: it’s XAA applied specifically to Model Context Protocol servers. The same ID-JAG delegation model, token structure (sub + act.sub), and policy enforcement apply. Okta provides a dedicated connection type to streamline MCP-specific configuration.
XAA Configuration Overview#
Implementing XAA requires:
- Register the agent in Okta Universal Directory as a workload principal
- Create a Managed Connection and select “Authorization Server” (for custom APIs) or “MCP Server” (for MCP endpoints). Both use the same ID-JAG exchange under the hood.
- Configure the Custom Authorization Server protecting your resource
- Define RBAC policies that evaluate both user and agent attributes
Example policy logic, to ensure the agent can only access sales data when the user is a member of the sales team:
IF (act.sub == "sales-representative-agent")
AND (sub IN groups["sales-team"])
AND (requested_scope IN ["sales:read", "accounts:read"])
THEN issue_token_with_scope
ELSE denyThis is a pseudo-code example. In the Okta Admin Console, Custom Authorization Server access policies use a policy + rule model โ not a scripting language. To follow the example you can:
- Create an Access Policy assigned to the agent’s AI Agent (i.e.,
sales-representative-agent). This implicitly scopes the policy to that specific agent (act.sub). - Create a Rule within that policy:
- Grant type:
JWT Bearer(the ID-JAG exchange grant) - User eligibility: Users member of group:
sales-teamโ this ensures only sales team members can delegate access through this agent - Scopes: Allowlist of
orders:readandaccounts:readโ the agent can’t request broader scopes even if the user has them - Token lifetime: 5 minutes (ephemeral)
- Evaluation: Policies and rules are evaluated in priority order. The first match wins. If no rule matches (e.g., a user outside
sales-teamtries to use this agent), the request is denied.
- Grant type:
The result: the agent can only access sales data when the user behind the request is a member of the sales team. The effective permissions are the intersection of the policy’s scope allowlist, the user’s group membership, and the agent’s managed connection.
STS Configuration#
In the Okta admin console, you configure STS by creating a managed connection with the “Application” resource type, selecting an OIN app integration or a custom resource server7. Okta then brokers the OAuth token exchange between the agent and the third-party service.
Pre-Shared Key Configuration#
In the Okta admin console, you configure PSK by creating a managed connection with the “Secret” resource type. The API key or bearer token must first be stored in Okta Privileged Access; the managed connection then references that vaulted secret. At runtime, the agent retrieves the credential on demand โ it is never hardcoded in the agent’s code or configuration.
Service Account Configuration#
In the Okta admin console, you configure a Service Account connection by creating a managed connection with the “Service Account” resource type, linking it to a service account identity stored in Okta Privileged Access. Because multiple agents can reference the same service account, treat this as a last resort and document a clear sunset plan before deploying it.
Where to Next#
You now have the protocol-level toolkit for every Okta for AI Agents pattern. Two natural next stops:
- Back to the strategic view: re-read Part 2 โ Access Patterns with the implementation details fresh in mind. The decision framework and migration ladder make a lot more sense once you’ve seen the token claims and sequence diagrams.
- Forward to compliance: in Part 4 โ EU AI Act Compliance I map each pattern to the regulatory requirements (EU AI Act, NIST AI RMF, NIS2, DORA), showing how the audit fields you’ve just learned to read translate directly into compliance evidence.
Try It Live#
Explore the interactive XAA playground at xaa.dev โ no setup required. Spin up agents, perform ID-JAG exchanges, and inspect the resulting tokens directly in your browser.
Reference Library#
- Cross App Access Documentation โ Official Okta configuration guide
- XAA.dev Documentation โ Interactive playground docs
- Cross App Access Introduction โ Technical introduction
- XAA Developer Playground โ Hands-on tutorial
- Building Resource Apps โ Implementation guide
- AI Agent Token Exchange Guide โ Step-by-step developer guide
- Secure AI agent access to resources โ Okta Help Center
Join the Conversation#
Which pattern are you implementing first? Hitting any blockers on ID-JAG exchange or managed connection policies? Drop a note in the comments below or reach out on LinkedIn.
Identity JWT Authorization Grant, IETF OAuth Working Group, 2025 ↩︎
NIST SP 800-63-4: Digital Identity Guidelines, NIST, 2024 ↩︎
EU Artificial Intelligence Act, European Union, 2024 ↩︎
NIS2 Directive, European Commission, 2024 ↩︎
DORA: Digital Operational Resilience Act, European Commission, 2024 ↩︎
XAA Developer Playground, Okta Developer Blog, January 2026 ↩︎
Secure AI agent access to resources, Okta Help Center ↩︎


