Table of Contents

Amazon Bedrock AgentCore Identity on EC2: A Step-by-Step GitHub OAuth Guide

TL;DR: Amazon Bedrock AgentCore Identity can handle identity, OAuth, and token storage for agents that need to reach external services, but self-hosted compute adds setup work. This guide walks through an EC2 agent using a workload identity to obtain a GitHub OAuth token, complete the callback flow, retrieve the credential from Token Vault, and use it to access GitHub. Amazon Bedrock AgentCore Identi…

Víctor Jiménez
Víctor Jiménez

Technical Writer at RootNode

Summarize:

Read
0%
A professional in a modern office works at a laptop displaying Amazon Bedrock and GitHub access configuration, with coworkers blurred in the background.

Table of Contents

Read
0%

Today we’ll explore how to use the identity service from Amazon Bedrock AgentCore to manage credentials that an AI agent running on EC2 can use to connect with an external service like GitHub.

Amazon Bedrock is the AWS cloud computing suite of services for building and deploying AI applications and generative artificial intelligence agents.

AgentCore is an AWS platform for Amazon Bedrock that makes it easier to build, deploy, and orchestrate AI agents.

Although AgentCore services are designed to work together to build a whole agent, you can use a single service from AgentCore within an existing agent. However, there are some challenges in integrating with AgentCore, and that’s what we’ll cover in this article.

We’ll follow a practical example to review how it’s done internally:

  • Build a scenario in which an agent runs on EC2.
  • The agent will call the AgentCore Identity API to retrieve an access token.
  • The agent will use this token to reach outside of AWS.

What is Amazon Bedrock AgentCore Identity

Within Bedrock, the AgentCore Identity service handles authentication for AI agents.

Its main features are:

  • A centralized identity directory that provides an experience focused on agents, while using Cognito under the hood.
  • A secure credential store, token vault, to store elements like API keys and credentials.
  • Built-in OAuth 2.0 support, both 2-legged and 3-legged.
  • Token exchange system using on-behalf-of tokens.

In practice, this means your AI agents can access external resources without needing to manage sensitive information like OAuth client IDs and client secrets.

Using AgentCore Identity in EC2 agents

If you run your agent in AgentCore Runtime, workload identities are automatically created and managed for you. In this scenario, using AgentCore Identity is rather straightforward.

However, if you are running your agent in ECS, EC2, or Lambda, you’ll need to take some extra steps:

  • You’ll have to create a workload identity for your agent.
  • You’ll have to manage auth flows.
  • You’ll have to request access tokens via the AWS API.

It takes a bit of manual work, but it’s doable. Let’s see the process in detail with an example.

How to Authenticate with AgentCore Identity in Self-Hosted Compute

We’ll explore a scenario in which an Agent running on EC2 wants to connect to the GitHub API.

The actors in this scenario are:

  • The agent:
    • A self-hosted agent running in EC2 that wants to access the GitHub API.
    • It’s a Python application exposed by an NGINX proxy.
    • Will need a workload identity to receive authentication tokens from AgentCore.
  • GitHub:
    • Its API server is the resource server the agent wants to access.
    • An authorization server gatekeeps the API.
    • We’ll access it through our own OAuth App.
  • AgentCore:
    • A resource provider that stores the GitHub OAuth App’s config as an Outbound Auth.
    • An OAuth client to request access tokens to GitHub.
    • Will store the tokens in Token Vault.
    • Finally, it will act as an identity service, providing the access tokens to the agents.

To make our scenario easier (and cheaper) to replicate, we’ve taken some shortcuts:

  • Our agent won’t have a UI, and won’t connect to an LLM. It has only the minimal code needed to manage access to the external resource.
  • We’ll use a user account. In a production environment you must attach an IAM role to our EC2 instance.

Also note that:

  • For educational purposes, our agent will output sensible information in the console. This should stay private in a real application.

The Authentication Flow

Two parallel flows will take place in our scenario. A regular OAuth flow between AgentCore Identity and GitHub, and the agent requesting the access token to AgentCore Identity via the AWS API.

We’ve split the flow into four sections:

  1. The agent requests an access token, but none is available.
  2. The user initiates the OAuth flow.
  3. AgentCore Identity notifies the agent that a token is available, and the agent retrieves the access token.
  4. The agent uses the access token.

Initial Setup

There are several pieces we need to put in place before running our agent:

  1. The GitHub OAuth App.
  2. Registering the OAuth App in AgentCore Identity.
  3. The EC2 instance.
  4. The workload identity.

If you are only interested in following the flow, skip ahead to the “Running the agent” section below.

Warnings:

  • This is an advanced guide intended for those already familiar with cloud infrastructure. Although it’s easy to follow, we skipped some explanations of the basics of AWS. Take time to check the documentation and understand the implications of each step.
  • This setup uses paid services. Although we’ll be using a t3.micro instance that falls into the free tier, the public IP is not free. While creating and testing this scenario, we generated around USD 0.40 in expenses.
  • Remember to destroy and terminate the infrastructure we are about to create once you are done. We took some shortcuts for educational purposes that don’t follow every security best practice; if these services are compromised, a malicious actor could exploit your infrastructure.

To register the GitHub OAuth App:

  1. In GitHub, go to Settings -> Developer Settings -> OAuth Apps.
  2. Register a new OAuth App:
    1. Values in the fields are not important right now.
    2. We’ll change the Redirect URI in the next step.
    3. Leave Enable device flow unchecked.
  3. Write down the Client ID and the Client Secret for the next step.

To register the OAuth App in AgentCore Identity:

  1. Log in to the AWS console.
  2. Navigate to Amazon Bedrock AgentCore -> Build -> Identity.
  3. Press on Add Outbound Auth, then Add OAuth Client.

4. Set the name to oauth-client. We’ll use this value in the agent’s code.
5. For Provider, choose: Included provider -> GitHub.

6. Paste the Client ID and Client Secret we generated in the previous step.
7. Press on Add OAuth client.
8. Copy the Callback URL.
9. Go back to the GitHub OAuth App and paste AgentCore’s callback URL in the Redirect URI field.

To create the EC2 instance:

  1. Log in to the AWS console.
  2. Navigate to EC2 -> Instances.
  3. Press on Launch instances.
  4. In Name and tags, set ec2-github-demo-agent as the name. We’ll use this name later on.
  5. In Application and OS Images, choose the Amazon Linux 2023 image.
  6. For Instance type, choose t3.micro.
  7. Choose either an existing Key pair or create a new pair.
    1. If you create a new pair, download the .pem file.
    2. Copy it to your .ssh folder.
    3. Then change its permissions with:
      chmod 0600 ~/.ssh/github-demo-agent-ec2.pem
  8. In Network settings, enable both HTTPS and SSH traffic.
  9. Review the configuration and press Launch instance.
  10. Once created, navigate to the instance details and copy the Public DNS. We’ll use it to access the instance later on. 

To create the workload identity:

  1. Open a terminal.
  2. Install the AWS CLI.
  3. Log in with aws login (or manually set up the credentials).
  4. Run this command to create the workload identity. Use the same name as the EC2 instance:
$ aws bedrock-agentcore-control create-workload-identity --name "ec2-github-demo-agent"
{
    "name": "ec2-github-demo-agent",
    "workloadIdentityArn": "arn:aws:bedrock-agentcore:us-east-1:XXXXXXXXXXXX:workload-identity-directory/default/workload-identity/ec2-github-demo-agent",
    "allowedResourceOauth2ReturnUrls": []
}

All these services can be deleted or terminated from the same UIs we used to create them, except for the workload identity. To delete it, run:

aws bedrock-agentcore-control delete-workload-identity --name "ec2-github-demo-agent"

Setting up the EC2 Instance

We have to set up a few things in our EC2 instance:

  1. Install the AWS CLI, Python, and NGINX.
  2. Register some AWS credentials.
  3. Point NGINX as an HTTPS proxy to our Python agent.
  4. Load the agent.

Start an SSH session to the instance using the .pem file from the key pair and the Public DNS as the address:

ssh ssh -i ~/.ssh/github-demo-agent-ec2.pem ec2-user@ec2-XX-XX-XX-XX.compute.amazonaws.com

To install the software, run:

sudo dnf install -y python3.11 python3.11-pip nginx awscli

To register AWS credentials:

You can log in using your own user by:

  1. Running aws login --remote.
  2. The command will output a URL.
  3. Open the URL in a browser and follow the steps to authenticate.
  4. Copy the code from the browser and paste it into the terminal.

Warning: This is a shortcut to keep things simple and keep the focus on the agent’s code. Ideally, you would assign an IAM role with minimal privileges to this instance and use its credentials to connect with the AWS API. Terminate this instance as soon as you are finished, as anyone with access to this instance will be able to use these credentials.

Setting up NGINX as an HTTPS Proxy

The AgentCore Identity service will use a callback in our agent to notify it that the OAuth flow has finished. For security, AgentCore requires that this callback uses HTTPS.

We’ll use an NGINX server as an HTTPS proxy for our agent.

To simplify the setup, we’ll use a self-signed certificate. As a result, your browser will throw a warning in step 4 of the flow when the OAuth flow finishes. In a production environment, you would set up proper DNS for the EC2 instance and use a tool like certbot to generate valid SSL certificates.

We already installed NGINX in a previous step; we just need to:

  1. Generate a self-signed certificate.
  2. Update the configuration.
  3. Start the server.

To generate a self-signed certificate, run:

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/nginx/nginx-selfsigned.key \
  -out /etc/nginx/nginx-selfsigned.crt

You’ll be prompted for several bits of information: Country code, Organization name, etc. You can leave all those fields empty, except for the Common Name one. Set the Common Name field to the Public DNS value of your instance. It will look something like: ec2-XX-XX-XX-XX.compute.amazonaws.com.

Then create a Diffie-Hellman key exchange with:

sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048

To update the configuration:

Replace the contents of /etc/nginx/nginx.conf with the following. The vim editor is available by default in the Amazon Linux image:

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    server {
        listen       443 ssl;
        server_name  _;

        location / {
                proxy_pass http://localhost:8080/;
        }

        ssl_certificate /etc/nginx/nginx-selfsigned.crt;
        ssl_certificate_key /etc/nginx/nginx-selfsigned.key;
        include /etc/nginx/options-ssl-nginx.conf;
        ssl_dhparam /etc/nginx/dhparam.pem;
    }
}

Then, create a /etc/nginx/options-ssl-nginx.conf file with this content:

ssl_session_cache shared:le_nginx_SSL:10m;
ssl_session_timeout 1440m;
ssl_session_tickets off;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;

ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";

To start the server:

The server is already configured. Just start it with:

sudo systemctl start nginx.service

Note that if you try to access the NGINX server, it will throw an error until we start the agent.

Loading up the agent

A few final steps before we can run our agent:

  1. Create a virtual environment for the Python dependencies.
  2. Gather our AWS user ID.
  3. Load the agent code.

In a previous step, we installed Python 3.11 (the default version is too old for our agent). We’ll work in a virtual environment to keep our libraries separate from the system ones.

To create the virtual environment for our agent and install the dependencies, run these commands:

$ python3.11 -m venv ~/agentcore-venv
$ source ~/agentcore-venv/bin/activate
(agentcore-venv)$ pip install --upgrade pip
(agentcore-venv)$ pip install bedrock-agentcore boto3 botocore[crt] strands-agents httpx

To obtain your AWS user ID, run this command, and write down the value for the UserId field:

$ aws sts get-caller-identity
{
    "UserId": "XXXXXXXXXXXXX",
    "Account": "XXXXXXXXXXXXX",
    "Arn": "arn:aws:iam::XXXXXXXXXXXXX:username"
}

We’ll use this value in the agent’s code.

Finally, load the agent’s code in /home/ec2-user/agent/oauth_test.py with the following. Update the following fields with the information we’ve been collecting so far:

  • REGION: The region where you created the AWS services.
  • WORKLOAD: The name of the workload identity.
  • USER_ID: The AWS user ID we just collected.
  • PROVIDER: The name of the OAuth Client in AgentCore Identity.
  • RETURN_URL: Set your instance’s Public DNS as the URL’s domain; keep the path as is.

You can also download the code here. We’ll explain it step by step in the next section.

import os
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse, parse_qs

import boto3
from bedrock_agentcore.services.identity import IdentityClient


# ============================================================
# Configuration
# ============================================================

