From 22085a9a1765d4894092d64c8baf2b3503547e21 Mon Sep 17 00:00:00 2001 From: wirjo Date: Fri, 17 Apr 2026 07:34:18 +1000 Subject: [PATCH] feat(bedrock): support Bearer token auth for Converse API (#3125) Adds bearer token authentication for the Bedrock Converse API, enabling users to authenticate with an API key instead of SigV4/IAM credentials. When a bearer token is available (via `options.bearerToken` or the `AWS_BEARER_TOKEN_BEDROCK` env var), the provider: 1. Sets dummy credentials to prevent SDK credential resolution errors 2. Injects middleware after SigV4 signing that replaces the Authorization header with `Bearer ` and removes SigV4-specific headers This uses the official `bedrock:CallWithBearerToken` IAM action, which is a documented AWS feature for API key auth on Bedrock endpoints. Use case: users who receive a Bedrock API key (bearer token) from the AWS console or their admin, without having IAM access keys or instance roles. Similar to how ANTHROPIC_API_KEY works for direct Anthropic API. Required IAM permission on the token's identity: bedrock:CallWithBearerToken Tested: Bearer token successfully authenticates against Bedrock Converse API (returns correct 403 for missing IAM permission, not auth format error). SigV4 path is unchanged when no bearer token is set. --- packages/ai/src/providers/amazon-bedrock.ts | 34 +++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/ai/src/providers/amazon-bedrock.ts b/packages/ai/src/providers/amazon-bedrock.ts index 4b2e61f6..a1f328cb 100644 --- a/packages/ai/src/providers/amazon-bedrock.ts +++ b/packages/ai/src/providers/amazon-bedrock.ts @@ -74,6 +74,12 @@ export interface BedrockOptions extends StreamOptions { * Tags appear in AWS Cost Explorer split cost allocation data. * @see https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ConverseStream.html */ requestMetadata?: Record; + /** Bearer token for Bedrock API key authentication. + * When set, bypasses SigV4 signing and sends Authorization: Bearer instead. + * Requires `bedrock:CallWithBearerToken` IAM permission on the token's identity. + * Set via AWS_BEARER_TOKEN_BEDROCK env var or pass directly. + * @see https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonbedrock.html */ + bearerToken?: string; } type Block = (TextContent | ThinkingContent | ToolCall) & { index?: number; partialJson?: string }; @@ -110,6 +116,9 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream", BedrockOpt profile: options.profile, }; + // Resolve bearer token for API key auth (bypasses SigV4) + const bearerToken = options.bearerToken || process.env.AWS_BEARER_TOKEN_BEDROCK || undefined; + // in Node.js/Bun environment only if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) { // Region resolution: explicit option > env vars > SDK default chain. @@ -130,6 +139,15 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream", BedrockOpt }; } + // Bearer token auth: use API key instead of SigV4 signing. + // Requires bedrock:CallWithBearerToken IAM permission. + if (bearerToken && process.env.AWS_BEDROCK_SKIP_AUTH !== "1") { + config.credentials = { + accessKeyId: "bearer-token-auth", + secretAccessKey: "bearer-token-auth", + }; + } + if ( process.env.HTTP_PROXY || process.env.HTTPS_PROXY || @@ -164,6 +182,22 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream", BedrockOpt try { const client = new BedrockRuntimeClient(config); + // Inject bearer token middleware after SigV4 signing + if (bearerToken) { + client.middlewareStack.addRelativeTo( + (next) => async (args: any) => { + if (args.request?.headers) { + args.request.headers["authorization"] = `Bearer ${bearerToken}`; + delete args.request.headers["x-amz-date"]; + delete args.request.headers["x-amz-security-token"]; + delete args.request.headers["x-amz-content-sha256"]; + } + return next(args); + }, + { relation: "after", toMiddleware: "awsAuthMiddleware", name: "bearerTokenAuth" }, + ); + } + const cacheRetention = resolveCacheRetention(options.cacheRetention); let commandInput = { modelId: model.id,