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:
{
"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:
{
"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:
{
"allowedResourceOauth2ReturnUrls": ["https://[api-gateway].execute-api.eu-central-1.amazonaws.com/oauth/callback"]
}
Steps to Reproduce
- Call Gateway with
X-Amzn-Bedrock-AgentCore-Runtime-User-Id: user@example.com header
- Gateway returns elicitation URL with
request_uri
- Store the
request_uri → userId mapping in DynamoDB
- Redirect user to AgentCore authorization URL
- User completes Microsoft login and grants consent
- AgentCore redirects to
defaultReturnUrl with session_id query parameter
- Callback Lambda calls
CompleteResourceTokenAuth:
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
{
"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):
{
"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):
{
"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
- Does Gateway 3LO handle
CompleteResourceTokenAuth differently than Runtime 3LO?
- Is there a specific format required for the
userId parameter?
- Does the workload identity (Cognito client credentials) need to match between session creation and completion?
- Is
CompleteResourceTokenAuth even needed for Gateway 3LO, or does Gateway auto-complete sessions?
IAM Permissions
Lambda role has:
{
"Effect": "Allow",
"Action": "bedrock-agentcore:CompleteResourceTokenAuth",
"Resource": "*"
}
Related Documentation
Any guidance on correctly implementing 3LO with Gateway would be greatly appreciated!
Description
When using Amazon Bedrock AgentCore Gateway with 3-legged OAuth (AUTHORIZATION_CODE grant type) for Microsoft Graph integration, the
CompleteResourceTokenAuthAPI call consistently fails withAccessDeniedException: Invalid or expired session, even though the OAuth flow completes successfully from the user's perspective.Environment
Configuration
Gateway Target:
{ "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:
{ "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:
{ "allowedResourceOauth2ReturnUrls": ["https://[api-gateway].execute-api.eu-central-1.amazonaws.com/oauth/callback"] }Steps to Reproduce
X-Amzn-Bedrock-AgentCore-Runtime-User-Id: user@example.comheaderrequest_urirequest_uri→userIdmapping in DynamoDBdefaultReturnUrlwithsession_idquery parameterCompleteResourceTokenAuth:Expected Behavior
CompleteResourceTokenAuthshould succeed, and the OAuth token should be stored in Token Vault, allowing subsequent Gateway calls to succeed without re-authentication.Actual Behavior
{ "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):
{ "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):
{ "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:
722e706f7374756c6172744063617265626561742e6e6c(both)Questions
CompleteResourceTokenAuthdifferently than Runtime 3LO?userIdparameter?CompleteResourceTokenAutheven needed for Gateway 3LO, or does Gateway auto-complete sessions?IAM Permissions
Lambda role has:
{ "Effect": "Allow", "Action": "bedrock-agentcore:CompleteResourceTokenAuth", "Resource": "*" }Related Documentation
Any guidance on correctly implementing 3LO with Gateway would be greatly appreciated!