REGION = "us-east-1"
# Must match the workload identity you created with:
#
# $aws bedrock-agentcore-control create-workload-identity --name "ec2-github-demo-agent"
#
# List your identities with:
#
# $aws bedrock-agentcore-control list-workload-identities
WORKLOAD = "ec2-github-demo-agent"
# Must match the user id you are logged in this machine as.
#
# Take it from the UserId field on:
#
# $ aws sts get-caller-identity
USER_ID = "USER_ID"
# Must match the name of the Outbound Auth you created in AgentCore identity.
PROVIDER = "oauth-client"

CALLBACK_HOST = "0.0.0.0"
CALLBACK_PORT = 8080

# For a public EC2 callback, set this to the public HTTPS URL:
#
# export OAUTH2_RETURN_URL="https://your-public-host/oauth/callback"
#
# For local testing you can use:
#
# export OAUTH2_RETURN_URL="http://localhost:8080/oauth/callback"
RETURN_URL = os.environ.get(
    "OAUTH2_RETURN_URL",
    "https://ec2-XXX-XXX-XXX-XXX.compute-1.amazonaws.com/oauth/callback",
)


print(f"Region:   {REGION}")
print(f"Workload: {WORKLOAD}")
print(f"User:     {USER_ID}")
print(f"Provider: {PROVIDER}")
print(f"Callback: {RETURN_URL}")


# ============================================================
# AWS clients
# ============================================================

identity = IdentityClient(REGION)

client = boto3.client(
    "bedrock-agentcore",
    region_name=REGION,
)


# Used to keep the process alive until the OAuth callback arrives.
oauth_completed = threading.Event()


# ============================================================
# OAuth callback endpoint
# ============================================================

class OAuthCallbackHandler(BaseHTTPRequestHandler):

    def do_GET(self):
        parsed = urlparse(self.path)

        if parsed.path != "/oauth/callback":
            self.send_response(404)
            self.end_headers()
            self.wfile.write(b"Not found")
            return

        params = parse_qs(parsed.query)

        print()
        print("=" * 70)
        print("AGENTCORE OAUTH CALLBACK RECEIVED")
        print("=" * 70)

        print("Callback URL:", self.path)

        # AgentCore sends the OAuth session URI as session_id.
        session_id = params.get("session_id", [None])[0]

        error = params.get("error", [None])[0]
        error_description = params.get(
            "error_description",
            [""],
        )[0]

        if error:
            print("OAuth error:", error)
            print("Description:", error_description)

            self.send_response(400)
            self.send_header("Content-Type", "text/html")
            self.end_headers()

            self.wfile.write(
                f"""
                <html>
                  <body>
                    <h1>Authorization failed</h1>
                    <p>{error}</p>
                    <p>{error_description}</p>
                  </body>
                </html>
                """.encode()
            )

            oauth_completed.set()
            return

        if not session_id:
            print("ERROR: no session_id received")

            self.send_response(400)
            self.end_headers()
            self.wfile.write(b"Missing session_id")
            return

        print()
        print("Session ID received:")
        print(session_id)

        print()
        print("Completing AgentCore OAuth session...")
        print(f"User ID: {USER_ID}")

        try:
            client.complete_resource_token_auth(
                sessionUri=session_id,
                userIdentifier={
                    "userId": USER_ID
                },
            )

            print()
            print("OAuth session successfully completed.")
            print("The credential is now available through AgentCore Identity.")

            self.send_response(200)
            self.send_header("Content-Type", "text/html")
            self.end_headers()

            self.wfile.write(
                b"""
                <!DOCTYPE html>
                <html>
                  <head>
                    <title>Authorization successful</title>
                  </head>
                  <body>
                    <h1>Authorization successful</h1>
                    <p>
                      GitHub authorization has been completed.
                    </p>
                    <p>
                      You can close this window and return to the EC2 agent.
                    </p>
                  </body>
                </html>
                """
            )

            oauth_completed.set()

        except Exception as e:
            print()
            print("ERROR completing OAuth session:")
            print(type(e).__name__)
            print(str(e))

            self.send_response(500)
            self.send_header("Content-Type", "text/plain")
            self.end_headers()
            self.wfile.write(
                f"Failed to complete OAuth session: {e}".encode()
            )


    def log_message(self, format, *args):
        # Keep the output focused on the OAuth flow.
        pass


