Full Cross-Domain Access in the AuthPlayground
How enterprise AI safely crosses trust boundaries end to end.
Imagine you’re the Chief Architect at a Sarah’s company. The CEO wants AI used across the company to improve productivity. The CISO wants every AI action to remain secure, attributable, governed, and auditable. These goals are challenging when one user request has to cross multiple authorization domains.
Sarah asks the AI assistant to help her prepare for the Apex Logistics meeting. The AI assistant needs to access Salesforce opportunity data, ServiceNow support history, and information from other enterprise systems. The request must cross distinct authorization domains, each with its own policy and access control.
This scenario raises several trust questions:
-
Did Sarah really authenticate?
-
What’s the AI workflow allowed to do?
-
Which software is acting on Sarah’s behalf?
-
How does Sarah’s identity reach another authorization domain?
-
Does the target domain make its own authorization decision?
-
Can a stolen token be replayed by someone else?
No single protocol answers all of those questions. The full cross-domain access flow shows how individual protocols come together in an end-to-end access path.
The full cross-domain access model
It’s tempting to think of enterprise AI security as a token problem. A token alone doesn’t protect the authorization request before the browser redirect. It doesn’t constrain what the AI client is asking to do, or preserve delegated identity across an authorization boundary. A token doesn’t stop a stolen bearer token from being replayed.
The enterprise needs a chain of controls, each one solving a different part of the problem. Sarah’s request flows through these stages:
-
Sarah initiates the request.
-
The secure authorization request uses Pushed Authorization Requests (PAR), Rich Authorization Requests (RAR), and Proof Key for Code Exchange (PKCE).
-
The authenticated user context establishes Sarah’s identity.
-
Delegation combines Sarah (the subject) and the software actor.
-
Identity JSON Assertion Grant (ID-JAG) crosses the authorization boundary.
-
The target authorization server applies local policy.
-
The Demonstrating Proof of Possession (DPoP) bound access token is used to access the protected enterprise API.
These protocols coexist and work together to create, constrain, exchange, and validate the security context as the request moves through the architecture.
|
The full cross-domain access flow intentionally combines several security controls to show how they work together in a high-assurance architecture. However, every production AI workflow doesn’t need every protocol. The right combination depends on trust boundaries, authorization requirements, the threat model, and the sensitivity of resources. The goal of this flow isn’t to prescribe one architecture for every use case. It’s to show what’s possible when these standards are combined. |
The AuthPlayground implementation
Entities involved: Client application, IdP (authorization server), authorization server B, resource server
The AuthPlayground demonstrates the Full Cross-Domain Access flow in seven steps:
-
Generate the DPoP key pair.
-
Push the authorization request.
-
Authorize the user with
request_uri. -
Exchange the code with PKCE and DPoP.
-
Exchange the subject and actor context for an ID-JAG.
-
Present the JSON Web Token (JWT) grant with DPoP proof for an authorization decision.
-
Access the protected resource with DPoP.
Let’s walk through these steps using Sarah’s enterprise AI request as a model.
Step 1: Generate the DPoP key pair
Before the authorization flow begins, the client generates an asymmetric key pair. The playground computes a thumbprint for the public key:
{
"thumbprint": "kOG4j4ejqVDIY8rYxtqtme3e1TqPJmBhhLm2tRCh2zs"
}
The private key stays with the caller. The public-key thumbprint is derived from the public key and it’s shared. The thumbprint is carried into later parts of the flow to bind credentials to the same key. This prepares the proof-of-possession mechanism that DPoP will use later.
Step 2: Push the authorization request
The client establishes the authorization request directly with the IdP authorization server using PAR. The request includes:
POST /idp/par
scope=openid profile read write delegation
code_challenge=<pkce_challenge>
code_challenge_method=S256
dpop_jkt=kOG4j4ejqVDIY8rYxtqtme3e1TqPJmBhhLm2tRCh2zs
authorization_details=[
{
"type": "account_information",
"actions": ["read"]
}
]
Several protocols come together:
-
PAR: Sends the detailed authorization request directly to the authorization server instead of carrying all of it through the browser.
-
PKCE: Introduces the
code_challengethat protects the authorization code exchange. -
RAR: Adds structured authorization intent using
authorization_details. -
DPoP: Introduces the public-key thumbprint (
dpop_jkt) associated with the issued credentials.
The authorization server stores the request and returns a short-lived reference, establishing the request before the browser gets involved:
{
"request_uri": "urn:ietf:params:oauth:request_uri:35dff7b3-f034-4186-94cc-437ffa511058",
"expires_in": 60
}
Step 3: Authorize the user with request_uri
The browser is redirected using the short-lived reference:
GET /idp/authorize
request_uri=<request_uri>
client_id=playground-demo-client
The IdP retrieves the authorization request it already has through PAR, authenticates Sarah, and returns an authorization code:
{
"code": "<authorization_code>",
"state": "<original_state>"
}
The browser participates in the user-facing authorization flow, but doesn’t need to carry the full authorization request.
Step 4: Exchange the code with PKCE and DPoP
The client exchanges the authorization code, presenting the PKCE code_verifier and also sending a DPoP proof:
POST /idp/token
DPoP: <dpop_proof>
grant_type=authorization_code
code=<authorization_code>
redirect_uri=<callback>
client_id=playground-demo-client
code_verifier=<code_verifier>
The resulting access token preserves context established earlier and contains RAR authorization details:
{
"authorization_details": [
{
"type": "account_information",
"actions": ["read"]
}
]
}
The access token also contains DPoP key binding:
{
"cnf": {
"jkt": "kOG4j4ejqVDIY8rYxtqtme3e1TqPJmBhhLm2tRCh2zs"
}
}
The ID token contains the delegation relationship:
{
"sub": "demo-user@example.com",
"may_act": {
"sub": "api-gateway@example.com"
}
}
A separate actor token represents api-gateway@example.com. There are now two distinct identities:
-
Subject:
demo-user@example.com -
Actor:
api-gateway@example.com, linked via themay_actclaim
In the playground, the actor is an API gateway. In an enterprise AI architecture, that role might be an MCP gateway, AI agent, backend service, or another software workload.
|
The playground creates prerequisite credentials to keep the flow self-contained. In production, the human subject and software actor establish credentials through their own identity lifecycles. |
Step 5: Exchange the subject and actor context for an ID-JAG
The workflow needs to cross into another authorization domain. The client performs token exchange using Sarah’s ID token as the subject_token and the gateway credential as the actor_token:
POST /idp/token
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
subject_token=<id_token>
subject_token_type=urn:ietf:params:oauth:token-type:id_token
actor_token=<actor_token>
actor_token_type=urn:ietf:params:oauth:token-type:access_token
requested_token_type=urn:ietf:params:oauth:token-type:id-jag
audience=https://.../as-b
client_id=playground-demo-client
dpop_jkt=kOG4j4ejqVDIY8rYxtqtme3e1TqPJmBhhLm2tRCh2zs
This is an ID-JAG exchange because the requested_token_type is set to urn:ietf:params:oauth:token-type:id-jag. The response confirms that the issued_token_type matches and that the JWT typ is set to oauth-id-jag+jwt. The resulting ID-JAG includes the subject, actor, audience, and DPoP key binding:
{
"sub": "demo-user@example.com",
"cnf": {
"jkt": "kOG4j4ejqVDIY8rYxtqtme3e1TqPJmBhhLm2tRCh2zs"
},
"act": {
"sub": "api-gateway@example.com",
"iss": "https://.../idp"
},
"aud": "https://.../as-b"
}
The ID-JAG preserves several pieces of context:
-
sub: Sarah remains the subject. -
act: The API gateway remains visible as the actor. -
aud: The ID-JAG is intended for authorization server B. -
cnf.jkt: The assertion remains bound to the DPoP key.
The software workload doesn’t become Sarah. The delegated relationship remains explicit as the request crosses the authorization boundary.
Step 6: Present the JWT grant with DPoP proof for an authorization decision
The client presents the ID-JAG to authorization server B. The ID-JAG is used as a JWT bearer authorization grant, while DPoP independently proves possession of the bound key:
POST /as-b/token
DPoP: <dpop_proof>
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
assertion=<id_jag>
scope=read write
Authorization server B validates the ID-JAG, verifies that it’s the intended audience, evaluates the delegated identity context, validates the DPoP proof, and applies its own authorization policy. If the request is allowed, authorization server B issues an access token:
{
"sub": "demo-user@example.com",
"scope": "read write",
"cnf": {
"jkt": "kOG4j4ejqVDIY8rYxtqtme3e1TqPJmBhhLm2tRCh2zs"
},
"iss": "https://.../as-b",
"aud": "https://.../rs"
}
This is an important transition in the flow. The local access token, issued by authorization server B for the resource server, maintains the DPoP key binding.
Sarah’s identity has crossed the authorization boundary, but authorization control hasn’t. The target domain still decides what access to grant to the resources it protects.
Step 7: Access the protected resource with DPoP
The client calls the protected API, sending the DPoP-bound access token and a fresh DPoP proof:
GET /rs/api/data
Authorization: DPoP <access_token>
DPoP: <fresh_dpop_proof>
The proof is specific to this API request and includes an ath claim binding it to the presented access token:
{
"htm": "GET",
"htu": "https://.../rs/api/data",
"ath": "<hash_of_access_token>"
}
This enables the resource server to validate whether the token is valid for its use, and whether the caller presenting it can prove possession of the key the token is bound to. When those checks succeed, the playground returns:
{
"message": "Access granted to protected resource",
"user": "demo-user@example.com",
"scope": "read write",
"token_type": "DPoP"
}
The chain reaches the protected resource without turning Sarah’s original credential into a token that every system is expected to accept.
Bringing it back to enterprise AI
Return to Sarah’s original request to prepare for the Apex Logistics meeting. The AI assistant needs Salesforce opportunity information and ServiceNow support history. An MCP gateway might broker those calls across two distinct paths. In the Salesforce path:
-
Sarah initiates the request to the AI assistant.
-
The MCP gateway brokers the call.
-
The authorization domain issues a Salesforce-targeted ID-JAG.
-
The target authorization domain applies local policy to protect Salesforce access.
-
The Salesforce-facing API is reached using the DPoP-bound local access token.
ServiceNow information can be accessed using the same pattern. For a target authorization environment, each path in the pattern establishes its own delegated access.
|
The playground provides an architectural example of a target authorization environment protecting Salesforce access. This doesn’t imply that Salesforce natively implements this particular protocol combination. |
How cross-domain access solves trust questions
To summarize the security properties of the full cross-domain access flow:
-
Sarah remains the human subject.
-
The MCP or API gateway remains identifiable as the software actor.
-
RAR constrains what the workflow is asking to do.
-
ID-JAG carries the delegated user identity into the target authorization domain.
-
The target authorization server makes its own access decision and issues its own token.
-
DPoP keeps the credential bound to the caller that possesses the corresponding private key.
Salesforce doesn’t need to accept Sarah’s original token. ServiceNow doesn’t inherit Salesforce’s authorization decision. The MCP gateway doesn’t need to impersonate Sarah.
Key takeaways
Sarah experiences a simple interaction with the AI assistant. Behind Sarah’s request, the architecture makes a series of deliberate security decisions. Each protocol solves a different part of the problem, and together they form a complete cross-domain access flow.
| Protocol | Description |
|---|---|
PAR |
Establishes the authorization request directly with the server. |
PKCE |
Protects authorization-code redemption. |
RAR |
Constrains what the workflow requests. |
Token Exchange |
Combines subject and actor context for the next authorization domain. |
ID-JAG |
Carries delegated user identity across the authorization domain boundary. |
Target authorization server |
Applies local policy and issues a local access token. |
DPoP |
Binds credentials to the caller’s key. |
Sarah’s identity remains visible, the software actor remains distinct, authorization stays narrow, the target system retains control over its resources, and the credentials used along the way are protected against bearer-token replay.
For enterprise AI operating across multiple systems, this is more than giving an agent access. Full cross-domain access builds an access path that the enterprise can trust.