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

AwsMobileClient is not initialising automatically in 2.7.1 #2400

Closed
1 task done
ZubairAkber opened this issue Apr 19, 2023 · 15 comments
Closed
1 task done

AwsMobileClient is not initialising automatically in 2.7.1 #2400

ZubairAkber opened this issue Apr 19, 2023 · 15 comments
Assignees
Labels
auth Related to the Auth category/plugins closing soon This issue will be closed in 7 days unless further comments are made. question General question

Comments

@ZubairAkber
Copy link

ZubairAkber commented Apr 19, 2023

Before opening, please confirm:

Language and Async Model

Kotlin

Amplify Categories

Authentication, REST API, DataStore

Gradle script dependencies

// Amplify dependencies
implementation 'com.amplifyframework:core:2.7.1'
implementation 'com.amplifyframework:aws-api:2.7.1'
implementation 'com.amplifyframework:aws-datastore:2.7.1'
implementation 'com.amplifyframework:aws-auth-cognito:2.7.1'
implementation 'com.amplifyframework:aws-storage-s3:2.7.1'

// AWS MQTT dependencies
implementation 'com.amazonaws:aws-android-sdk-iot:2.65.0'
implementation 'com.amazonaws:aws-android-sdk-mobile-client:2.65.0'

// Support for Java 8 features
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'

Environment information

    classpath 'com.android.tools.build:gradle:7.3.1'
    classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20'

    // Google Services plugin
    classpath 'com.google.gms:google-services:4.3.10'
    // Add the Crashlytics Gradle plugin
    classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'

Please include any relevant guides or documentation you're referencing

No response

Describe the bug

I was using 1.30.0 version of amplify and every thing works fine, when i move to 2.7.1 and updated the code amplify works fine i just get the data and ready state is triggered but when it tries to connect to MQTT i got the following error

MQTT broker: a3jmi0po6ccx5n-ats.iot.ap-southeast-1.amazonaws.com:443
MQTT I Connection Lost com.amazonaws.AmazonClientException: Cognito Identity not configured
System.err W com.amazonaws.AmazonClientException: Cognito Identity not configured
System.err W at com.amazonaws.mobile.client.AWSMobileClient.getCredentials(AWSMobileClient.java:391)
System.err W at com.amazonaws.mobileconnectors.iot.AWSIotMqttManager$1.run(AWSIotMqttManager.java:993)
System.err W at java.lang.Thread.run(Thread.java:1012)

What i have observed is AwsMobileClient is not initialised at the start of app when i use 2.7.1 and in case of 1.30.0 it is initialised properly at the start with the logs that its initialise is called.

Reproduction steps (if applicable)

Simply update to 2.7.1 version of amplify from 1.30.0

Code Snippet

// Put your code below this line.

Log output

// Put your logs below this line


amplifyconfiguration.json

No response

GraphQL Schema

// Put your schema below this line

Additional information and screenshots

No response

@ZubairAkber ZubairAkber changed the title AwsMobileClient is not initializing automatically in 2.7.1 AwsMobileClient is not initialising automatically in 2.7.1 Apr 19, 2023
@tylerjroach
Copy link
Member

@ZubairAkber AWSMobileClient and Amplify v2 are incompatible with each other. Amplify for Android v2 is considered an upgrade of Amplify for Android v1 and the AWS Android SDK. v2 migrates data from v1/sdk and they use a different local storage mechanism to securely store credentials.

@tylerjroach tylerjroach self-assigned this Apr 19, 2023
@tylerjroach tylerjroach added the auth Related to the Auth category/plugins label Apr 19, 2023
@ZubairAkber
Copy link
Author

ZubairAkber commented Apr 20, 2023

@tylerjroach i am using aws-android-sdk-iot for the MQTT broker and in case of amplify v2 it doesn't gets connected because it doesn't get the required information from AwsMobileClient object, what do you suggest me to do as i want to use v2 and iot from aws-android-sdk both together. Below is the code that i use to connect to MQTT

    val clientId = BuildConfig.MQTT_USER_ID_PREFIX + UUID.randomUUID()
    iotMqttManager = AWSIotMqttManager(clientId, BuildConfig.MQTT_END_POINT)
    iotMqttManager.setReconnectRetryLimits(minRetryTimeout, maxRetryTimeout)
    iotMqttManager.maxAutoReconnectAttempts = maxConnectionRetries

    iotMqttManager.connect(AWSMobileClient.getInstance(),
        AWSIotMqttClientStatusCallback { status, throwable ->
    })

