Skip to main contentArrow Right
AgentCore Descope Blog Thumbnail

Table of Contents

Summarize with AI

Don't have the time to read the entire post? Our human writers will be sad, but we understand. Summarize the post with your preferred LLM here instead.

Amazon Bedrock AgentCore is AWS’s managed platform for running agents in production. AgentCore Identity manages workload identities, stores credentials in a token vault, and validates inbound JWTs.This covers the needs of most agents that live in AWS. 

But while non-AWS agents can register, they still need AWS credentials to participate; every identity operation routes through AWS APIs. Cognito, the default authorization server for agent-facing resources, lacks Client-Initiated Backchannel Authentication (CIBA), Dynamic Client Registration (DCR), and out-of-band approval. 

Bottom line: The further you extend your agent fleet from AWS, the harder this identity layer is to observe and maintain. 

Descope resolves this with cloud-neutral agent identity management, a credential vault that works across every runtime, and an authorization server that covers what Cognito doesn’t.

What Descope adds to AgentCore

The Descope Agentic Identity Hub provides several capabilities (Policies, Connections, cloud-neutral agent directory) that address specific gaps in AgentCore’s native identity capabilities.

Cloud-neutral agent directory

Every cloud’s native agent identity is scoped to its own ecosystem. AgentCore is no different; non-AWS-native agents need AWS credentials and routing to exist as entries. At the same time, AgentCore’s directory provides a very basic inventory of agents: workload identity names, creation timestamps, the source that created it (Runtime, Gateway, manual).

The Descope Agentic Identity Hub is a single repository for every agent in your fleet: the agent on AgentCore, the Vertex AI agent, the Foundry agent, the LangGraph prototype. Descope lets you manage these agents in the same directory, with one policy engine and unified audit trails. The Descope agent directory is runtime-neutral by design: each entry carries issuance-time policy and a credential vault, and the integration point with any runtime (AgentCore included) is a standard OIDC discovery URL.

Cloud neutral agent directory
Fig: Cloud-neutral agent directory

AgentCore Identity scopes which credential providers a workload can read, but it doesn’t evaluate at issuance whether this user invoking this agent should yield a token carrying this scope. That per-request, per-user, per-scope decision is a core tenet of agentic authorization.

Descope Policies handle agent token requests by evaluating the directory entry, the invoking user’s roles and tenant, and the requested scopes. Policies decide token contents at issuance, before the token exists. An agent can’t escalate by asking, and users can’t delegate what they don’t hold. The unit of permission is scope, not the SSO group; your Okta or Entra groups feed the decision as inputs rather than acting as grants.

Defining access control policies for AgentCore agents
Fig: Defining access control policies for AgentCore agents

One credential manager

Descope Connections vault OAuth tokens for third-party services and static API keys for internal systems. Any agent in the fleet retrieves credentials through the same token exchange, governed by the same policy. The agent exchanges its token for a scoped credential at call time. Nothing long-lived stays in any agent’s environment, and the raw secret never enters a model’s context.

One credential manager
Fig: Descope credential vault

Descope also handles authentication for users. Federate your corporate IdP through SSO, and agent access dies with the directory account when someone leaves. Passkeys, magic links, one-time passwords (OTP), and social login are available for customer-facing agents. The agent Security Token Service (STS) issues short-lived, delegated tokens carrying user context without user credentials, and Descope Flows handle the consent screens, step-up, multi-factor authentication (MFA), and CIBA out-of-band approvals for headless agents.

Fig Federate your corporate IdP through SSO Setup Suite
Fig: Federate your corporate IdP through SSO Setup Suite

Resource authorization beyond Cognito

Your MCP servers and backend APIs need an authorization server. Cognito supports three grant types: implicit (legacy type), client credentials (machine-to-machine), and authorization code. But Cognito doesn’t give you CIBA, DCR, or out-of-band approval. Token customization requires Lambda triggers. With Cognito as the default option for AgentCore, B2B multi-tenancy with per-customer IdP federation is a capability you’d have to build from scratch.

Fig CIBA flow in Descope
Fig: CIBA flow in Descope

Descope MCP Auth implements the MCP authorization spec with OAuth 2.1, including DCR and Client ID Metadata Documents (CIMD), which allows off-the-shelf clients like Claude Desktop to register themselves. The same project protects backend APIs with resource-level access control, CIBA for async approval, and per-tenant SSO that customers configure themselves.

Why use both AgentCore Identity and Descope

Descope augments AgentCore by sidestepping its ecosystem restraints, making agents more observable, and enabling more granular policy enforcement. Descope’s cloud-neutral directory, unified credential management, and extension of resource authorization significantly enhance AgentCore’s native agentic identity functions.

That said, there are a few things AgentCore Identity does that no external platform can replicate. AgentCore Runtime attests to each agent’s identity at startup and injects its token without bootstrap credentials or secret-zero problems. For agents calling AWS services, the authorization path is IAM and SigV4 end to end, which no other platform can replicate. 

In a model using both AgentCore and Descope for agentic identity, AgentCore Identity is the runtime’s identity layer. It handles workload attestation inside AWS and IAM authorization against AWS services. Descope is the fleet’s directory and policy layer. Descope and AgentCore Identity stack at the inbound JWT authorizer, which validates whatever OIDC provider you configure.

Both can be used together gracefully, and implementation is a straightforward process covered in the following section.

Integrating Descope and AgentCore

The full flow has three phases: the user authenticates and consents in the browser, the app invokes the agent with the resulting token, and the agent fetches scoped credentials as it works. We'll take them one at a time.

Getting started

