awslabs / aws-sdk-swift

Apache License 2.0
395 stars 77 forks source link

Create cognito credentials provider #324

Closed wooj2 closed 1 year ago

sebsto commented 1 year ago

When developing iOS or macOS apps without using Amplify, there are use cases where the app needs anonymous AWS credentials to call AWS API. Examples might be to send events to PinPoint, to send logs to cloudwatch, to fetch configuration from AppConfig, to interact with DynamoDB or Lambda.

In scenario where the app user is not authenticated, the simplest secure way to obtain AWS credentials is to use GetCredentialsForIdentity to obtain temporary credentials. Temporary credentials are limited in time (15 minutes by default) and limited in scope (limited to the permissions attached to the role assumed with Cognito)

Getting this right involves ~30-40 lines of undifferentiated heavy lifting code. It would be great to have these factored out as an AnonymousCredentialsProvider in the SDK.

Why do we need SDK Support ?

Because of the sandbox, macOS and iOS ap do not have access to ~/.aws directory where the SDK tries to fetch default credentials.

What Other SDK are proposing ?

I did not check all SDK, but the ones I am using regularly have manually crafted, higher-level credentials providers :

Prof of Concept

Here is a DynamoDB Example:

One time setup on the AWS side

  1. create an cognito identity pool
  2. enable unauthenticated access
  3. give permission to unauth role to access dynamodb

    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Sid": "VisualEditor0",
    "Effect": "Allow",
    "Action": [
    "dynamodb:PutItem",
    "dynamodb:GetItem",
    "dynamodb:Scan",
    "dynamodb:Query"
    ],
    "Resource": "arn:aws:dynamodb:eu-central-1:486652066693:table/swift-demo"
    }
    ]
    }

Swift function to create an Identity ID if it does not exist and then obtain credentials for that identity id

This function is equivalent to this command line

 aws --region eu-central-1 cognito-identity get-id --identity-pool-id eu-central-1:dc01c701-95c1-4c5a-915f-52bc5a3cdef5 --account-id 486652066693

 aws --region eu-central-1 cognito-identity get-credentials-for-identity --identity-id "eu-central-1:177b0c13-d519-43de-b24d-721253d5f133"
func getTempCredentials() async throws -> AWSClientRuntime.AWSCredentialsProvider {

    struct InvalidCredentialsError : Error {}
    print("get AWS credentials")
    do {
        let cognitoClient = try CognitoIdentityClient(region: "eu-central-1")

        // get a cognito identity id, only one per user and we cache it in user preferences
        var identityId = UserDefaults.standard.string(forKey: "identity-id")
        if identityId == nil {
            let cognitoGetIdRequest = GetIdInput(identityPoolId: "eu-central-1:dc01c701-95c1-4c5a-915f-52bc5a3cdef5")
            let cognitoGetIdResponse = try await cognitoClient.getId(input: cognitoGetIdRequest)
            identityId = cognitoGetIdResponse.identityId
            UserDefaults.standard.setValue(identityId, forKey: "identity-id")
        }

        // get aws credentials for that identity
        let cognitoRequest = GetCredentialsForIdentityInput(identityId: identityId)
        let cognitoResponse = try await cognitoClient.getCredentialsForIdentity(input: cognitoRequest)

        guard let credentials = cognitoResponse.credentials,
              let accessKeyId = credentials.accessKeyId,
              let secretKey = credentials.secretKey,
              let sessionToken = credentials.sessionToken else {
            print("no credentials returned")
            throw InvalidCredentialsError()
        }

        let tempCredentials = AWSCredentialsProviderStaticConfig(accessKey: accessKeyId,
                                                                 secret: secretKey,
                                                                 sessionToken: sessionToken)
        return try AWSClientRuntime.AWSCredentialsProvider.fromStatic(tempCredentials)
    } catch {
        throw error
    }
}

The code can use this credential provider to configure clients:

        let credentialsProvider = try await getTempCredentials()

        print("add item")
        let dynamoDBConfig
            = try DynamoDBClient.DynamoDBClientConfiguration(credentialsProvider: credentialsProvider,
                                                             region: "eu-central-1")

        let dynamoDBClient = DynamoDBClient(config: dynamoDBConfig)
dayaffe commented 1 year ago

This work will be tracked in Issue #1082