What additional steps do i take to make it work as it was working for v1

@tylerjroach
Copy link
Member

@ZubairAkber Looking at the iOT implementation, it just requires an AWSCredentialsProvider. Instead of using AWSMobileClient (mobile client dependency needs to be removed completely), you will want to create an implementation for the AWSCredentialsProvider interface.

You can see examples of how to do this from a similar interface we have on Amplify v2 (https://github.com/aws-amplify/amplify-android/blob/d0dd5e6b5b8dd388a24850f016adc3ab73993075/aws-core/src/main/java/com/amplifyframework/auth/CognitoCredentialsProvider.kt). It doesn't translate directly the the AWSCredentialsProvider you will need to implement but should give a rough guide on how to do so.

In summary, you should be able to use aws-android-sdk-iot with Amplify v2, as long as you remove the mobile client sdk.

@eeatonaws eeatonaws added the question General question label Apr 20, 2023
@ZubairAkber
Copy link
Author

ZubairAkber commented Apr 26, 2023

Thanks @tylerjroach, i will try and let you know.

Can you please guide me a bit more, do i have to set it in the AWSIotMqttManager class using setCredentialsProvider method or there is any other way of doing this

    val clientId = BuildConfig.MQTT_USER_ID_PREFIX + UUID.randomUUID()
    iotMqttManager = AWSIotMqttManager(clientId, BuildConfig.MQTT_END_POINT)
    iotMqttManager.setReconnectRetryLimits(minRetryTimeout, maxRetryTimeout)
    iotMqttManager.maxAutoReconnectAttempts = maxConnectionRetries
    iotMqttManager.setCredentialsProvider( ........ )

This method accepts com.amazonaws.auth.AWSCredentialsProvider but the package you have mentioned give me com.amplifyframework.auth.CognitoCredentialsProvider, i am a bit confuse is there any other things as well that needs to be updated ?

@tylerjroach
Copy link
Member

tylerjroach commented May 1, 2023

@ZubairAkber

connect looks like this:

public void connect(
  AWSCredentialsProvider credentialsProvider,
  final AWSIotMqttClientStatusCallback statusCallback
)

AWSCredentialsProvider is just an interface:

public interface AWSCredentialsProvider {
    public AWSCredentials getCredentials();
    public void refresh();
}

We can implement our own version of this interface by using Amplify v2 to fetch credentials.

class MyCredentialsProvider: AWSCredentialsProvider {

    override fun getCredentials(): AWSCredentials {
        // Need to make fetchAuthSessionBlocking
        val latch = CountDownLatch(1)
        var sdkCredentials: AWSCredentials? = null

        Amplify.Auth.fetchAuthSession(
            { authSession ->
                val awsTemporaryCredentials = (authSession as? AWSCognitoAuthSession)
                    ?.awsCredentialsResult?.value as? AWSTemporaryCredentials
                
                sdkCredentials = awsTemporaryCredentials?.let {
                    BasicSessionCredentials(it.accessKeyId, it.secretAccessKey, it.sessionToken)
                }
                
                latch.countDown()
            },
            {
                // can better handle this exception and pass it down to the throwing call below
                latch.countDown()
            }
        )

        // wait for fetchAuthSession to return
        latch.await()

        // return captured credentials or throw
        return sdkCredentials ?: throw IllegalStateException("Failed to get credentials")
    }

    override fun refresh() {
        // Call refresh but we don't need to store or capture result here
        Amplify.Auth.fetchAuthSession(
            AuthFetchSessionOptions.builder().forceRefresh(true).build(),
            { /* do nothing on success */ },
            { /* do nothing on failure */ },
        )
    }
}

Your connect call would then take MyCredentialsProvider() instead of AWSMobileClient.getInstance().

note: I've updated the snippet in this comment to the corrected one suggested later in the thread to prevent confusion.

@ZubairAkber
Copy link
Author

Thanks @tylerjroach i will try and let you know

@ZubairAkber
Copy link
Author

@tylerjroach i have tried using the above solution but i am getting this error now when i try to connect

AWSIotMqttManager W onFailure: connection failed.
MqttException (0) - java.io.IOException: WebSocket Response header: Incorrect upgrade.
at org.eclipse.paho.client.mqttv3.internal.ExceptionHelper.createMqttException(ExceptionHelper.java:38)
at org.eclipse.paho.client.mqttv3.internal.ClientComms$ConnectBG.run(ClientComms.java:738)
at java.lang.Thread.run(Thread.java:923)
Caused by: java.io.IOException: WebSocket Response header: Incorrect upgrade.
at org.eclipse.paho.client.mqttv3.internal.websocket.WebSocketHandshake.receiveHandshakeResponse(WebSocketHandshake.java:162)
at org.eclipse.paho.client.mqttv3.internal.websocket.WebSocketHandshake.execute(WebSocketHandshake.java:80)
at org.eclipse.paho.client.mqttv3.internal.websocket.WebSocketSecureNetworkModule.start(WebSocketSecureNetworkModule.java:65)
at org.eclipse.paho.client.mqttv3.internal.ClientComms$ConnectBG.run(ClientComms.java:724)
at java.lang.Thread.run(Thread.java:923) 

@tylerjroach
Copy link
Member

@ZubairAkber Please check that the time on your device is accurate. Please see this thread for more details: aws-amplify/aws-sdk-android#2674.

@ZubairAkber
Copy link
Author

@tylerjroach time is accurate as automatic date and time is enabled and it works fine with version 1 only version 2 is causing this issue

@tylerjroach
Copy link
Member

I have observed the same. I'll investigate and provide a follow up.

@tylerjroach
Copy link
Member

@ZubairAkber Please try the below credentials provider. I have verified this to work on an IoT project sample I have set up. IoT requires a sessionToken that was not present in BasicAWSCredentials. Instead, we can pass BasicSessionCredentials.

class MyCredentialsProvider: AWSCredentialsProvider {

    override fun getCredentials(): AWSCredentials {
        // Need to make fetchAuthSessionBlocking
        val latch = CountDownLatch(1)
        var sdkCredentials: AWSCredentials? = null

        Amplify.Auth.fetchAuthSession(
            { authSession ->
                val awsTemporaryCredentials = (authSession as? AWSCognitoAuthSession)
                    ?.awsCredentialsResult?.value as? AWSTemporaryCredentials
                
                sdkCredentials = awsTemporaryCredentials?.let {
                    BasicSessionCredentials(it.accessKeyId, it.secretAccessKey, it.sessionToken)
                }
                
                latch.countDown()
            },
            {
                // can better handle this exception and pass it down to the throwing call below
                latch.countDown()
            }
        )

        // wait for fetchAuthSession to return
        latch.await()

        // return captured credentials or throw
        return sdkCredentials ?: throw IllegalStateException("Failed to get credentials")
    }

    override fun refresh() {
        // Call refresh but we don't need to store or capture result here
        Amplify.Auth.fetchAuthSession(
            AuthFetchSessionOptions.builder().forceRefresh(true).build(),
            { /* do nothing on success */ },
            { /* do nothing on failure */ },
        )
    }
}

@eeatonaws eeatonaws added the pending-community-response Issue is pending response from the issue requestor label May 11, 2023
@ininmm
Copy link

ininmm commented May 12, 2023

Hi @tylerjroach I've face the same issue. When I create a custom credential provider as you mention, some I would get a exception if I wait for a while and call AWSIotClient(newCredentialsProvider).attachPolicy(attachReq) (there are some pesudo code).

Here is my code:

class CognitoAWSCredentialsProvider() : AWSCredentialsProvider {
    var profile = Profile()

    override fun getCredentials(): AWSCredentials {
        val latch = CountDownLatch(1)
        var sdkCredentials: AWSCredentials? = null

        try {
            Amplify.Auth.fetchAuthSession(
                { authSession ->
                    sdkCredentials = ((authSession as AWSCognitoAuthSession).awsCredentialsResult.value as? AWSTemporaryCredentials)?.let {
                        Timber.i("fetchSession sdkCredentials: awsSessionToken: ${it.sessionToken}, awsAccessKeyId: ${it.accessKeyId}, awsSecretKey: ${it.secretAccessKey}")
                        BasicAWSCredentials(
                            it.accessKeyId, it.secretAccessKey
                        )
                    }
                    Timber.i("sdkCredentials: ${sdkCredentials?.awsAccessKeyId}, ${sdkCredentials?.awsSecretKey}")
                    latch.countDown()
                },
                {
                    // can better handle this exception and pass it down to the throwing call below
                    latch.countDown()
                }
            )
        } catch (e: Exception) {
            Timber.e(e)
            val builder = AuthFetchSessionOptions.builder()
            builder.forceRefresh(true)
            Amplify.Auth.fetchAuthSession(
                builder.build(),
                { authSession ->
                    sdkCredentials = ((authSession as AWSCognitoAuthSession).awsCredentialsResult.value as? AWSTemporaryCredentials)?.let {
                        Timber.i("fetchSession sdkCredentials: awsSessionToken: ${it.sessionToken}, awsAccessKeyId: ${it.accessKeyId}, awsSecretKey: ${it.secretAccessKey}")
                        BasicAWSCredentials(
                            it.accessKeyId, it.secretAccessKey
                        )
                    }
                    Timber.i("sdkCredentials: ${sdkCredentials?.awsAccessKeyId}, ${sdkCredentials?.awsSecretKey}")
                    latch.countDown()
                },
                {
                    // can better handle this exception and pass it down to the throwing call below
                    latch.countDown()
                }
            )
        }

        // wait for fetchAuthSession to return
        latch.await()

        // return captured credentials or throw
        return sdkCredentials ?: throw IllegalStateException("Failed to get credentials")
    }

    override fun refresh() {
        // refresh
    }
}

    fun initIot(accessKey: String, secretKey: String, sessionToken: String) {
        credentialsProvider = CognitoAWSCredentialsProvider()
        if (::mqttManager.isInitialized) return
        Timber.i("[Remote] init Mqtt.")
        mqttManager = AWSIotMqttManager(parameter.identityId, EndPoint)
    }
    
    suspend fun attachPolicy() {
        val attachPolicyReq = AttachPolicyRequest()
        val serviceType = ServiceType.getServiceType(currentServiceType)
        attachPolicyReq.policyName = serviceType.policy
        Timber.i("attachPolicy id: ${parameter.identityId}")
        attachPolicyReq.target = parameter.identityId
        Timber.i("credentialsProvider.credentials: ${credentialsProvider.credentials.awsAccessKeyId}, ${credentialsProvider.credentials.awsSecretKey}")
        val iotAndroidClient = AWSIotClient(credentialsProvider.credentials)
        iotAndroidClient.setRegion(Region.getRegion(IotRegion)) // name of your IoT Region such as "us-east-1"
        iotAndroidClient.attachPolicy(attachPolicyReq)
    }

And the exception which crash at attachPolicy:
com.amazonaws.AmazonServiceException: The security token included in the request is invalid. (Service: AWSIot; Status Code: 403; Error Code: UnrecognizedClientException; Request ID: 599602b3-0361-40b2-8cbd-31b0d3e3c413).

Is that mean I didn't change the credentials inside the AWSIotClient? Cause when I got the exception, I'm sure I had already fetch a new session by amplify and create a new credential provider with new accesskey / secretkey to set into the AWSIotClient.

@ZubairAkber
Copy link
Author

ZubairAkber commented May 12, 2023

@tylerjroach it gets connected using the new implementation of MyCredentialsProvider but is it the final solution to that issue

@tylerjroach
Copy link
Member

@ZubairAkber I believe this provides everything you need. If you experience any other issues, please let us know. I'll mark as closing soon to give some time.

@ininmm I have replied in the new issue that you created and will follow up with any additional questions there.

@tylerjroach tylerjroach added closing soon This issue will be closed in 7 days unless further comments are made. and removed pending-community-response Issue is pending response from the issue requestor labels May 17, 2023
@github-actions
Copy link
Contributor

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see.
If you need more assistance, please either tag a team member or open a new issue that references this one.
If you wish to keep having a conversation with other community members under this issue feel free to do so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
auth Related to the Auth category/plugins closing soon This issue will be closed in 7 days unless further comments are made. question General question
Projects
None yet
Development

No branches or pull requests

4 participants