Skip to content

Commit 20fb3b5

Browse files
authored
Fix API Keys (#31)
* temporary disable API Keys until awslabs/aws-sdk-swift#1979 is fixed * swift format * add a note in the README * pave the road towards using a bearer token provider * remove debugging statements * swift-format * fix bearer token authentication * fix tests * fix format * add Examples/api-key in the list of integration tests
1 parent 5bfbb96 commit 20fb3b5

File tree

7 files changed

+83
-26
lines changed

7 files changed

+83
-26
lines changed

.github/workflows/pull_request.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
# We pass the list of examples here, but we can't pass an array as argument
3636
# Instead, we pass a String with a valid JSON array.
3737
# The workaround is mentioned here https://github.com/orgs/community/discussions/11692
38-
examples: "[ 'converse', 'converse-stream', 'text_chat' ]"
38+
examples: "[ 'api-key', 'converse', 'converse-stream', 'text_chat' ]"
3939

4040
swift-6-language-mode:
4141
name: Swift 6 Language Mode

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Package.resolved
1313
.vscode
1414
.env
1515
Makefile
16-
16+
**/temp
1717
node_modules
1818

1919
# **/backend

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,9 @@ let bedrock = try await BedrockService(
135135
)
136136
```
137137

138-
### API Key Authentication
138+
### API Key Authentication (temporary unavailable)
139+
140+
This capability will be available when issue [#1979](https://github.com/awslabs/aws-sdk-swift/issues/1979) from the AWS SDK for Swift will be fixed or a workaround provided.
139141

140142
Use an API key for authentication. API keys are generated in the AWS console and provide a simpler authentication method for specific use cases.
141143

Sources/BedrockAuthentication.swift

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,15 @@ public enum BedrockAuthentication: Sendable, CustomStringConvertible {
5656
/// Creates an AWS credential identity resolver depending on the authentication parameter.
5757
/// - Parameters:
5858
/// - authentication: The authentication type to use
59-
/// - Returns: An optional AWS credential identity resolver. A nil return value means that the default AWS credential provider chain will be used.
59+
/// - Returns: An optional AWS credential identity resolver. A nil return value means that the default AWS credential provider chain will be used or that the authentication type does not require a specific resolver (like `apiKey`).
6060
///
6161
func getAWSCredentialIdentityResolver(
6262
logger: Logger
6363
) async throws -> (any SmithyIdentity.AWSCredentialIdentityResolver)? {
6464

6565
switch self {
66-
case .default,
67-
.apiKey(_):
68-
return nil
66+
case .default, .apiKey(_):
67+
return nil //TODO should we throw an error when apiKey is used ?
6968
case .profile(let profileName):
7069
return try? ProfileAWSCredentialIdentityResolver(profileName: profileName)
7170
case .sso(let profileName):
@@ -84,4 +83,17 @@ public enum BedrockAuthentication: Sendable, CustomStringConvertible {
8483
return StaticAWSCredentialIdentityResolver(creds)
8584
}
8685
}
86+
87+
/// Creates a BearerTokenIdentityResolver depending on the authentication parameter.
88+
/// - Returns: An optional BearerTokenIdentityResolver. A nil return value means that the authentication type requires an AWSCredentialsProvider instead (like `default`, `profile`, `sso`, `webIdentity`, or `static`).
89+
/// - Note: Only `apiKey` authentication uses BearerTokenIdentityResolver.
90+
func getBearerTokenIdentityResolver(logger: Logger) -> (any SmithyIdentity.BearerTokenIdentityResolver)? {
91+
guard case .apiKey(let key) = self else {
92+
return nil // Only apiKey authentication uses BearerTokenIdentityResolver
93+
}
94+
95+
// Create a StaticBearerTokenIdentityResolver with the provided API key
96+
let identity = BearerTokenIdentity(token: key)
97+
return StaticBearerTokenIdentityResolver(token: identity)
98+
}
8799
}

Sources/BedrockService.swift

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public struct BedrockService: Sendable {
5252
) async throws {
5353
self.logger = logger ?? BedrockService.createLogger("bedrock.service")
5454
self.logger.trace(
55-
"Initializing SwiftBedrock",
55+
"Initializing BedrockService",
5656
metadata: ["region": .string(region.rawValue)]
5757
)
5858
self.region = region
@@ -121,7 +121,7 @@ public struct BedrockService: Sendable {
121121
-> BedrockClient
122122
{
123123
let config: BedrockClient.BedrockClientConfiguration = try await prepareConfig(
124-
region: region,
124+
initialConfig: BedrockClient.BedrockClientConfiguration(region: region.rawValue),
125125
authentication: authentication,
126126
logger: logger
127127
)
@@ -143,7 +143,9 @@ public struct BedrockService: Sendable {
143143
-> BedrockRuntimeClient
144144
{
145145
let config: BedrockRuntimeClient.BedrockRuntimeClientConfiguration = try await prepareConfig(
146-
region: region,
146+
initialConfig: BedrockRuntimeClient.BedrockRuntimeClientConfiguration(
147+
region: region.rawValue
148+
),
147149
authentication: authentication,
148150
logger: logger
149151
)
@@ -152,13 +154,18 @@ public struct BedrockService: Sendable {
152154

153155
/// Generic function to create client configuration and avoid duplication code.
154156
internal static func prepareConfig<C: BedrockConfigProtocol>(
155-
region: Region,
157+
initialConfig: C,
156158
authentication: BedrockAuthentication,
157159
logger: Logging.Logger
158160
) async throws -> C {
159-
var config: C = try await .init()
160161

161-
config.region = region.rawValue
162+
var config = initialConfig
163+
164+
if logger.logLevel == .trace {
165+
// enable trace HTTP requests and responses for the SDK
166+
// see https://github.com/smithy-lang/smithy-swift/blob/main/Sources/ClientRuntime/Telemetry/Logging/ClientLogMode.swift
167+
config.clientLogMode = .requestAndResponse
168+
}
162169

163170
// support profile, SSO, web identity and static authentication
164171
if let awsCredentialIdentityResolver = try? await authentication.getAWSCredentialIdentityResolver(
@@ -168,11 +175,25 @@ public struct BedrockService: Sendable {
168175
}
169176

170177
// support API keys
171-
if case .apiKey(let key) = authentication {
172-
config.httpClientConfiguration.defaultHeaders.add(
173-
name: "Authorization",
174-
value: "Bearer \(key)"
175-
)
178+
if case .apiKey(_) = authentication {
179+
// config.httpClientConfiguration.defaultHeaders.add(
180+
// name: "Authorization",
181+
// value: "Bearer \(key)"
182+
// )
183+
if let bearerTokenIdentityresolver = authentication.getBearerTokenIdentityResolver(logger: logger) {
184+
config.bearerTokenIdentityResolver = bearerTokenIdentityresolver
185+
186+
// force utilisation of a bearer token instead of AWS credentials + Signv4
187+
// see https://github.com/awslabs/aws-sdk-swift/blob/15b8951d108968f767f4199a3c011e27ac519d61/Sources/Services/AWSBedrockRuntime/Sources/AWSBedrockRuntime/AuthSchemeResolver.swift#L58
188+
config.authSchemeResolver = DefaultBedrockRuntimeAuthSchemeResolver(authSchemePreference: [
189+
"httpBearerAuth"
190+
])
191+
} else {
192+
// TODO: should we throw an error here ?
193+
logger.error(
194+
"API Key authentication is used but no BearerTokenIdentityResolver is provided. This will lead to issues."
195+
)
196+
}
176197
logger.trace("Using API Key for authentication")
177198
} else {
178199
logger.trace("Using AWS credentials for authentication")

Sources/Protocols/BedrockConfigProtocol.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,23 @@
1616
import AWSBedrock
1717
import AWSBedrockRuntime
1818
import ClientRuntime
19+
import SmithyHTTPAuthAPI
1920
import SmithyIdentity
2021

2122
protocol BedrockConfigProtocol {
22-
init() async throws
23+
// support regular AWS Credentials + Sigv4 authentication
2324
var awsCredentialIdentityResolver: any SmithyIdentity.AWSCredentialIdentityResolver { get set }
24-
var httpClientConfiguration: ClientRuntime.HttpClientConfiguration { get set }
25-
var region: String? { get set }
25+
26+
// support bearer token authentication (for API Keys)
27+
var bearerTokenIdentityResolver: any SmithyIdentity.BearerTokenIdentityResolver { get set }
28+
var authSchemeResolver: SmithyHTTPAuthAPI.AuthSchemeResolver { get set }
29+
30+
// not used at the moment, we use the bearer token instead
31+
//var httpClientConfiguration: ClientRuntime.HttpClientConfiguration { get set }
32+
33+
// for debugging
34+
var clientLogMode: ClientRuntime.ClientLogMode { get set }
35+
2636
}
2737
extension BedrockClient.BedrockClientConfiguration: @retroactive @unchecked Sendable, BedrockConfigProtocol {}
2838
extension BedrockRuntimeClient.BedrockRuntimeClientConfiguration: @retroactive @unchecked Sendable,

Tests/AuthenticationTests.swift

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
//===----------------------------------------------------------------------===//
1515

1616
import AWSBedrock
17+
import AWSBedrockRuntime
1718
import AwsCommonRuntimeKit
1819
import Logging
20+
import SmithyIdentity
1921
import Testing
2022

2123
@testable import BedrockService
@@ -88,16 +90,24 @@ extension BedrockServiceTests {
8890
// when
8991
// create bedrock configuration with API Key authentication
9092
let config: BedrockClient.BedrockClientConfiguration = try await BedrockService.prepareConfig(
91-
region: .useast1,
93+
initialConfig: BedrockClient.BedrockClientConfiguration(
94+
region: "us-east-1" // default region
95+
),
9296
authentication: auth,
9397
logger: Logger(label: "test.logger"),
9498
)
9599

96100
// then
97101
#expect(config.region == Region.useast1.rawValue) // default region
98-
#expect(
99-
config.httpClientConfiguration.defaultHeaders.value(for: "Authorization") == "Bearer test-api-key-12345"
100-
)
102+
103+
// check token
104+
let resolver = config.bearerTokenIdentityResolver as? StaticBearerTokenIdentityResolver
105+
let token = try await resolver?.getIdentity(identityProperties: nil).token
106+
#expect(token == testApiKey, "Expected token to match the API key")
107+
108+
// check bearer auth scheme
109+
let authScheme = (config.authSchemeResolver as? DefaultBedrockRuntimeAuthSchemeResolver)?.authSchemePreference
110+
#expect(authScheme?.contains("httpBearerAuth") == true, "Expected auth scheme to be HTTP Bearer")
101111

102112
}
103113

@@ -147,7 +157,9 @@ extension BedrockServiceTests {
147157

148158
// when
149159
let _: BedrockClient.BedrockClientConfiguration = try await BedrockService.prepareConfig(
150-
region: .useast1,
160+
initialConfig: BedrockClient.BedrockClientConfiguration(
161+
region: "us-east-1" // default region
162+
),
151163
authentication: auth,
152164
logger: logger
153165
)

0 commit comments

Comments
 (0)