hiw3c 日报精选
AWS ML Blog RSS 全文

Govern AI agent access with Okta Cross App Access and Amazon Bedrock AgentCore – Part 1

An AI agent that acts for a user raises an identity question that per-app OAuth and service accounts leave open: which agent may act for which user, and how you stop it. In this first post of two, you

来源:AWS ML Blog · aws.amazon.com

正文为英文,可一键机器翻译(仅首次需要等待)

An AI agent that updates a ticket or reads a customer record acts for a person, inside applications that person can reach. Your identity team then has to answer a question that per-app OAuth consent and shared service accounts leave open: which agent might act for which user in which application, and how you stop it. Okta Cross App Access (XAA) answers it in your identity provider (IdP). You register the agent in Okta, an administrator approves the applications it might reach, and Okta evaluates policy each time the agent asks to act for a user. In our tests, after an administrator removed a user’s assignment to the agent, Okta refused the agent’s new requests for that user within about 1 second, and tokens already issued expired within 5 minutes.

Amazon Bedrock AgentCore is a platform to build, connect, and optimize agents at scale, with any framework or model. This solution uses two of its capabilities to carry out Okta’s decision on AWS. Amazon Bedrock AgentCore runtime, a capability of Amazon Bedrock AgentCore, validates Okta’s grant. Amazon Bedrock AgentCore Identity, a capability of Amazon Bedrock AgentCore, redeems it for a short-lived access token with credentials your agent code doesn’t see, and keeps that token in its token vault. In our tests, AgentCore Identity added about 0.6 seconds to a request that took about 30 seconds end to end.

In this post, the first of two, you build a task agent on AgentCore runtime that acts for signed-in users through XAA and AgentCore Identity. You configure the AgentCore Identity parts it needs, enforce each user’s authority in the tools, and see what we measured: what each check refuses, the practices we settled on, and how fast Okta’s controls take effect. Part 2 then puts the same governance behind Amazon Bedrock AgentCore Gateway, a capability of Amazon Bedrock AgentCore, with Cedar policies, a second agent on Amazon Elastic Kubernetes Service (Amazon EKS), and an audit trail in Amazon CloudWatch.

Solution overview

Okta is the governance plane: it decides which agent might act for which user, against which resource. AgentCore Identity is the execution plane: it holds the agent’s credentials, turns Okta’s decision into a token the resource accepts, and keeps both identities in that token. In Figure 1, the agent and the two Model Context Protocol (MCP) servers it calls all run on AgentCore runtime.

Okta governance plane and AgentCore Identity execution plane, with the agent and two MCP servers running on AgentCore runtime

Figure 1: Solution architecture, with numbers matching the steps that follow

Each request carries three tokens, and each records a different decision: the ID token says who the user is, the Identity Assertion JWT Authorization Grant (ID-JAG) is Okta’s decision, and the access token is what the resource accepts. One request flows like this:

  1. The user signs in to the web front end, a backend for frontend (BFF), with OpenID Connect through the agent’s linked Okta app.
  2. For each request, the BFF exchanges the user’s ID token for an ID-JAG at Okta’s org authorization server, authenticated as the agent (RFC 8693 token exchange). Okta checks that the agent is active, that the user is assigned to it, and that an administrator connected it to the target authorization server for the requested scopes. The ID-JAG is single-use and expires in 5 minutes.
  3. The BFF invokes the agent on AgentCore runtime with the ID-JAG as its bearer token. The runtime’s JWT authorizer accepts only ID-JAGs that Okta issued to this agent.
  4. The runtime gets a workload access token from AgentCore Identity that binds the agent’s workload identity to the user, and passes it to the agent code. This workload access token is an AWS-internal binding token: neither Okta nor the resource sees it, so it isn’t one of the three tokens.
  5. The agent asks AgentCore Identity for an access token. AgentCore Identity redeems the ID-JAG at the resource’s Okta custom authorization server with the JWT authorization grant (RFC 7523), authenticated as the agent. Okta returns a 5-minute access token that names both the user and the agent and carries the user’s groups.
  6. The agent calls the MCP servers with that token. The task manager checks that a registered agent obtained it, and that the user’s group and scope allow the tool.

Figure 2 shows the same request as a sequence, with the check that each party makes.

Sequence of the three-token chain showing the ID token, ID-JAG, and access token, and the check each party makes

Figure 2: The three-token chain and the check each party makes, with the step 4 workload access token staying inside AWS

Prerequisites

To follow along, you need the following:

Prerequisite What we used
An AWS account with access to Amazon Bedrock AgentCore and an Anthropic Claude model in Amazon Bedrock US West (Oregon)
An Okta org with Cross App Access for AI agents and API Access Management (custom authorization servers) An Okta Integrator Free Plan org
Python, the AWS Command Line Interface (AWS CLI), boto3, the AgentCore CLI, and the AgentCore Python SDK, a capability of Amazon Bedrock AgentCore Python 3.11, AWS CLI version 2, boto3 1.43, AgentCore CLI 0.31.1, and AgentCore Python SDK 1.24.1
AWS Identity and Access Management (IAM) permissions Permission to create AgentCore resources and IAM roles

The walkthrough takes about 1 hour. AgentCore charges for what you use. See Amazon Bedrock AgentCore pricing.

The base for this solution is the AWS sample 08-okta-xaa-delegation-chain in the AgentCore samples repository on GitHub, and you can build the solution in this post on top of it. Beyond that sample, the snippets and configurations in this post show the pattern conceptually. They aren’t a complete, deployable solution. The code is sample code for non-production use, so work with your security and legal teams to meet your organization’s security, regulatory, and compliance requirements before you deploy it.

Register the agent in Okta

Okta needs an authorization server for the resource, the agent, a connection between them, and a policy that admits the agent’s grant. Do all of these steps in the Okta Admin Console: registering the agent creates its linked app, so you don’t need an app integration wizard.

  1. Create a custom authorization server for the resource application with the audience api://task-manager and the custom scopes xaa:read and xaa:write. Request only the resource’s custom scopes: Okta rejects an ID-JAG request that includes OpenID Connect scopes such as openid or profile (invalid_scope), so a request needs at least one custom scope and no OpenID Connect scopes.
  2. Add a groups claim to its access tokens, filtered to groups that start with xaa-. This claim carries each user’s authority to the tools.
  3. In the Admin Console, go to Directory > AI Agents, choose Register AI agent and then Register manually, optionally assign an owner, and create a linked OpenID Connect app. On Client registration, choose Client secret, generate a secret, and then activate the agent. The agent and the app share one client ID, and that app must sign your users in: an ID token from another app fails the exchange with “the client application is not registered for delegation to this agent.”
  4. On the agent’s Resource connections tab, connect it to the authorization server, limited to the scopes it needs, as Figure 3 shows.
  5. Add an access policy rule to the custom authorization server that allows the agent’s JWT Bearer grant, with a 5-minute access token lifetime. Without the grant type, the second exchange fails with no_matching_policy, and the lifetime bounds how long a revoked delegation keeps working.
  6. Assign the users or groups who might act through the agent. They sign in through the linked app, so give it an app sign-in policy that admits them with the authenticators they’ve enrolled. In our sandbox org, the linked app had its own policy with a rule for the test group. For real users, apply your organization’s policy, including multifactor authentication.
Okta resource connection for the task agent, limited to the xaa:read and xaa:write scopes

Figure 3: The task agent’s resource connection in Okta, limited to the xaa:read and xaa:write scopes

Okta’s Cross App Access guide covers the console steps, and the AWS sample 08-okta-xaa-delegation-chain scripts the authorization server and its JWT bearer policy. The BFF then requests the ID-JAG, authenticated as the agent:

POST /oauth2/v1/token HTTP/1.1
Host: YOUR-ORG.okta.com
Authorization: Basic <the agent's client ID and secret>
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&requested_token_type=urn:ietf:params:oauth:token-type:id-jag
&subject_token=<the user's ID token>
&subject_token_type=urn:ietf:params:oauth:token-type:id_token
&audience=https://YOUR-ORG.okta.com/oauth2/YOUR-CUSTOM-AS-ID
&scope=xaa:read xaa:write

The AWS sample 06-okta-xaa sends the same request in Python (agent/xaa_client.py). The BFF authenticates as the agent, so store the agent’s client secret in AWS Secrets Manager rather than in code or configuration files.

Set up AgentCore Identity

AgentCore Identity holds three things for this solution, and your agent code doesn’t see the client secret or call Okta’s token endpoint:

Part of AgentCore Identity What it holds Who creates it
Workload identity The agent’s identity on AWS, bound to the signed-in user for each request AgentCore runtime, for each agent runtime
OAuth2 credential provider The agent’s Okta client ID and secret, and the grant to use You, once per Okta agent
Token vault The access tokens the provider obtains AgentCore Identity

Create the credential provider

