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.