def start_callback_server():
    server = HTTPServer(
        (CALLBACK_HOST, CALLBACK_PORT),
        OAuthCallbackHandler,
    )

    thread = threading.Thread(
        target=server.serve_forever,
        daemon=True,
    )

    thread.start()

    print()
    print("=" * 70)
    print("OAUTH CALLBACK SERVER")
    print("=" * 70)
    print(
        f"Listening on http://{CALLBACK_HOST}:{CALLBACK_PORT}/oauth/callback"
    )
    print(f"Public return URL: {RETURN_URL}")

    return server


# ============================================================
# Start callback server
# ============================================================

server = start_callback_server()


# ============================================================
# Step 1 - Get workload access token
# ============================================================

print()
print("=" * 70)
print("STEP 1 - GET WORKLOAD ACCESS TOKEN")
print("=" * 70)

result = identity.get_workload_access_token_for_user_id(
    workload_name=WORKLOAD,
    user_id=USER_ID
)

print("Result type:", type(result))

if isinstance(result, dict):
    print("Result keys:", list(result.keys()))
    workload_token = result.get("workloadAccessToken")
else:
    workload_token = result

print(
    "Workload token present:",
    bool(workload_token),
)

print(
    "Workload token length:",
    len(workload_token) if workload_token else 0,
)


# ============================================================
# Step 2 - Request OAuth token and authorization URL
# ============================================================

print()
print("=" * 70)
print("STEP 2 - REQUEST OAUTH TOKEN")
print("=" * 70)

response = client.get_resource_oauth2_token(
    workloadIdentityToken=workload_token,
    resourceCredentialProviderName=PROVIDER,
    scopes=["repo", "read:user"],
    resourceOauth2ReturnUrl=RETURN_URL,
    oauth2Flow="USER_FEDERATION",
    forceAuthentication=True,
)

print()
print("Response keys:", list(response.keys()))


if "authorizationUrl" not in response:
    print("No authorization URL was returned.")
    print(response)
    raise SystemExit(1)

authorization_url = response["authorizationUrl"]


# ============================================================
# Step 3 - Request the user to start the OAuth flow
# ============================================================

print()
print("=" * 70)
print("STEP 3 - AUTHORIZE WITH GITHUB")
print("=" * 70)

print()
print("Open this URL in your browser:")
print()
print(authorization_url)
print()


# ============================================================
# Step 4 - Wait for callback
# ============================================================

print("=" * 70)
print("STEP 4 - WAITING FOR GITHUB CALLBACK")
print("=" * 70)

print()
print(f"Waiting on {RETURN_URL}...")
print()
print("The callback will be handled by THIS EC2 process.")
print("The session_id will be passed to CompleteResourceTokenAuth.")
print()

oauth_completed.wait()

print()
print("=" * 70)
print("STEP 5 - OAUTH FLOW COMPLETED")
print("=" * 70)

print()
print("The EC2 agent completed the OAuth session.")
print()
# ============================================================
# Step 6 - Retrieve the GitHub access token
# ============================================================

print()
print("=" * 70)
print("STEP 6 - RETRIEVE GITHUB ACCESS TOKEN")
print("=" * 70)

print()
print("Requesting the OAuth credential from AgentCore Identity...")
print("The credential is retrieved from the Token Vault.")

token_response = client.get_resource_oauth2_token(
    workloadIdentityToken=workload_token,
    resourceCredentialProviderName=PROVIDER,
    scopes=["repo", "read:user"],
    oauth2Flow="USER_FEDERATION",
    forceAuthentication=False,
)

print()
print("Response keys:", list(token_response.keys()))


# The exact field should be inspected rather than printing
# the credential itself.
github_access_token = token_response.get("accessToken")


print()
print("=" * 70)
print("STEP 7 - GITHUB ACCESS TOKEN")
print("=" * 70)

print()
print("Access token received:", bool(github_access_token))

if github_access_token:
    print("Access token length:", len(github_access_token))

    # Just for test, in production NEVER print the actual token.
    print("Access token value: ", github_access_token)

else:
    print("No access token was returned.")
    print("Full response:")
    print(token_response)
# ============================================================
# Keep the server alive briefly so the browser can receive
# the successful response.
# ============================================================

server.shutdown()
server.server_close()

print()
print("Callback server stopped.")

Running the agent

With everything set up, you can now run the agent with:

python3 ~/agent/oauth_test.py

You should see text appearing in the terminal like this:

(agentcore-venv)$ python3 ~/agent/oauth_test.py
Region:   us-east-1
Workload: ec2-github-demo-agent
User:     XXXXXXXXXXXX
Provider: oauth-client
Callback: https://ec2-XX-XX-XX-XX.compute-1.amazonaws.com/oauth/callback

======================================================================
OAUTH CALLBACK SERVER
======================================================================
Listening on http://0.0.0.0:8080/oauth/callback
Public return URL: https://ec2-XX-XX-XX-XX.compute-1.amazonaws.com/oauth/callback

======================================================================
STEP 1 - GET WORKLOAD ACCESS TOKEN
======================================================================
Result type: <class 'dict'>
Result keys: ['ResponseMetadata', 'workloadAccessToken']
Workload token present: True
Workload token length: 1794
[…]

Let’s analyze the code to see what’s going on behind the scenes.

Request Authorization to AgentCore Identity

Step 1: Our flow starts around line 240 in the oauth_test.py file:

result = identity.get_workload_access_token_for_user_id(
    workload_name=WORKLOAD,
    user_id=USER_ID
)

print("Result type:", type(result))

if isinstance(result, dict):
    print("Result keys:", list(result.keys()))
    workload_token = result.get("workloadAccessToken")
else:
    workload_token = result

With identity.get_workload_access_token_for_user_id, the agent requests a workload token that uniquely identifies it against AgentCore Identity in the rest of the calls.

Note: In a production environment, the user ID should represent an IAM role with minimal permissions instead of an AWS user.

Step 2: Next, the agent will use its workload_token to attempt to obtain an OAuth access token for the oauth-client resource (PROVIDER).

For that, it calls client.get_resource_oauth2_token:

response = client.get_resource_oauth2_token(
    workloadIdentityToken=workload_token,
    resourceCredentialProviderName=PROVIDER,
    scopes=["repo", "read:user"],
    resourceOauth2ReturnUrl=RETURN_URL,
    oauth2Flow="USER_FEDERATION",
    forceAuthentication=True,
)

[…]

authorization_url = response["authorizationUrl"]

If the credential provider has an OAuth access token, it will be returned in the accessToken field of the response. In a production environment the agent would grab the token and skip to step 6.

However, for education purposes we are using forceAuthentication=True to simulate that there’s no access token available. In these cases, the agent receives an authorizationUrl to start the OAuth authentication flow.

The OAuth Flow

f see the agent output this URL in the terminal, requesting interaction from the user:

======================================================================
STEP 3 - AUTHORIZE WITH GITHUB
======================================================================

Open this URL in your browser:

https://bedrock-agentcore.us-east-1.amazonaws.com/identities/oauth2/authorize?…

It is possible to set up OAuth flows that don’t require human intervention. However, not all resources support them, or maybe you want to protect access with human approval. In this example, we opted to demo a 3-legged OAuth flow to provide a more complete scenario.

At the same time, the agent will wait for the OAuth flow to finish:

oauth_completed.wait()

Once you open that URL in a browser, what follows is a regular OAuth flow between the GitHub authorization server and AgentCore Identity. You will be asked to authenticate with GitHub and grant AgentCore Identity access.

Your browser will be briefly redirected to an AgentCore URL, and then AgentCore will handle obtaining an access token.

With this, the OAuth flow has finished, and AgentCore Identity has obtained an access token. Let’s see now how the agent can retrieve this token.

AgentCore will send a final redirection to the user’s browser, pointing it to the agent’s callback located at RETURN_URL.

If you are following this guide, you’ll see a security warning in your browser. It’s expected, as we’ve set up our NGINX server with a self-signed certificate.

In this case, it is safe to accept the risk and visit the website. However, in a production environment, you would set up a proper domain and certificates for your agent. For example, using certbot.

Step 4: A second flow starts in the agent around line 75; the main thread is still waiting for a signal.

class OAuthCallbackHandler(BaseHTTPRequestHandler):

    def do_GET(self):
        […]

        # AgentCore sends the OAuth session URI as session_id.
        session_id = params.get("session_id", [None])[0]

        […]

        try:
            client.complete_resource_token_auth(
                sessionUri=session_id,
                userIdentifier={
                    "userId": USER_ID
                },
            )

            […]

            oauth_completed.set()

Within all the print statements and error handling, there are 3 key pieces of code in this callback.

First, the agent retrieves a session_id from the URL parameters. If the agent expects multiple tokens, it can match this session_id against the sessionUri parameter returned by get_resource_oauth2_token to determine which one is ready to use.

Step 5: The agent may also use this session_id to call client.complete_resource_token_auth and confirm that the token is ready for use.

Finally, the agent releases the lock in the main thread:

            oauth_completed.set()

Also, if all goes well, the agent will return a success message to the user’s browser. 

Step 6: Back in the main thread, around line 349, the agent tries again to obtain the access token:

token_response = client.get_resource_oauth2_token(
    workloadIdentityToken=workload_token,
    resourceCredentialProviderName=PROVIDER,
    scopes=["repo", "read:user"],
    oauth2Flow="USER_FEDERATION",
    forceAuthentication=False,
)

Now, the tokens are available in the accessToken field of the response.

github_access_token = token_response.get("accessToken")

Accessing the Resource

This is the point where our agent could use the access token to interact with the GitHub API and retrieve data.

Then the agent could follow up by working with other agents and LLMs to complete the task at hand.

Cleaning up

Remember to destroy and terminate all services once you are done playing with this demo.

This includes:

  • The GitHub OAuth App.
  • The Outbound Auth in AgentCore Identity.
  • The EC2 instance.
  • The EC2 key pair.
  • The EC2 security groups.
  • The workload identity.

Next Steps

If you want to expand on this exercise, you can try:

Adding more OAuth Providers

Aside from the one in GitHub, try connecting to other services and check how the same AgentCore Identity workflow can be used. The one in this example (GitHub) uses oauth2Flow="USER_FEDERATION“, which means a human user must approve.

Others support oauth2Flow="M2M", which is intended for Machine-to-Machine use with no user interaction.

Calling the LLM

Connect the agent to a Bedrock model and use GitHub credentials to perform actions in response to natural-language instructions.

Support Multiple Token Requests

Prepare the agent to support multiple calls to AgentCoreIdentity.

Tie the workload_token and session_id to each individual request, and validate that the session_id received in the callback corresponds to an active request.

Handling token expiration

Configure GitHub to handle token expiration by using refresh tokens to obtain new access tokens, eliminating the need for the agent to authenticate again.

If you want to learn more, try these links:

Conclusion

Amazon Bedrock AgentCore Identity simplifies agent authentication management. You can leverage the bits you are interested in, like identity, saving you weeks of building custom solutions. 

While AgentCore Identity is intended to be straightforward for agents running in runtime, other cases, such as agents running in EC2 instances, might be slightly more complex. 

FAQs about Amazon Bedrock Agent Core

What is Amazon Bedrock AgentCore?

Amazon Bedrock AgentCore is a suite of services designed to enable the easy, quick operation, deployment, and scaling of AI agents. Some of its main features include Runtime (a serverless environment that supports any agent framework) and Identity (a service for managing credentials and authentication).

How does authentication work if the agents are self-hosted?

Authentication with AgentCore Identity is usually done by calling its API. The flow is done through GetWorkloadAccessToken, GetResourceOauth2Token, and CompleteResourceTokenAuth. One of the most common ways to access this API is to use the provided Python SDK (bedrock_agentcore Python package).

What is a workload identity?

A workload identity is the mechanism used by AgentCore Identity to represent the agent as a distinct, recognizable identity separate from a human user (and from its AWS IAM role). It has its own unique ARN as well. While the workload identity identifies an agent, the workload token proves its identity when it requests an access token.

Related Reading

Víctor Jiménez
Víctor Jiménez

Víctor Jiménez is an engineer and technical writer who specializes in turning complex engineering concepts into clear, practical explanations. He began his career as a full-stack software engineer, while also working as a MySQL database administrator and certified instructor. After moving into technical marketing, Víctor focused increasingly on educational content for technical audiences. His recent work covers cloud infrastructure, containers and cybersecurity. Outside of work, he experiments with 3D printing, builds LEGO sets and hosts Dungeons & Dragons campaigns.

You might also like

PAM is changing as AI agents, workloads and machine identities take on more privileged access.
AI assistants are becoming coworkers. Aembit enterprise customers can secure them with the identity and access controls they already have in place, no new product or deployment required.
Personal AI agents like Meta Muse will be coming to work with employees. Aembit enforces how they access enterprise systems.