The credential provider performs the second exchange. Point it at the custom authorization server, authenticate it as the AI agent, and set the on-behalf-of grant to JWT_AUTHORIZATION_GRANT, which AgentCore Identity sends as jwt-bearer with the ID-JAG as the assertion:

control = boto3.client("bedrock-agentcore-control")
provider = control.create_oauth2_credential_provider(
    name="okta-xaa-obo",
    credentialProviderVendor="CustomOauth2",
    oauth2ProviderConfigInput={"customOauth2ProviderConfig": {
        "oauthDiscovery": {"discoveryUrl": f"{OKTA_ORG}/oauth2/{CUSTOM_AS_ID}/.well-known/openid-configuration"},
        "clientId": AGENT_CLIENT_ID,          # the AI agent's Okta client ID
        "clientSecret": AGENT_CLIENT_SECRET,  # AgentCore Identity stores it in AWS Secrets Manager
        "clientAuthenticationMethod": "CLIENT_SECRET_BASIC",
        "onBehalfOfTokenExchangeConfig": {"grantType": "JWT_AUTHORIZATION_GRANT"},
    }},
)
provider_arn = provider["credentialProviderArn"]

Okta binds both exchanges to the client named in the ID-JAG, so each Okta agent needs its own provider: when we redeemed one agent’s ID-JAG as another, Okta refused with “The ‘client_id’ in the JWT Bearer Grant must match the ‘client_id’ used to authenticate the client.” AgentCore Identity keeps the secret in a service-managed AWS Secrets Manager secret, which you rotate with update-oauth2-credential-provider. If your team already manages the client secret in Secrets Manager, you can reference that secret’s ARN instead. AgentCore Identity reads the client secret from it, so you can rotate the secret without updating the provider (see Reference your own AWS Secrets Manager secrets in Amazon Bedrock AgentCore Identity). To avoid a shared secret, authenticate with a private key instead. See Authenticate with Private Key JWT using Amazon Bedrock AgentCore Identity. Figure 4 shows the provider in the AgentCore console.

AgentCore console view of the credential provider configured with the JWT authorization grant

Figure 4: The credential provider authenticating as the agent with the JWT authorization grant

Use the workload identity that AgentCore runtime manages

AgentCore runtime creates and manages a workload identity for each agent runtime. When a request passes the runtime’s JWT authorizer, the runtime gets a workload access token for that workload identity and the inbound token’s issuer and subject, and passes it to your code. AgentCore Identity then uses the ID-JAG behind it as the assertion for the exchange.

The AgentCore Python SDK keeps the workload access token in BedrockAgentCoreContext, not in the request headers that your entrypoint receives. Our first version looked for it in the headers, found nothing, and fell back to its own GetWorkloadAccessTokenForJWT call with a second workload identity, on all 21 requests we checked. Read it from the context instead:

from bedrock_agentcore.runtime import BedrockAgentCoreContext

identity = boto3.client("bedrock-agentcore")

def okta_access_token() -> str:
    workload_token = BedrockAgentCoreContext.get_workload_access_token()  # minted by the runtime
    return identity.get_resource_oauth2_token(
        workloadIdentityToken=workload_token,
        resourceCredentialProviderName="okta-xaa-obo",
        scopes=["xaa:read", "xaa:write"],  # must equal the ID-JAG's scopes
        oauth2Flow="ON_BEHALF_OF_TOKEN_EXCHANGE",
    )["accessToken"]

AgentCore Identity keeps the access token in its token vault for the rest of the request. Across 10 requests, the agent’s first call redeemed the ID-JAG in about half a second (see the latency table in Validate the results), and the second, for the other MCP server, returned the same token in a median of 58 milliseconds without redeeming the single-use ID-JAG again. A new ID-JAG for the same user got a new token, so each request reflects Okta’s latest decision. The sample 08-okta-xaa-delegation-chain reads the workload access token the same way (agent/agent.py).

Scope the agent’s role to its workload identity and provider

AgentCore Identity doesn’t bind workload identities to credential providers in the same account. The agent’s IAM role does, so scope it to the agent’s own workload identity and provider, and to that provider’s secret:

[
  {
    "Sid": "OktaOnBehalfOf",
    "Effect": "Allow",
    "Action": "bedrock-agentcore:GetResourceOauth2Token",
    "Resource": [
      "arn:aws:bedrock-agentcore:us-west-2:123456789012:workload-identity-directory/default",
      "arn:aws:bedrock-agentcore:us-west-2:123456789012:workload-identity-directory/default/wor