auth0-3lo-mcp-server
by Neloh
README.md
# AgentCore Gateway 3LO: End-to-End Authorization Code Flow with Auth0
A complete working example of the Amazon Bedrock AgentCore Gateway 3-Legged OAuth (Authorization Code) flow using Auth0 as both the inbound JWT authorizer and the outbound credential provider, with a Lambda MCP server as the target.
## What This Demonstrates
1. Inbound JWT authentication (Auth0 token → Gateway)
2. `tools/list` discovering tools from a remote MCP server
3. `tools/call` triggering a -32042 OAuth elicitation
4. User completing OAuth consent at Auth0
5. Callback handler receiving the session_id
6. `CompleteResourceTokenAuth` binding the token to the user
7. Subsequent `tools/call` succeeding end-to-end
## Architecture
```
MCP Client (curl) --> AgentCore Gateway --> Lambda MCP Server
| |
| +--> AgentCore Identity (token store)
| |
| +--> Auth0 (consent + code exchange)
|
+--> Callback Handler (Flask @ localhost:5000)
|
+--> Receives session_id after consent
+--> Calls CompleteResourceTokenAuth
```

## Prerequisites
- AWS account with Amazon Bedrock AgentCore access
- AWS CLI with `bedrock-agentcore` and `bedrock-agentcore-control` commands
- Auth0 free developer account ([sign up](https://auth0.com/signup))
- Python 3.12+
- pip
## Quick Start
```bash
# Clone this repo
git clone <repo-url>
cd agentcore-3lo-auth0-example
# Install dependencies
pip install -r requirements.txt
# Copy and fill in your credentials
cp .env.example .env
# Edit .env with your Auth0 and AWS values
# Start the Flask app
python app.py
```
Then follow the step-by-step guide below.
## Step-by-Step Guide
### Step 1: Auth0 Configuration
#### Create an API
1. Auth0 Dashboard > APIs > Create API
2. Name: `agentcore-gateway-api`
3. Identifier: `https://agentcore-gateway-api`
4. Signing Algorithm: RS256
#### Configure API Settings
5. **Permissions tab**: Add `access:tools` (description: "Access MCP tools")
6. **Settings tab**: Set "Allow Skipping User Consent" to **OFF**


#### Create an Application
7. Auth0 Dashboard > Applications > Create Application
8. Name: `agentcore-3lo-repro`, Type: **Regular Web Application**
9. Settings:
- Allowed Callback URLs: `http://localhost:5000/callback, http://localhost:5000/token-callback`
- Allowed Logout URLs: `http://localhost:5000/`
You will add the AgentCore callback URL to this list in Step 4.

#### Deploy a Post Login Action
10. Auth0 Dashboard > Actions > Library > Build Custom
11. Name: `add-scp-claim`, Trigger: **Login / Post Login**
```javascript
exports.onExecutePostLogin = async (event, api) => {
const scopes = event.transaction.requested_scopes || [];
if (!scopes.includes('openid')) {
scopes.push('openid');
}
api.accessToken.setCustomClaim('scp', scopes);
};
```
12. Deploy, then add to the Login flow (Actions > Flows > Login)

### Step 2: AWS IAM Role
Create an execution role for the Gateway with trust policy for `bedrock-agentcore.amazonaws.com`:
```json
{
"Version": "2012-10-17",
"Statement": [
{"Effect": "Allow", "Action": "bedrock-agentcore:*", "Resource": "*"},
{"Effect": "Allow", "Action": "lambda:InvokeFunction", "Resource": "*"},
{"Effect": "Allow", "Action": "secretsmanager:GetSecretValue",
"Resource": "arn:aws:secretsmanager:<region>:<account>:secret:bedrock-agentcore-identity*"}
]
}
```
### Step 3: Create AgentCore Gateway
> **Important:** Use only `allowedAudience`. Do NOT use `allowedClients` with Auth0 — Auth0 places client_id in the `azp` claim, not `client_id`, causing `insufficient_scope` errors.
```bash
aws bedrock-agentcore-control create-gateway \
--name "auth0-3lo-example" \
--role-arn "arn:aws:iam::<account>:role/<role-name>" \
--protocol-type MCP \
--protocol-configuration '{"mcp":{"supportedVersions":["2025-11-25"]}}' \
--authorizer-type CUSTOM_JWT \
--authorizer-configuration '{
"customJWTAuthorizer": {
"discoveryUrl": "https://<tenant>.us.auth0.com/.well-known/openid-configuration",
"allowedAudience": ["https://agentcore-gateway-api"]
}
}' \
--region us-east-1
```
Wait for status: `READY`.
### Step 4: Create OAuth2 Credential Provider
```bash
aws bedrock-agentcore-control create-oauth2-credential-provider \
--name "auth0-3lo-example" \
--credential-provider-vendor "CustomOauth2" \
--oauth2-provider-config-input '{
"customOauth2ProviderConfig": {
"oauthDiscovery": {
"authorizationServerMetadata": {
"issuer": "https://<tenant>.us.auth0.com/",
"authorizationEndpoint": "https://<tenant>.us.auth0.com/authorize",
"tokenEndpoint": "https://<tenant>.us.auth0.com/oauth/token",
"responseTypes": ["code"]
}
},
"clientId": "<your-client-id>",
"clientSecret": "<your-client-secret>",
"clientAuthenticationMethod": "CLIENT_SECRET_POST"
}
}' \
--region us-east-1
```
The response includes a `callbackUrl`. **Add this URL to your Auth0 application's Allowed Callback URLs.**
### Step 5: Register Workload Identity Return URL
```bash
aws bedrock-agentcore-control update-workload-identity \
--name "<gateway-id>" \
--allowed-resource-oauth2-return-urls '["http://localhost:5000/agentcore-callback"]' \
--region us-east-1
```
### Step 6: Create Lambda MCP Server
Deploy the included Lambda function (see `lambda/` directory):
```bash
cd lambda && zip -r ../lambda.zip . && cd ..
aws lambda create-function \
--function-name auth0-3lo-mcp-server \
--runtime python3.12 \
--handler lambda_function.lambda_handler \
--role arn:aws:iam::<account>:role/<lambda-role> \
--zip-file fileb://lambda.zip \
--region us-east-1
aws lambda create-function-url-config \
--function-name auth0-3lo-mcp-server \
--auth-type NONE \
--region us-east-1
aws lambda add-permission \
--function-name auth0-3lo-mcp-server \
--statement-id FunctionURLAllowPublicAccess \
--action lambda:InvokeFunctionUrl \
--principal "*" \
--function-url-auth-type NONE \
--region us-east-1
```
### Step 7: Create Gateway Target
```bash
aws bedrock-agentcore-control create-gateway-target \
--gateway-identifier "<gateway-id>" \
--name "auth0-3lo-lambda-mcp" \
--target-configuration '{
"mcp": {
"mcpServer": {
"endpoint": "<lambda-function-url>",
"mcpToolSchema": {
"inlinePayload": "{\"tools\":[{\"name\":\"whoami\",\"description\":\"Returns user info\",\"inputSchema\":{\"type\":\"object\",\"properties\":{},\"required\":[]}},{\"name\":\"echo\",\"description\":\"Echoes input\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"message\":{\"type\":\"string\"}},\"required\":[\"message\"]}}]}"
}
}
}
}' \
--credential-provider-configurations '[{
"credentialProviderType": "OAUTH",
"credentialProvider": {
"oauthCredentialProvider": {
"providerArn": "arn:aws:bedrock-agentcore:us-east-1:<account>:token-vault/default/oauth2credentialprovider/auth0-3lo-example",
"scopes": ["openid", "profile", "email"],
"grantType": "AUTHORIZATION_CODE",
"defaultReturnUrl": "http://localhost:5000/agentcore-callback",
"customParameters": {
"audience": "https://agentcore-gateway-api"
}
}
}
}]' \
--region us-east-1
```
### Step 8: Get an Access Token
1. Start Flask: `python app.py`
2. Open http://localhost:5000/token in your browser
3. Log in to Auth0
4. Copy the access token displayed
### Step 9: Test the Flow
```bash
TOKEN="<your-access-token>"
GATEWAY="<gateway-id>"
# 1. List tools (confirms inbound auth)
curl -s -X POST "https://${GATEWAY}.gateway.bedrock-agentcore.us-east-1.amazonaws.com/mcp" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${TOKEN}" \
-H "MCP-Protocol-Version: 2025-11-25" \
-d '{"jsonrpc":"2.0","id":"1","method":"tools/list","params":{}}'
# 2. Call a tool (triggers -32042 elicitation)
curl -s -X POST "https://${GATEWAY}.gateway.bedrock-agentcore.us-east-1.amazonaws.com/mcp" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${TOKEN}" \
-H "MCP-Protocol-Version: 2025-11-25" \
-d '{"jsonrpc":"2.0","id":"1","method":"tools/call","params":{"name":"auth0-3lo-lambda-mcp___echo","arguments":{"message":"hello"}}}'
# 3. Open the URL from the -32042 response IMMEDIATELY (60s TTL)
# Complete consent in browser
# Browser lands on localhost:5000/agentcore-callback?session_id=...
# 4. Complete token binding
SESSION_ID="<session_id_from_callback>"
aws bedrock-agentcore complete-resource-token-auth \
--user-identifier "{\"userToken\":\"${TOKEN}\"}" \
--session-uri "${SESSION_ID}" \
--region us-east-1
# 5. Retry the tool call (should succeed now)
curl -s -X POST "https://${GATEWAY}.gateway.bedrock-agentcore.us-east-1.amazonaws.com/mcp" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${TOKEN}" \
-H "MCP-Protocol-Version: 2025-11-25" \
-d '{"jsonrpc":"2.0","id":"1","method":"tools/call","params":{"name":"auth0-3lo-lambda-mcp___echo","arguments":{"message":"hello from 3LO end-to-end"}}}'
```
Expected final response:
```json
{
"jsonrpc": "2.0",
"id": "1",
"result": {
"content": [{"type": "text", "text": "Echo: hello from 3LO end-to-end"}]
}
}
```

## Key Learnings
| Issue | Root Cause | Fix |
|---|---|---|
| `insufficient_scope` on all requests | `allowedClients` in gateway config; Auth0 uses `azp` not `client_id` | Use only `allowedAudience` |
| `authorizationCode must not be null` | Auth0 skips consent for first-party apps without permissions | Add API permission + disable consent skip |
| `Authorization error when sending message` | Lambda Function URL missing resource policy | `aws lambda add-permission` with `lambda:InvokeFunctionUrl` |
| `Invalid request` on consent URL | request_uri TTL expired (~60 seconds) | Open URL immediately after generation |
| Token never persists | Nothing calls `CompleteResourceTokenAuth` | Implement callback handler (this repo) |
## Auth0 Configuration Checklist
- [ ] API created with custom identifier (audience)
- [ ] At least one permission defined on the API
- [ ] "Allow Skipping User Consent" is OFF
- [ ] Post Login Action deployed (adds `scp` claim)
- [ ] Application callback URLs include AgentCore callback URL
- [ ] Application type: Regular Web Application
- [ ] Token Endpoint Auth Method: client_secret_post
## File Structure
```
.
├── README.md
├── requirements.txt
├── .env.example
├── .gitignore
├── app.py # Flask app (token endpoint + callback handler)
├── callback_handler.py # Standalone callback handler example
├── lambda/
│ └── lambda_function.py # Lambda MCP server (echo + whoami tools)
└── screenshots/
├── 3lo-sequence-diagram.png
├── 02-auth0-app-settings-callbacks.png
├── 04-auth0-api-settings-consent.png
├── 05-auth0-api-permissions.png
├── 08-agentcore-callback-received.png
└── 09-auth0-login-flow-action.png
```
## Cleanup
```bash
aws bedrock-agentcore-control delete-gateway-target \
--gateway-identifier "<gateway-id>" --target-id "<target-id>" --region us-east-1
aws bedrock-agentcore-control delete-gateway \
--gateway-identifier "<gateway-id>" --region us-east-1
aws bedrock-agentcore-control delete-oauth2-credential-provider \
--name "auth0-3lo-example" --region us-east-1
aws lambda delete-function --function-name auth0-3lo-mcp-server --region us-east-1
```
## Documentation
- [Session binding (CompleteResourceTokenAuth)](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/oauth2-authorization-url-session-binding.html)
- [Gateway outbound auth](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-outbound-auth.html)
- [Auth0 integration](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/identity-idp-auth0.html)
- [CompleteResourceTokenAuth API](https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/API_CompleteResourceTokenAuth.html)
- [AWS blog: Authorization code flow](https://aws.amazon.com/blogs/machine-learning/connecting-mcp-servers-to-amazon-bedrock-agentcore-gateway-using-authorization-code-flow/)
## License
MIT
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues