Presigned URLs and requests with AWS SDK for Swift
Overview
You can presign certain AWS API operations in advance of their use to let the request be used at a later time without the need to provide credentials. With a presigned URL, the owner of a resource can grant an unauthorized person access to a resource for a limited time by simply sending the other user a presigned URL to use the resource.
Presigning basics
The AWS SDK for Swift provides functions that create presigned URLs or requests for each of
the service actions that support presigning. These functions take as their
input
parameter the same input struct used by the unsigned action, plus an
expiration time that restricts how long the presigned request will be valid and
usable.
For example, to create a presigned request for the Amazon S3 action
GetObject
:
let getInput = GetObjectInput(
bucket: bucket,
key: key
)
let presignedRequest: URLRequest
do {
presignedRequest = try await s3Client.presignedRequestForGetObject(
input: getInput,
expiration: TimeInterval(5 * 60)
)
} catch {
throw TransferError.signingError
}
This first creates a GetObjectInput
struct to identify the object to retrieve, then creates a
Foundation URLRequest
with the presigned request by calling the SDK for Swift
function presignedRequestForGetObject(input: expiration:)
, specifying the input
struct and a five-minute expiration period. The resulting request can then be sent by
anyone, up to five minutes after the request was created. The codebase that sends the
request can be in a different application, and even written in a different programming
language.
Advanced presigning configuration
The SDK's input structs used to pass options into presignable operations have two
methods you can call to generate presigned requests or URLs. For example, the
PutObjectInput
struct has the methods presign(config: expiration:)
and presignURL(config: expiration:)
. These are useful if you need to apply
to the operation a configuration other than the one used when initializing the service
client.
In this example, the AsyncHTTPClient
package from Apple's swift-server
project is used to create an
HTTPClientRequest
, which in turn is used to send the file to Amazon S3. A custom
configuration is created that lets the SDK make up to six attempts to send an object to
Amazon S3:
let fileData = try Data(contentsOf: fileURL)
let dataStream = ByteStream.data(fileData)
let presignedURL: URL
// Create a presigned URL representing the `PutObject` request that
// will upload the file to Amazon S3. If no URL is generated, a
// `TransferError.signingError` is thrown.
let putConfig = try await S3Client.S3ClientConfiguration(
maxAttempts: 6,
region: region
)
do {
let url = try await PutObjectInput(
body: dataStream,
bucket: bucket,
key: fileName
).presignURL(
config: putConfig,
expiration: TimeInterval(10 * 60)
)
guard let url = url else {
throw TransferError.signingError
}
presignedURL = url
} catch {
throw TransferError.signingError
}
// Send the HTTP request and upload the file to Amazon S3.
var request = HTTPClientRequest(url: presignedURL.absoluteString)
request.method = .PUT
request.body = .bytes(fileData)
_ = try await HTTPClient.shared.execute(request, timeout: .seconds(5*60))
This creates a new S3ClientConfiguration
struct with the value of
maxAttempts
set to 6, then creates a PutObjectInput
struct which is used to generate an URL that's presigned
using the custom configuration. The presigned URL is a standard Foundation
URL
object.
To use the AsyncHTTPClient
package to send the data to Amazon S3, an
HTTPClientRequest
is created using the presigned URL as the destination
URL. The request's method is set to .PUT
, indicating that it's an upload
request. Then the request body is set to the contents of fileData
, which
contains the Data
to be sent to Amazon S3.
Finally, the request is executed using the shared HTTPClient
managed by
AsyncHTTPClient
.