Skip to content

Commit 5bfbb96

Browse files
authored
[auth] Add support for Bedrock API Keys (#30)
* add support for Bedrock API Key * add documentation for authentication * add a test on AWS_BEARER_TOKEN_BEDROCK * add example * swift-format * add license header
1 parent e9d22c8 commit 5bfbb96

File tree

9 files changed

+393
-18
lines changed

9 files changed

+393
-18
lines changed

Examples/api-key/.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc

Examples/api-key/Package.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// swift-tools-version: 6.0
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "APIKey",
8+
platforms: [.macOS(.v15), .iOS(.v18), .tvOS(.v18)],
9+
products: [
10+
.executable(name: "APIKey", targets: ["APIKey"])
11+
],
12+
dependencies: [
13+
// for production use, uncomment the following line
14+
// .package(url: "https://github.com/build-on-aws/swift-bedrock-library.git", branch: "main"),
15+
16+
// for local development, use the following line
17+
.package(name: "swift-bedrock-library", path: "../.."),
18+
19+
.package(url: "https://github.com/apple/swift-log.git", from: "1.6.0"),
20+
],
21+
targets: [
22+
// Targets are the basic building blocks of a package, defining a module or a test suite.
23+
// Targets can depend on other targets in this package and products from dependencies.
24+
.executableTarget(
25+
name: "APIKey",
26+
dependencies: [
27+
.product(name: "BedrockService", package: "swift-bedrock-library"),
28+
.product(name: "Logging", package: "swift-log"),
29+
]
30+
)
31+
]
32+
)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Bedrock Library open source project
4+
//
5+
// Copyright (c) 2025 Amazon.com, Inc. or its affiliates
6+
// and the Swift Bedrock Library project authors
7+
// Licensed under Apache License v2.0
8+
//
9+
// See LICENSE.txt for license information
10+
// See CONTRIBUTORS.txt for the list of Swift Bedrock Library project authors
11+
//
12+
// SPDX-License-Identifier: Apache-2.0
13+
//
14+
//===----------------------------------------------------------------------===//
15+
16+
// You can create short-lived or long-term API keys in the AWS Management Console.
17+
// see documentation : https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys-generate.html
18+
// link to the console : https://console.aws.amazon.com/bedrock/home#/api-keys?tab=short-term
19+
let myApiKey = "bedrock-api-key-YmVkcm9jay ... (redacted for brevity) .... b249MQ=="
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Bedrock Library open source project
4+
//
5+
// Copyright (c) 2025 Amazon.com, Inc. or its affiliates
6+
// and the Swift Bedrock Library project authors
7+
// Licensed under Apache License v2.0
8+
//
9+
// See LICENSE.txt for license information
10+
// See CONTRIBUTORS.txt for the list of Swift Bedrock Library project authors
11+
//
12+
// SPDX-License-Identifier: Apache-2.0
13+
//
14+
//===----------------------------------------------------------------------===//
15+
16+
import BedrockService
17+
import Logging
18+
19+
@main
20+
struct Main {
21+
static func main() async throws {
22+
do {
23+
try await Main.converse()
24+
} catch {
25+
print("Error:\n\(error)")
26+
}
27+
}
28+
static func converse() async throws {
29+
var logger = Logger(label: "APIKey")
30+
logger.logLevel = .debug
31+
32+
// generate an API Key in the AWS Management Console
33+
// see https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys-generate.html
34+
let bedrock = try await BedrockService(
35+
region: .useast1,
36+
logger: logger,
37+
authentication: .apiKey(key: myApiKey) // define your API Key in APIKey.swift
38+
)
39+
40+
// select a model that supports the converse modality
41+
// models must be enabled in your AWS account
42+
let model: BedrockModel = .nova_lite
43+
44+
guard model.hasConverseModality() else {
45+
throw MyError.incorrectModality("\(model.name) does not support converse")
46+
}
47+
48+
// create a request
49+
let builder = try ConverseRequestBuilder(with: model)
50+
.withPrompt("What is an API key?")
51+
52+
// send the request
53+
let reply = try await bedrock.converse(with: builder)
54+
55+
print("Assistant: \(reply)")
56+
}
57+
58+
enum MyError: Error {
59+
case incorrectModality(String)
60+
}
61+
}

README.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,96 @@ Use the `listModels()` function to test your set-up. This function will return a
7373
let models = try await bedrock.listModels()
7474
```
7575

76+
## Authentication
77+
78+
The Swift Bedrock Library supports multiple authentication methods to work with Amazon Bedrock. By default, it uses the standard AWS credential provider chain, but you can specify different authentication types when initializing the `BedrockService`.
79+
80+
### Default Authentication
81+
82+
Uses the standard AWS credential provider chain, which checks for credentials in the following order:
83+
1. Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`)
84+
2. AWS credentials file (`~/.aws/credentials`)
85+
3. AWS config file (`~/.aws/config`)
86+
4. IAM roles for Amazon EC2 instances
87+
5. IAM roles for tasks (Amazon ECS)
88+
6. IAM roles for Lambda functions
89+
90+
```swift
91+
let bedrock = try await BedrockService(
92+
region: .uswest2
93+
// authentication defaults to .default
94+
)
95+
```
96+
97+
### Profile-based Authentication
98+
99+
Use a specific profile from your AWS credentials file. This is useful when you have multiple AWS accounts or roles configured locally.
100+
101+
```swift
102+
let bedrock = try await BedrockService(
103+
region: .uswest2,
104+
authentication: .profile(profileName: "my-profile")
105+
)
106+
```
107+
108+
### SSO Authentication
109+
110+
Use AWS Single Sign-On (SSO) authentication. You must run `aws sso login --profile <profile_name>` before using this authentication method.
111+
112+
```swift
113+
let bedrock = try await BedrockService(
114+
region: .uswest2,
115+
authentication: .sso(profileName: "my-sso-profile")
116+
)
117+
```
118+
119+
### Web Identity Token Authentication
120+
121+
Use a JWT token from an external identity provider (like Sign In with Apple or Google) to assume an IAM role. This is particularly useful for iOS, tvOS, and macOS applications where traditional AWS CLI-based authentication isn't available.
122+
123+
```swift
124+
let bedrock = try await BedrockService(
125+
region: .uswest2,
126+
authentication: .webIdentity(
127+
token: jwtToken,
128+
roleARN: "arn:aws:iam::123456789012:role/MyAppRole",
129+
region: .uswest2,
130+
notification: {
131+
// Optional: Called on main thread when credentials are retrieved
132+
print("AWS credentials updated")
133+
}
134+
)
135+
)
136+
```
137+
138+
### API Key Authentication
139+
140+
Use an API key for authentication. API keys are generated in the AWS console and provide a simpler authentication method for specific use cases.
141+
142+
```swift
143+
let bedrock = try await BedrockService(
144+
region: .uswest2,
145+
authentication: .apiKey(key: "your-api-key-here")
146+
)
147+
```
148+
149+
### Static Credentials Authentication
150+
151+
Use static AWS credentials directly. **This method is strongly discouraged for production use** and should only be used for testing and debugging purposes.
152+
153+
```swift
154+
let bedrock = try await BedrockService(
155+
region: .uswest2,
156+
authentication: .static(
157+
accessKey: "AKIAIOSFODNN7EXAMPLE",
158+
secretKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
159+
sessionToken: "optional-session-token"
160+
)
161+
)
162+
```
163+
164+
**Security Note**: Never hardcode credentials in your source code or commit them to version control. Use environment variables, secure credential storage, or other secure methods to manage credentials in production applications.
165+
76166
## Chatting using the Converse or ConverseStream API
77167

78168
### Text prompt

Sources/BedrockAuthentication.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@ import SmithyIdentity
2424
/// - `webIdentity`: Use a web identity token (JWT) to assume an IAM role. This is useful for applications running on iOS, tvOS or macOS where you cannot use the AWS CLI. Typically, the application authenticates the user with an external Identity provider (such as Sign In with Apple or Login With Google) and receives a JWT token. The application then uses this token to assume an IAM role and receive temporary AWS credentials. Some additional configuration is required on your AWS account to allow this. See https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_oidc.html for more information. If you use Sign In With Apple, read https://docs.aws.amazon.com/sdk-for-swift/latest/developer-guide/apple-integration.html for more information.
2525
/// Because `webidentity` is often used by application presenting a user interface. This method of authentication allows you to pass an optional closure that will be called when the credentials are retrieved. This is useful for updating the UI or notifying the user. The closure is called on the main (UI) thread.
2626
/// - `static`: Use static AWS credentials. We strongly recommend to not use this option in production. This might be useful in some rare cases when testing and debugging.
27+
/// - `apiKey`: Use an API key to authenticate. This is useful for applications that do not require full AWS credentials and only need to access specific APIs. The API key is passed as a string. API Keys are generated in the AWS console.
2728
public enum BedrockAuthentication: Sendable, CustomStringConvertible {
2829
case `default`
2930
case profile(profileName: String = "default")
3031
case sso(profileName: String = "default")
3132
case webIdentity(token: String, roleARN: String, region: Region, notification: @Sendable () -> Void = {})
3233
case `static`(accessKey: String, secretKey: String, sessionToken: String)
34+
case apiKey(key: String)
3335

3436
public var description: String {
3537
switch self {
@@ -43,6 +45,8 @@ public enum BedrockAuthentication: Sendable, CustomStringConvertible {
4345
return "webIdentity: \(redactingSecret(secret: token)), roleARN: \(roleARN), region: \(region)"
4446
case .static(let accessKey, let secretKey, _):
4547
return "static: \(accessKey), secretKey: \(redactingSecret(secret: secretKey))"
48+
case .apiKey(let key):
49+
return "apiKey: \(redactingSecret(secret: key))"
4650
}
4751
}
4852
private func redactingSecret(secret: String) -> String {
@@ -59,7 +63,8 @@ public enum BedrockAuthentication: Sendable, CustomStringConvertible {
5963
) async throws -> (any SmithyIdentity.AWSCredentialIdentityResolver)? {
6064

6165
switch self {
62-
case .default:
66+
case .default,
67+
.apiKey(_):
6368
return nil
6469
case .profile(let profileName):
6570
return try? ProfileAWSCredentialIdentityResolver(profileName: profileName)
@@ -74,7 +79,7 @@ public enum BedrockAuthentication: Sendable, CustomStringConvertible {
7479
notify: notification
7580
)
7681
case .static(let accessKey, let secretKey, let sessionToken):
77-
logger.warning("Using static AWS credentials. This is not recommended for production.")
82+
logger.info("Using static AWS credentials. This is not recommended for production.")
7883
let creds = AWSCredentialIdentity(accessKey: accessKey, secret: secretKey, sessionToken: sessionToken)
7984
return StaticAWSCredentialIdentityResolver(creds)
8085
}

Sources/BedrockService.swift

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ import AwsCommonRuntimeKit
2020
import Foundation
2121
import Logging
2222

23+
// for setenv and unsetenv functions
24+
#if os(Linux)
25+
import Glibc
26+
#else
27+
import Darwin.C
28+
#endif
29+
2330
public struct BedrockService: Sendable {
2431
package let region: Region
2532
package let logger: Logging.Logger
@@ -106,21 +113,18 @@ public struct BedrockService: Sendable {
106113
/// - authentication: The authentication type to use
107114
/// - Returns: Configured BedrockClientProtocol instance
108115
/// - Throws: Error if client creation fails
109-
static private func createBedrockClient(
116+
internal static func createBedrockClient(
110117
region: Region,
111118
authentication: BedrockAuthentication,
112119
logger: Logging.Logger
113120
) async throws
114-
-> BedrockClientProtocol
121+
-> BedrockClient
115122
{
116-
let config = try await BedrockClient.BedrockClientConfiguration(
117-
region: region.rawValue
118-
)
119-
if let awsCredentialIdentityResolver = try? await authentication.getAWSCredentialIdentityResolver(
123+
let config: BedrockClient.BedrockClientConfiguration = try await prepareConfig(
124+
region: region,
125+
authentication: authentication,
120126
logger: logger
121-
) {
122-
config.awsCredentialIdentityResolver = awsCredentialIdentityResolver
123-
}
127+
)
124128
return BedrockClient(config: config)
125129
}
126130

@@ -130,24 +134,56 @@ public struct BedrockService: Sendable {
130134
/// - authentication: The authentication type to use
131135
/// - Returns: Configured BedrockRuntimeClientProtocol instance
132136
/// - Throws: Error if client creation fails
133-
static private func createBedrockRuntimeClient(
137+
internal static func createBedrockRuntimeClient(
134138
region: Region,
135139
authentication: BedrockAuthentication,
136140
logger: Logging.Logger
137141
)
138142
async throws
139-
-> BedrockRuntimeClientProtocol
143+
-> BedrockRuntimeClient
140144
{
141-
let config =
142-
try await BedrockRuntimeClient.BedrockRuntimeClientConfiguration(
143-
region: region.rawValue
144-
)
145+
let config: BedrockRuntimeClient.BedrockRuntimeClientConfiguration = try await prepareConfig(
146+
region: region,
147+
authentication: authentication,
148+
logger: logger
149+
)
150+
return BedrockRuntimeClient(config: config)
151+
}
152+
153+
/// Generic function to create client configuration and avoid duplication code.
154+
internal static func prepareConfig<C: BedrockConfigProtocol>(
155+
region: Region,
156+
authentication: BedrockAuthentication,
157+
logger: Logging.Logger
158+
) async throws -> C {
159+
var config: C = try await .init()
160+
161+
config.region = region.rawValue
162+
163+
// support profile, SSO, web identity and static authentication
145164
if let awsCredentialIdentityResolver = try? await authentication.getAWSCredentialIdentityResolver(
146165
logger: logger
147166
) {
148167
config.awsCredentialIdentityResolver = awsCredentialIdentityResolver
149168
}
150-
return BedrockRuntimeClient(config: config)
169+
170+
// support API keys
171+
if case .apiKey(let key) = authentication {
172+
config.httpClientConfiguration.defaultHeaders.add(
173+
name: "Authorization",
174+
value: "Bearer \(key)"
175+
)
176+
logger.trace("Using API Key for authentication")
177+
} else {
178+
logger.trace("Using AWS credentials for authentication")
179+
}
180+
181+
//We uncheck AWS_BEARER_TOKEN_BEDROCK to avoid conflict with future AWS SDK version
182+
//see https://docs.aws.amazon.com/bedrock/latest/userguide/getting-started-api-keys.html
183+
//FIXME: there is a risk of side effect here - what other ways we have to ignore this variable ?
184+
unsetenv("AWS_BEARER_TOKEN_BEDROCK")
185+
186+
return config
151187
}
152188

153189
func handleCommonError(_ error: Error, context: String) throws -> Never {

0 commit comments

Comments
 (0)