Cloudwatch Evidently on iOS

4

I am trying to figure out how to retrieve Evidently feature flags in an iOS app. I did create the flag on AWS console and also an UnAuthenticated IAM Role to use with Evidently.

I have so far the Cognito authentication code:

   // Initialize the Amazon Cognito credentials provider
   let credentialsProvider = AWSCognitoCredentialsProvider(regionType:.USEast1, identityPoolId:"us-east-1:xxx")
   let configuration = AWSServiceConfiguration(region:.USEast1, credentialsProvider:credentialsProvider)
   AWSServiceManager.default().defaultServiceConfiguration = configuration

BUT the iOS SDK does not seem to include Evidently or at least I could not find it.

Any advice or examples on how to call Evidently?

Can Evidently even be used with iOS?

There seems to be APIs to call on Evidently directly but I did not find any example on how to use it with all the authentication flow. Also cannot figure out if it would be possible to authenticate with the Congito SDK and then call the Evidently using the API.

2 Answers
1

Hello, based on the code you shared, It looks like you are using the old and deprecated AWS Mobile SDK for iOS

You should use the AWS SDK for Swift instead.

I will share a code sample in the coming days.

answered a year ago
  • Thank you so much for the reply, will I be able to access Cloudwatch Evidently with the new SDK? Thank you so much for the reply, will I be able to access Cloudwatch Evidently with the new SDK?

  • Yes, the new AWS SDk for Swift has automaticly generated support for most AWS services and their API. I tried it yesterday night and found an issue preventing calling Evidently (see https://github.com/awslabs/aws-sdk-swift/issues/865). I will follow up with the team and post a code sample here when fixed

0

Here is some code to acquire a temporary access key and secret key from Cognito. This is for unauthenticated users. If your user are authenticated, the flow is slightly different. In both cases, It requires some setup on the cognito side.

  1. create an cognito identity pool
  2. enable unauthenticated access
  3. give permission to unauth role to access Evidently
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "EvidentlyDemoEvaluateFeature",
            "Effect": "Allow",
            "Action": "evidently:EvaluateFeature",
            "Resource": "arn:aws:evidently:us-west-2:0123456789:project/demo/feature/*"
        }
    ]
}

Role's trust permission must be like this (replace region and cognito id as appropriate)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "cognito-identity.amazonaws.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "cognito-identity.amazonaws.com:aud": "us-west-2:<your cognito id>"
                },
                "ForAnyValue:StringLike": {
                    "cognito-identity.amazonaws.com:amr": "unauthenticated"
                }
            }
        }
    ]
}
  1. generate an identity in the pool (once per user)
aws --region us-west-2 cognito-identity get-id --identity-pool-id eu-central-1:<cognito id> --account-id <account id>

Be sure to update region in all commands above

GetTempCredentials()

Be sure to update region and identity id in the command below

The traditional method of using unauthenticated identities obtained with fromCognitoIdentityPool(...) IS NOT WORKING for Evidently. It causes an error like:

AccessDeniedException: User: arn:aws:sts::0123456789:assumed-role/Cognito_evidentlydemoUnauth_Role/CognitoIdentityCredentials is not authorized to perform: evidently:EvaluateFeature on resource: arn:aws:evidently:us-west-2:0123456789:project/demo/feature/EditableGuestbook because no session policy allows the evidently:EvaluateFeature action

This happens because Cognito Identity Pool scopes down permissions to selected list of services. Evidently is not part of it. See the list of supported services here https://docs.aws.amazon.com/cognito/latest/developerguide/iam-roles.html

The workaround to handle unauthenticated identities is to obtain credentials manually Cognito and STS. This is required for unauth role. Be sure to enable Basic (classic) flow in Cognito to make this work.

func getTempCredentialsforEvidently() async throws -> AWSClientRuntime.AWSCredentialsProvider {
    
    struct InvalidCredentialsError : Error {}
    
    let IDENTITY_ID = "identity-id"
    let ROLE_ARN = "arn:aws:iam::<your account id>:role/<cognito unauth role name from above>
    
    do {
        let cognitoClient = try CognitoIdentityClient(region: REGION)
        
        // 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: IDENTITY_POOL_ID)
            let cognitoGetIdResponse = try await cognitoClient.getId(input: cognitoGetIdRequest)
            identityId = cognitoGetIdResponse.identityId
            UserDefaults.standard.setValue(identityId, forKey: IDENTITY_ID)
        }
        
        // get openid token
        let cognitOpenIdRequest = GetOpenIdTokenInput(identityId: identityId)
        let cognitoOpenIdResponse = try await cognitoClient.getOpenIdToken(input: cognitOpenIdRequest)
        
        // STS Assume role with web identity
        let stsClient = try STSClient(region: REGION)
        let assumeRoleRequest = AssumeRoleWithWebIdentityInput(roleArn: ROLE_ARN,
                                                               roleSessionName: "swift-evidently-demo",
                                                               webIdentityToken: cognitoOpenIdResponse.token)
        let assumeRoleResponse = try await stsClient.assumeRoleWithWebIdentity(input: assumeRoleRequest)
        
        // extract credentials from response
        guard let credentials = assumeRoleResponse.credentials,
              let accessKeyId = credentials.accessKeyId,
              let secretKey = credentials.secretAccessKey,
              let sessionToken = credentials.sessionToken else {
            print("no credentials returned")
            throw InvalidCredentialsError()
        }
        
        let tempCredentials = AWSCredentialsProviderStaticConfig(accessKey: accessKeyId,
                                                                 secret: secretKey,
                                                                 sessionToken: sessionToken)
        
        print("Credentials received, with access key: \(accessKeyId)")
        
        return try AWSClientRuntime.AWSCredentialsProvider.fromStatic(tempCredentials)
        
    } catch {
        throw error
    }
}

Invoking Evidently

Once you have collected the temporary credentials, you can call Evidently like this :

/*
     This function is equivalent to:
        aws --region us-west-2 evidently evaluate-feature --feature EditableGuestbook --project demo --entity-id 1
     */
    private func evaluateFeature() async throws {
        let PROJECT_NAME = "demo"
        let FEATURE_NAME = "EditableGuestbook"
        let identityId = UserDefaults.standard.string(forKey: "identity-id")
        
        do {
            let credentialsProvider = try await getTempCredentialsforEvidently()
            let evidentlyClientConfig = try EvidentlyClient.EvidentlyClientConfiguration(credentialsProvider: credentialsProvider,
                                                                                         region: REGION)
            print("evaluate Feature")
            let evidentlyClient = EvidentlyClient(config: evidentlyClientConfig)
            let request = EvaluateFeatureInput(entityId: identityId,
                                               feature: FEATURE_NAME,
                                               project: PROJECT_NAME)
            let response = try await evidentlyClient.evaluateFeature(input: request)
            print(response)
        } catch {
            print(error)
            throw error
        }
    }
}
answered a year ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions