aws / aws/bedrock-agentcore-sdk-typescript

[BUG] CompleteResourceTokenAuth fails with 'Invalid or expired session' when using Gateway 3LO (AUTHORIZATION_CODE)

Open
#37 3 comments 5 reactions 0 assignees View on GitHub
bug
Dominant language
TypeScript
Stars
91
Forks
31
Avg merge
1d 11h
Merged PRs (30d)
5

Description

## Description

When using Amazon Bedrock AgentCore Gateway with 3-legged OAuth (AUTHORIZATION_CODE grant type) for Microsoft Graph integration, the `CompleteResourceTokenAuth` API call consistently fails with `AccessDeniedException: Invalid or expired session`, even though the OAuth flow completes successfully from the user's perspective.

## Environment

- Region: eu-central-1
- AgentCore Gateway with MCP target
- Credential Provider: CustomOauth2 (Microsoft Entra ID)
- Grant Type: AUTHORIZATION_CODE
- AWS SDK: @aws-sdk/client-bedrock-agentcore (latest)

## Configuration

**Gateway Target:**
```json
{
"grantType": "AUTHORIZATION_CODE",
"defaultReturnUrl": "https://[api-gateway].execute-api.eu-central-1.amazonaws.com/oauth/callback",
"providerArn": "arn:aws:bedrock-agentcore:eu-central-1:[account]:token-vault/default/oauth2credentialprovider/microsoft-oauth-acc"
}
```

**Credential Provider:**
```json
{
"credentialProviderVendor": "CustomOauth2",
"callbackUrl": "https://bedrock-agentcore.eu-central-1.amazonaws.com/identities/oauth2/callback/[uuid]",
"oauth2ProviderConfigOutput": {
"customOauth2ProviderConfig": {
"oauthDiscovery": {
"discoveryUrl": "https://login.microsoftonline.com/[tenant]/v2.0/.well-known/openid-configuration"
}
}
}
}
```

**Workload Identity:**
```json
{
"allowedResourceOauth2ReturnUrls": ["https://[api-gateway].execute-api.eu-central-1.amazonaws.com/oauth/callback"]
}
```

## Steps to Reproduce

1. Call Gateway with `X-Amzn-Bedrock-AgentCore-Runtime-User-Id: user@example.com` header
2. Gateway returns elicitation URL with `request_uri`
3. Store the `request_uri` → `userId` mapping in DynamoDB
4. Redirect user to AgentCore authorization URL
5. User completes Microsoft login and grants consent
6. AgentCore redirects to `defaultReturnUrl` with `session_id` query parameter
7. Callback Lambda calls `CompleteResourceTokenAuth`:

```typescript
const command = new BedrockAgentCore.CompleteResourceTokenAuthCommand({
sessionUri: params.sessionId, // e.g., "urn:ietf:params:oauth:request_uri:Y2M4M2QyNDkt..."
userIdentifier: { userId: "user@example.com" }, // Same as sent in step 1
});
await agentCoreClient.send(command);
```

## Expected Behavior

`CompleteResourceTokenAuth` should succeed, and the OAuth token should be stored in Token Vault, allowing subsequent Gateway calls to succeed without re-authentication.

## Actual Behavior

```json
{
"Error": {
"Message": "Invalid or expired session",
"Code": "AccessDeniedException"
},
"ResponseMetadata": {
"HTTPStatusCode": 403
}
}
```

This error occurs immediately (within 2-3 seconds of session creation), so it's not a TTL expiration issue.

After the error, calling the Gateway with the same user ID still returns an elicitation URL, indicating the token was NOT stored.

## Debug Information

**OAuth Start Lambda (session creation):**
```json
{
"message": "Replaying request to Gateway",
"userId": "xxx@xxxx.nl",
"userIdLength": 23,
"userIdBytes": "722e706f7374756c6172744063617265626561742e6e6c",
"headerValue": "X-Amzn-Bedrock-AgentCore-Runtime-User-Id: rxx@xxxx.nl"
}
{
"message": "Stored session mapping in DynamoDB",
"requestUri": "urn:ietf:params:oauth:request_uri:MGE5YTIxMWUtOWQxMC00YWVjLWIxZGItYWVkZWI2NmY5NGMx",
"requestUriLength": 82,
"userId": "xx@xxxx.nl",
"userIdLength": 23,
"userIdBytes": "722e706f7374756c6172744063617265626561742e6e6c"
}
```

**OAuth Callback Lambda (2 seconds later):**
```json
{
"message": "CompleteResourceTokenAuth - Debug Info",
"sessionUri": "urn:ietf:params:oauth:request_uri:MGE5YTIxMWUtOWQxMC00YWVjLWIxZGItYWVkZWI2NmY5NGMx",
"sessionUriLength": 82,
"sessionUriPattern": "VALID",
"userId": "xx@xxxx.nl",
"userIdLength": 23,
"userIdSource": "DynamoDB",
"awsRegion": "eu-central-1"
}
{
"message": "Sending CompleteResourceTokenAuth command",
"commandInput": "{\"sessionUri\":\"urn:ietf:params:oauth:request_uri:MGE5YTIxMWUtOWQxMC00YWVjLWIxZGItYWVkZWI2NmY5NGMx\",\"userIdentifier\":{\"userId\":\"xx@xxxx.nl\"}}"
}
{
"message": "CompleteResourceTokenAuth failed - Full Details",
"error": "Invalid or expired session",
"errorName": "AccessDeniedException",
"httpStatusCode": 403,
"sessionUri": "urn:ietf:params:oauth:request_uri:MGE5YTIxMWUtOWQxMC00YWVjLWIxZGItYWVkZWI2NmY5NGMx",
"userId": "xxx@xxt.nl",
"userIdBytes": "722e706f7374756c6172744063617265626561742e6e6c"
}
```

**Byte-level comparison confirms EXACT match:**
- Session URI: ✅ Identical
- User ID: ✅ Identical
- User ID bytes: ✅ `722e706f7374756c6172744063617265626561742e6e6c` (both)
- Timing: Only ~2 seconds between session creation and callback (not expired)

## Questions

1. Does Gateway 3LO handle `CompleteResourceTokenAuth` differently than Runtime 3LO?
2. Is there a specific format required for the `userId` parameter?
3. Does the workload identity (Cognito client credentials) need to match between session creation and completion?
4. Is `CompleteResourceTokenAuth` even needed for Gateway 3LO, or does Gateway auto-complete sessions?

## IAM Permissions

Lambda role has:
```json
{
"Effect": "Allow",
"Action": "bedrock-agentcore:CompleteResourceTokenAuth",
"Resource": "*"
}
```

## Related Documentation

- [OAuth 2.0 authorization URL session binding](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/oauth2-authorization-url-session-binding.html)
- [Example: Authentication with authorization code grant](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-using-auth-ex-3lo.html)

Any guidance on correctly implementing 3LO with Gateway would be greatly appreciated!

Contributor guide

Open the contributing guide

Research direction

Start with the CompleteResourceTokenAuth call in the callback Lambda and compare its sessionUri and userIdentifier handling with the linked authorization-code and session-binding documentation. Reproduce the Gateway 3LO flow in eu-central-1, then determine whether the failure is in SDK request handling or the service flow; done means documenting the confirmed cause and supported completion behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, authentication
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.