To get started building with Descope and AgentCore:

  1. Create a Descope project. Sign up and copy your Project ID from Settings > Project.

  2. Connect your corporate IdP. In the console, select your tenant under Tenants, then navigate to the SSO Setup Suite Configurations and generate a link for the SSO Setup Suite. Go to that link, and follow thesetup guide for your Corporate IdP within the SSO Setup Suite to configure SSO and the user and group attributes so IdP groups map to Descope roles.

  3. Create an Agentic Client. Go to Clients, click + Add Client, and name it. Open the app's Connection Information section and copy three system-generated values you'll use below: Client ID, Client Secret, and Discovery URL. You can tag each by runtime if you want to as well.

  4. Define your Resources. Go to Resources, click + Resource, and choose API or MCP Server. For an API resource, set the Resource identifier (for example https://crm.internal.example.com), define its OAuth scopes (crm:read, crm:write), and use Role association to map each scope to the Descope roles allowed to hold it. This mapping is the policy the control plane enforces at token issuance.

  5. Configure Connections for third-party and internal services. Go toConnections, click + Connections, and pick from the library (for example Google Calendar) or create a custom connection for an internal API key. Note the Connection ID in Connection Details; it's set at creation and not editable afterward, and it's the app_id your agent code passes when fetching credentials.

  6. Point AgentCore at Descope. In your AgentCore deployment, set discoveryUrl to the Discovery URL you copied in step 3, with allowedClients and allowedAudience set to your Descope client and audience.

  7. Front your MCP servers and backend APIs with Descope MCP Auth.

  8. Replace hardcoded credentials in agent tools with Connections token fetching or Resource token exchange (code below).

The docs for MCP Auth, Resources, and Connections cover this configuration in more depth. 

User authentication and agent invocation

User auth and agent invocation AgentCore Descope
Fig: User auth and agent invocation

First, create the Client in Descope that represents your Bedrock agent (if you haven't already in step 3 of Getting Started): go to Clients in the Agentic Identity Hub, create a client for the agent, and copy its Client ID. You'll need it for the azp claim validator below.

Then, configure Inbound Auth in the AWS console by following these steps:

  • In the AWS Console, open Amazon Bedrock AgentCore and select Runtime in the left navigation.

  • Click the orange Host agent/tool button in the top right corner.

  • Fill out the Agent or Tool details section and the Agent Source and Permissions section for your agent.

  • Under Inbound Auth, select HTTP as the protocol and Use JSON Web Tokens (JWT) as the Inbound Auth Type.

  • Two JWT schema configuration settings appear. Select Use existing Identity provider configurations, then paste your Descope project's Discovery URL (copied from your Inbound App's Connection Information in step 3 of Getting Started):

  https://api.descope.com/v1/apps/{project-id}/.well-known/openid-configuration
  • Optionally, under Allowed audiences, add the Resource audience if applicable.

  • Add a custom claim validator: set the claim to azp and the value to the Client ID you copied from the Clients page in Descope.

  • Finish hosting the agent.

The main Inbound Auth configuration UI in AWS Bedrock Runtime
Fig: The main Inbound Auth configuration UI in AWS Bedrock Runtime
The azp claim, which needs to map to the Descope Client ID of your Agentic Client
Fig: The azp claim, which needs to map to the Descope Client ID of your Agentic Client

If you’re deploying programmatically, the starter toolkit takes the same values:

# agentcore_deployment.py
response = agentcore_runtime.configure(
    entrypoint="agentcore_agent.py",
    auto_create_execution_role=True,
    auto_create_ecr=True,
    requirements_file="requirements.txt",
    region=region,
    agent_name=agent_name,
    authorizer_configuration={
        "customJWTAuthorizer": {
            "discoveryUrl": f"https://api.descope.com/v1/apps/{os.getenv('DESCOPE_PROJECT_ID')}/.well-known/openid-configuration",
            "allowedClients": [os.getenv("DESCOPE_CLIENT_ID")],
            "allowedAudience": [os.getenv("DESCOPE_AUDIENCE")]
        }
    }
)

If your agent reaches tools through an AgentCore Gateway, the gateway's inbound auth gets the same Descope configuration. In the AWS console:

  1. Open Amazon Bedrock AgentCore and select Gateways, then create a gateway or open your existing one.

  2. Under Inbound Identity details -> Use existing Identity provider configurations, enter the same Descope Discovery URL you used for the Runtime.

  3. Add a Custom claim: name azp, type String, value set to the Client ID from your Descope Clients page. This ensures only tokens issued to your agent's client pass validation.

After that, there's nothing else to configure. The gateway will fetch the issuer and signing keys from the discovery document automatically. Creating the gateway programmatically takes the same values:

gateway = client.create_gateway(
    name="internal-tools-gateway",
    authorizerType="CUSTOM_JWT",
    authorizerConfiguration={
        "customJWTAuthorizer": {
            "discoveryUrl": f"https://api.descope.com/v1/apps/{os.getenv('DESCOPE_PROJECT_ID')}/.well-known/openid-configuration",
            "allowedClients": [os.getenv("DESCOPE_CLIENT_ID")],
        }
    },
    # ... protocol and target configuration
)

Your app runs a standard OIDC authorization code flow with Descope as the provider. The user authenticates and consents in the browser before invocation; this is ordinary OIDC client work, and the Using Inbound Apps guide walks through the authorization code flow step by step, with the Authorization Server reference covering the endpoints. For browserless clients, the device code flow fills the same role. CIBA handles scenarios where the agent needs mid-session approval that no prior consent covers.

The AgentCore-specific part is what you do with the resulting access token: pass it as the bearer on the runtime call, where the JWT authorizer you configured above validates it.

# After the OIDC callback yields access_token:
return requests.post(
    AGENT_RUNTIME_URL,
    headers={"Authorization": f"Bearer {access_token}"},
    json={"prompt": user_prompt},
).json()

Fetching credentials inside agent tools

Fetching credentials inside agent tools
Fig: Fetching credentials inside agent tools

Inside agent tools, credential retrieval depends on the target. For Descope-protected resources, the agent performs OAuth Token Exchange. Policies checks the user, agent, and requested scope; a refused scope never becomes a token.

DESCOPE_TOKEN_URL = "https://api.descope.com/oauth2/v1/apps/token"

def exchange_for_resource_token(user_access_token: str, resource: str, scopes: list[str]) -> str:
    """RFC 8693: trade the user's access token for a scoped access token
    targeting a Descope-protected resource. Policy is applied at issuance."""
    resp = requests.post(DESCOPE_TOKEN_URL, data={
        "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
        "client_id": os.getenv("DESCOPE_CLIENT_ID"),
        "client_secret": os.getenv("DESCOPE_CLIENT_SECRET"),
        "subject_token": user_access_token,
        "subject_token_type": "urn:ietf:params:oauth:token-type:access_token",
        "resource": resource,  # Resource identifier from the console (RFC 8707)
        "scope": " ".join(scopes),
    })
    resp.raise_for_status()
    return resp.json()["access_token"]

def query_crm(user_access_token: str) -> dict:
    crm_token = exchange_for_resource_token(
        user_access_token, "https://crm.internal.example.com", ["crm:read"]
    )
    headers = {"Authorization": f"Bearer {crm_token}"}
    return requests.get(f"{os.getenv('CRM_API')}/records", headers=headers).json()

For third-party services, OAuth tokens, or static API keys, the agent fetches from the Connections vault with the outbound application SDK. 

def get_calendar_information(session_token: str) -> dict:
    """Third-party OAuth: fetch the user's vaulted Google Calendar token from Connections."""
    user = descope_client.validate_session(session_token=session_token)
    vaulted = descope_client.mgmt.outbound_application.fetch_token(
        app_id="google-calendar",            # Connection ID from the console
        user_id=user["sub"],
    )
    headers = {"Authorization": f"Bearer {vaulted['accessToken']}"}
    return requests.get( "https://www.googleapis.com/calendar/v3/calendars/primary/events", headers=headers, params={"maxResults": 10, "orderBy": "startTime", "singleEvents": True}, ).json()

def call_internal_api(session_token: str) -> dict:
    """Static API key: same vault, same fetch, API-key connection."""
    user = descope_client.validate_session(session_token=session_token)
    vaulted = descope_client.mgmt.outbound_application.fetch_token(
        app_id="internal-reporting-api",
        user_id=user["sub"],
    )
    headers = {"X-API-Key": vaulted["accessToken"]}
    return requests.get(f"{os.getenv('INTERNAL_API_URL')}/reports", headers=headers).json()

Gating sensitive actions with CIBA

Gating sensitive actions with CIBA
Fig: Gating sensitive actions with CIBA

For sensitive actions, CIBA provides out-of-band human approval. This runs as a tool inside your AgentCore agent: when the LLM decides an elevated operation is needed (an account reset, a payment), the tool blocks until a human approves. Descope delivers the approval as an email link with a roughly three-minute expiration. The initiation endpoint is published in your Descope client's discovery document as backchannel_authentication_endpoint.

# agentcore_agent.py: tool gating a sensitive action behind human approval
def request_step_up(login_hint: str, binding_message: str) -> dict:
    """CIBA: out-of-band human approval for a sensitive action."""
    init = requests.post(BACKCHANNEL_AUTH_URL, data={
        "client_id": os.getenv("DESCOPE_CLIENT_ID"),
        "client_secret": os.getenv("DESCOPE_CLIENT_SECRET"),
        "login_hint": login_hint,
        "binding_message": binding_message,  # shown to the user with the approval request
        "scope": "openid",
    }).json()

    for _ in range(18):  # approval links expire after ~3 minutes
        time.sleep(10)
        poll = requests.post(DESCOPE_TOKEN_URL, data={
            "grant_type": "urn:openid:params:grant-type:ciba",
            "client_id": os.getenv("DESCOPE_CLIENT_ID"),
            "client_secret": os.getenv("DESCOPE_CLIENT_SECRET"),
            "auth_req_id": init["auth_req_id"],
        })
        if poll.status_code == 200:
            return {"status": "approved", "token": poll.json()["access_token"]}
        if poll.json().get("error") not in ("authorization_pending", "slow_down"):
            return {"status": "denied"}
    return {"status": "timeout"}

Start building with Descope and AgentCore

AgentCore gives your agents a production runtime. Descope gives them one identity layer across every cloud they touch, with policy evaluated at token issuance. Together, they separate where an agent runs from what it’s allowed to do.

Sign up and explore the docs for Agentic Identity, MCP Auth, Connections, and Resources. Running agents on more than one cloud and want a unified directory for all of them? Reach out to our team or connect with us on our dev community, AuthTown.