Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added authentication with custom credential provider and removed authentication with API key #9

Merged
merged 20 commits into from
Jul 8, 2024

Conversation

shah279
Copy link

@shah279 shah279 commented Jul 1, 2024

Issue #, if available:: N/A

Description of changes:
feat: Added authentication with custom credential provider
feat: Removed authentication with API key

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@imraymondlee
Copy link
Collaborator

Can you provide me an example of how I could use the custom credential provider?

@shah279
Copy link
Author

shah279 commented Jul 2, 2024

Can you provide me an example of how I could use the custom credential provider?

You can pass the credentialsProvider created with AWS Kotlin SDK in the authenticateWithCredentialsProvider method like below:

private fun exampleCustomCredentialLogin() {
    var authHelper = AuthHelper(applicationContext)
    var locationCredentialsProvider : LocationCredentialsProvider = authHelper.authenticateWithCredentialsProvider("MY-AWS-REGION", credentialsProvider)
    var locationClient = locationCredentialsProvider?.getLocationClient()
}

@imraymondlee
Copy link
Collaborator

Can you provide me an example of how I could use the custom credential provider?

You can pass the credentialsProvider created with AWS Kotlin SDK in the authenticateWithCredentialsProvider method like below:

private fun exampleCustomCredentialLogin() {
    var authHelper = AuthHelper(applicationContext)
    var locationCredentialsProvider : LocationCredentialsProvider = authHelper.authenticateWithCredentialsProvider("MY-AWS-REGION", credentialsProvider)
    var locationClient = locationCredentialsProvider?.getLocationClient()
}

The goal of the custom credential provider is to provide support for Cognito's developer-authenticated identities.

As some of these (AWSAbstractCognitoDeveloperIdentityProvider, CognitoCachingCredentialsProvider) classes are no longer part of AWS SDK for Kotlin, what would a developer need to do to create credentialsProvider that follows the developer-authenticated identities pattern in order to authenticate into the SDK?

@shah279
Copy link
Author

shah279 commented Jul 4, 2024

Can you provide me an example of how I could use the custom credential provider?

You can pass the credentialsProvider created with AWS Kotlin SDK in the authenticateWithCredentialsProvider method like below:

private fun exampleCustomCredentialLogin() {
    var authHelper = AuthHelper(applicationContext)
    var locationCredentialsProvider : LocationCredentialsProvider = authHelper.authenticateWithCredentialsProvider("MY-AWS-REGION", credentialsProvider)
    var locationClient = locationCredentialsProvider?.getLocationClient()
}

The goal of the custom credential provider is to provide support for Cognito's developer-authenticated identities.

As some of these (AWSAbstractCognitoDeveloperIdentityProvider, CognitoCachingCredentialsProvider) classes are no longer part of AWS SDK for Kotlin, what would a developer need to do to create credentialsProvider that follows the developer-authenticated identities pattern in order to authenticate into the SDK?

Here is an example of creating a credential provider:

private suspend fun generateCredentialsProvider(region: String, identityPoolId: String): CredentialsProvider? {
        val cognitoIdentityClient = CognitoIdentityClient { this.region = region }
        try {
            val getIdResponse = cognitoIdentityClient.getId(GetIdRequest { this.identityPoolId = identityPoolId })
            val identityId =
                getIdResponse.identityId ?: throw Exception("Failed to get identity ID")
            if (identityId.isNotEmpty()) {
                val getCredentialsResponse =
                    cognitoIdentityClient.getCredentialsForIdentity(
                        GetCredentialsForIdentityRequest {
                            this.identityId = identityId
                        })

                val credentials = getCredentialsResponse.credentials ?: throw Exception("Failed to get credentials")
                return createCredentialsProvider(credentials)
            }
        } catch (e: Exception) {
            throw Exception("Credentials generation failed")
        }
        return null
    }

    private fun createCredentialsProvider(credentials: Credentials): CredentialsProvider {
        if (credentials.accessKeyId == null ||credentials.secretKey == null) throw Exception(
            "Failed to get credentials"
        )
        return StaticCredentialsProvider(
            aws.smithy.kotlin.runtime.auth.awscredentials.Credentials.invoke(
                accessKeyId = credentials.accessKeyId!!,
                secretAccessKey = credentials.secretKey!!,
                sessionToken = credentials.sessionToken,
                expiration = credentials.expiration
            )
        )
    }

Also, document comment is updated at the top of the method authenticateWithCredentialsProvider in AuthHelper.

README.md Outdated
var authHelper = AuthHelper(applicationContext)
var locationCredentialsProvider : LocationCredentialsProvider = authHelper.authenticateWithCognitoIdentityPool("My-Cognito-Identity-Pool-Id")
var locationCredentialsProvider : LocationCredentialsProvider = authHelper.authenticateWithCredentialsProvider("MY-AWS-REGION", "MY-CUSTOM-CREDENTIAL-PROVIDER")

Choose a reason for hiding this comment

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

The second argument in the authenticateWithCredentialsProvider is not as string, it is a CredentialsProvider

Copy link
Author

Choose a reason for hiding this comment

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

Resolved

*/
constructor(context: Context, apiKey: String) {
constructor(context: Context, region: AwsRegions) {

Choose a reason for hiding this comment

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

If this constructor is used just for the custom Credential provider method, might as well pass it here rather than using the initializeLocationClient method. Wdyt?

Copy link
Author

Choose a reason for hiding this comment

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

initializeLocationClient is a suspend function, so instead of initializing the coroutine in the constructor of LocationCredentialsProvider, we can directly call the function here.

Choose a reason for hiding this comment

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

I think this is related to my other open comment, as I don't think we need the extra translation layer that is done in setCustomCredentials between Credentials(aws.smithy.kotlin.runtime.auth.awscredentials) and Credentials( aws.sdk.kotlin.services.cognitoidentity.model.Credentials). If we don't invoke the the credentials.resolve() method and just assign the credential porvider directly to the LocationClient, then we could pass in the credential provider directly in the constructor as there is no async behavior that needs to occur at instantiation. Thus the constructor could look something like this:

    constructor(context: Context, region: AwsRegions, provider: CredentialsProvider) {
        this.context = context
        this.credentialsProvider = provider
        securePreferences = initPreference(context)
        securePreferences.put(METHOD, "custom")
        securePreferences.put(REGION, region.regionName)
    }

Which then allows the caller to call getLocationClient and get a properly configured client. Wdyt?

Choose a reason for hiding this comment

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

As discussed internally, can be addressed as fast follow.

private var context: Context
private var cognitoCredentialsProvider: CognitoCredentialsProvider? = null
private var apiKeyProvider: ApiKeyCredentialsProvider? = null
private var securePreferences: EncryptedSharedPreferences
private var securePreferences: EncryptedSharedPreferences? = null

Choose a reason for hiding this comment

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

All the constructors initialize the securePreferences, why are we making them nullable?

Copy link
Author

Choose a reason for hiding this comment

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

Resolved

Comment on lines 280 to 287
fun getCredentialsProvider(): aws.sdk.kotlin.services.cognitoidentity.model.Credentials? {
val method = securePreferences?.get(METHOD)
if (method == "custom" && customCredentials != null) {
return customCredentials
}
if (cognitoCredentialsProvider === null) throw Exception("Cognito credentials not initialized")
return cognitoCredentialsProvider?.getCachedCredentials()
}

Choose a reason for hiding this comment

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

This and the createCredentialsProvider should be rethought considering that the we will be receiving a fully configured CredentialsProvider. I think there is unnecessary conversions between CredentialsProvider and aws.sdk.kotlin.services.cognitoidentity.model.Credentials

Copy link
Author

Choose a reason for hiding this comment

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

We are generating credentials with CognitoIdentityClient with AWS Kotlin SDK and we are getting this Credentials(aws.sdk.kotlin.services.cognitoidentity.model)

and for creating LocationClient we need CredentialsProvider(aws.smithy.kotlin.runtime.auth.awscredentials)
and for creating CredentialsProvider we need Credentials(aws.smithy.kotlin.runtime.auth.awscredentials)

So, the conversions are needed in createCredentialsProvider

Choose a reason for hiding this comment

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

Since we createCredentialsProvider returns a CredentialsProvider(aws.smithy.kotlin.runtime.auth.awscredentials) so why not early return the this.credentialsProvider if it is not null (and potentially if method == "custom"). Otherwise we are passing a credentials provider to get cerdentials to create a new credentials provider.

Choose a reason for hiding this comment

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

As discussed internally, can be addressed as fast follow.

@shah279 shah279 requested a review from juaneslucero July 8, 2024 04:55
@imraymondlee imraymondlee merged commit 1da44b6 into aws-geospatial:main Jul 8, 2024
2 checks passed
imraymondlee pushed a commit to imraymondlee/amazon-location-mobile-auth-sdk-android that referenced this pull request Jul 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants