Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Examples/api-key/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
32 changes: 32 additions & 0 deletions Examples/api-key/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "APIKey",
platforms: [.macOS(.v15), .iOS(.v18), .tvOS(.v18)],
products: [
.executable(name: "APIKey", targets: ["APIKey"])
],
dependencies: [
// for production use, uncomment the following line
// .package(url: "https://github.com/build-on-aws/swift-bedrock-library.git", branch: "main"),

// for local development, use the following line
.package(name: "swift-bedrock-library", path: "../.."),

.package(url: "https://github.com/apple/swift-log.git", from: "1.6.0"),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.executableTarget(
name: "APIKey",
dependencies: [
.product(name: "BedrockService", package: "swift-bedrock-library"),
.product(name: "Logging", package: "swift-log"),
]
)
]
)
19 changes: 19 additions & 0 deletions Examples/api-key/Sources/APIKey.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Bedrock Library open source project
//
// Copyright (c) 2025 Amazon.com, Inc. or its affiliates
// and the Swift Bedrock Library project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift Bedrock Library project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

// You can create short-lived or long-term API keys in the AWS Management Console.
// see documentation : https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys-generate.html
// link to the console : https://console.aws.amazon.com/bedrock/home#/api-keys?tab=short-term
let myApiKey = "bedrock-api-key-YmVkcm9jay ... (redacted for brevity) .... b249MQ=="
61 changes: 61 additions & 0 deletions Examples/api-key/Sources/Converse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Bedrock Library open source project
//
// Copyright (c) 2025 Amazon.com, Inc. or its affiliates
// and the Swift Bedrock Library project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift Bedrock Library project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import BedrockService
import Logging

@main
struct Main {
static func main() async throws {
do {
try await Main.converse()
} catch {
print("Error:\n\(error)")
}
}
static func converse() async throws {
var logger = Logger(label: "APIKey")
logger.logLevel = .debug

// generate an API Key in the AWS Management Console
// see https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys-generate.html
let bedrock = try await BedrockService(
region: .useast1,
logger: logger,
authentication: .apiKey(key: myApiKey) // define your API Key in APIKey.swift
)

// select a model that supports the converse modality
// models must be enabled in your AWS account
let model: BedrockModel = .nova_lite

guard model.hasConverseModality() else {
throw MyError.incorrectModality("\(model.name) does not support converse")
}

// create a request
let builder = try ConverseRequestBuilder(with: model)
.withPrompt("What is an API key?")

// send the request
let reply = try await bedrock.converse(with: builder)

print("Assistant: \(reply)")
}

enum MyError: Error {
case incorrectModality(String)
}
}
90 changes: 90 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,96 @@ Use the `listModels()` function to test your set-up. This function will return a
let models = try await bedrock.listModels()
```

## Authentication

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`.

### Default Authentication

Uses the standard AWS credential provider chain, which checks for credentials in the following order:
1. Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`)
2. AWS credentials file (`~/.aws/credentials`)
3. AWS config file (`~/.aws/config`)
4. IAM roles for Amazon EC2 instances
5. IAM roles for tasks (Amazon ECS)
6. IAM roles for Lambda functions

```swift
let bedrock = try await BedrockService(
region: .uswest2
// authentication defaults to .default
)
```

### Profile-based Authentication

Use a specific profile from your AWS credentials file. This is useful when you have multiple AWS accounts or roles configured locally.

```swift
let bedrock = try await BedrockService(
region: .uswest2,
authentication: .profile(profileName: "my-profile")
)
```

### SSO Authentication

Use AWS Single Sign-On (SSO) authentication. You must run `aws sso login --profile <profile_name>` before using this authentication method.

```swift
let bedrock = try await BedrockService(
region: .uswest2,
authentication: .sso(profileName: "my-sso-profile")
)
```

### Web Identity Token Authentication

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.

```swift
let bedrock = try await BedrockService(
region: .uswest2,
authentication: .webIdentity(
token: jwtToken,
roleARN: "arn:aws:iam::123456789012:role/MyAppRole",
region: .uswest2,
notification: {
// Optional: Called on main thread when credentials are retrieved
print("AWS credentials updated")
}
)
)
```

### API Key Authentication

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

```swift
let bedrock = try await BedrockService(
region: .uswest2,
authentication: .apiKey(key: "your-api-key-here")
)
```

### Static Credentials Authentication

Use static AWS credentials directly. **This method is strongly discouraged for production use** and should only be used for testing and debugging purposes.

```swift
let bedrock = try await BedrockService(
region: .uswest2,
authentication: .static(
accessKey: "AKIAIOSFODNN7EXAMPLE",
secretKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
sessionToken: "optional-session-token"
)
)
```

**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.

## Chatting using the Converse or ConverseStream API

### Text prompt
Expand Down
9 changes: 7 additions & 2 deletions Sources/BedrockAuthentication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ import SmithyIdentity
/// - `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.
/// 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.
/// - `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.
/// - `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.
public enum BedrockAuthentication: Sendable, CustomStringConvertible {
case `default`
case profile(profileName: String = "default")
case sso(profileName: String = "default")
case webIdentity(token: String, roleARN: String, region: Region, notification: @Sendable () -> Void = {})
case `static`(accessKey: String, secretKey: String, sessionToken: String)
case apiKey(key: String)

public var description: String {
switch self {
Expand All @@ -43,6 +45,8 @@ public enum BedrockAuthentication: Sendable, CustomStringConvertible {
return "webIdentity: \(redactingSecret(secret: token)), roleARN: \(roleARN), region: \(region)"
case .static(let accessKey, let secretKey, _):
return "static: \(accessKey), secretKey: \(redactingSecret(secret: secretKey))"
case .apiKey(let key):
return "apiKey: \(redactingSecret(secret: key))"
}
}
private func redactingSecret(secret: String) -> String {
Expand All @@ -59,7 +63,8 @@ public enum BedrockAuthentication: Sendable, CustomStringConvertible {
) async throws -> (any SmithyIdentity.AWSCredentialIdentityResolver)? {

switch self {
case .default:
case .default,
.apiKey(_):
return nil
case .profile(let profileName):
return try? ProfileAWSCredentialIdentityResolver(profileName: profileName)
Expand All @@ -74,7 +79,7 @@ public enum BedrockAuthentication: Sendable, CustomStringConvertible {
notify: notification
)
case .static(let accessKey, let secretKey, let sessionToken):
logger.warning("Using static AWS credentials. This is not recommended for production.")
logger.info("Using static AWS credentials. This is not recommended for production.")
let creds = AWSCredentialIdentity(accessKey: accessKey, secret: secretKey, sessionToken: sessionToken)
return StaticAWSCredentialIdentityResolver(creds)
}
Expand Down
68 changes: 52 additions & 16 deletions Sources/BedrockService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ import AwsCommonRuntimeKit
import Foundation
import Logging

// for setenv and unsetenv functions
#if os(Linux)
import Glibc
#else
import Darwin.C
#endif

public struct BedrockService: Sendable {
package let region: Region
package let logger: Logging.Logger
Expand Down Expand Up @@ -106,21 +113,18 @@ public struct BedrockService: Sendable {
/// - authentication: The authentication type to use
/// - Returns: Configured BedrockClientProtocol instance
/// - Throws: Error if client creation fails
static private func createBedrockClient(
internal static func createBedrockClient(
region: Region,
authentication: BedrockAuthentication,
logger: Logging.Logger
) async throws
-> BedrockClientProtocol
-> BedrockClient
{
let config = try await BedrockClient.BedrockClientConfiguration(
region: region.rawValue
)
if let awsCredentialIdentityResolver = try? await authentication.getAWSCredentialIdentityResolver(
let config: BedrockClient.BedrockClientConfiguration = try await prepareConfig(
region: region,
authentication: authentication,
logger: logger
) {
config.awsCredentialIdentityResolver = awsCredentialIdentityResolver
}
)
return BedrockClient(config: config)
}

Expand All @@ -130,24 +134,56 @@ public struct BedrockService: Sendable {
/// - authentication: The authentication type to use
/// - Returns: Configured BedrockRuntimeClientProtocol instance
/// - Throws: Error if client creation fails
static private func createBedrockRuntimeClient(
internal static func createBedrockRuntimeClient(
region: Region,
authentication: BedrockAuthentication,
logger: Logging.Logger
)
async throws
-> BedrockRuntimeClientProtocol
-> BedrockRuntimeClient
{
let config =
try await BedrockRuntimeClient.BedrockRuntimeClientConfiguration(
region: region.rawValue
)
let config: BedrockRuntimeClient.BedrockRuntimeClientConfiguration = try await prepareConfig(
region: region,
authentication: authentication,
logger: logger
)
return BedrockRuntimeClient(config: config)
}

/// Generic function to create client configuration and avoid duplication code.
internal static func prepareConfig<C: BedrockConfigProtocol>(
Copy link

Copilot AI Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The prepareConfig function bundles initialization, credential resolver setup, API-key header injection, and environment cleanup. Consider splitting these responsibilities into focused helper methods or adding detailed comments to clarify each step and improve readability and testability.

Copilot uses AI. Check for mistakes.
region: Region,
authentication: BedrockAuthentication,
logger: Logging.Logger
) async throws -> C {
var config: C = try await .init()

config.region = region.rawValue

// support profile, SSO, web identity and static authentication
if let awsCredentialIdentityResolver = try? await authentication.getAWSCredentialIdentityResolver(
logger: logger
) {
config.awsCredentialIdentityResolver = awsCredentialIdentityResolver
}
return BedrockRuntimeClient(config: config)

// support API keys
if case .apiKey(let key) = authentication {
config.httpClientConfiguration.defaultHeaders.add(
name: "Authorization",
value: "Bearer \(key)"
)
logger.trace("Using API Key for authentication")
} else {
logger.trace("Using AWS credentials for authentication")
}

//We uncheck AWS_BEARER_TOKEN_BEDROCK to avoid conflict with future AWS SDK version
//see https://docs.aws.amazon.com/bedrock/latest/userguide/getting-started-api-keys.html
//FIXME: there is a risk of side effect here - what other ways we have to ignore this variable ?
unsetenv("AWS_BEARER_TOKEN_BEDROCK")
Copy link

Copilot AI Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Unsetting the environment variable globally may have unintended side effects for other concurrent code. Consider scoping this change or documenting its impact on other AWS SDK behavior.

Suggested change
unsetenv("AWS_BEARER_TOKEN_BEDROCK")
// Temporarily remove AWS_BEARER_TOKEN_BEDROCK from the environment for this configuration
var environment = ProcessInfo.processInfo.environment
environment["AWS_BEARER_TOKEN_BEDROCK"] = nil
// Pass the modified environment to the relevant configuration or process if needed

Copilot uses AI. Check for mistakes.

return config
}

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