aws-amplify / aws-sdk-android

AWS SDK for Android. For more information, see our web site:
https://docs.amplify.aws
Other
1.02k stars 548 forks source link

Android 14: Cognito Hosted UI auth redirect response fails to reopen app after successful login. #3530

Closed aleksnied closed 3 months ago

aleksnied commented 4 months ago

Describe the bug I'm using showSignIn to launch a HostedUI with Cognito and a third party provider. After successfully logging in with the HostedUI in the mobile's browser, AWS correctly sends back a 302 redirect, with a location header that is a link back to my app. The link correctly triggers the declared intent-filter for the HostedUIRedirectActivity declared in my app's manifest. The app launches very briefly (invisibly), successfully receives the codes and logs the user in.

However, here is the bug: the app proceeds to immediately background again, we go back to the HostedUI which errors out with some bouncing redirects, eventually landing on a page with a button that says it will take you back to the app but it doesn't. If the user then closes the browser, they can get back to the app, and the app is in a logged in state. This only happens on Android 14.

My set up works on Android < 14, iOS, and web. So it's not a Cognito, or third party SAML (OKTA in this case) configuration problem I expect. Sounds more like some mishandling of Android 14 by UI-manipulating code in the SDK or cancelling the 302 redirect on client side once received by SDK (see screenshots below)

To Reproduce A code sample or steps:

**Manifest:**

<queries>
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <data android:scheme="https" />
        </intent>
        <intent>
            <action android:name=
                "android.support.customtabs.action.CustomTabsService" />
        </intent>
    </queries>

<application>

          <activity
            android:name="com.amazonaws.mobile.client.activities.HostedUIRedirectActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="yourscheme" />
            </intent-filter>
           </activity>

**Code:**
           val hostedUIOptions = HostedUIOptions.builder()
                .scopes("openid", "email", "profile")
                .identityProvider("providerName")
                .build()

            val signInUIOptions = SignInUIOptions.builder()
                .hostedUIOptions(hostedUIOptions)
                .build()

            awsMobileClient.showSignIn(
                activity,
                signInUIOptions,
                object : Callback<UserStateDetails> {
                    override fun onResult(result: UserStateDetails) {
                        ...
                    }

                    override fun onError(e: Exception) {
                       ...
                    }
                })

**Typical config:**
              "Default": {
                    "OAuth": {
                        "WebDomain": "...",
                        "AppClientId": "...",
                        "SignInRedirectURI": "yourscheme://callback",
                        "SignOutRedirectURI": "yourscheme://signout",
                        "Scopes": [
                            "email",
                            "openid",
                            "profile"
                        ]
                    }
                },
                "Custom": {
                    "authenticationFlowType": "CUSTOM_AUTH"
                }
            }

Which AWS service(s) are affected? Cognito

Expected behavior The expected behavior is that once the SDK receives the redirect, it will keep the app open and not send the user back to the browser.

Screenshot captured redirect response saml2/idpresponse in the Android 14 browser actually shows that the server responded with 302 correctly, with correct location set to app link yourscheme://callback.... but is then aborted (on client side). When testing on web/iOS, this does not get aborted and is listed as 302 when captured. Screenshot 2024-02-08 at 18 06 53

Screenshot 2024-02-08 at 18 03 56

Log output, I think mainly generated by the SDK: this is what it prints out when it's successfully receiving the redirect, foregrounding and backgrounding the app. Note the complaint about CustomTabsManagerActivity null state, I've only seen that mentioned in an unrelated bug report from a while ago.

2024-02-08 15:47:44.598 11101-11101 AuthClient D Handling auth redirect response 2024-02-08 15:47:44.598 11101-11147 SessionLifecycleClient D Sending lifecycle 1 to service 2024-02-08 15:47:44.604 11101-11152 SessionLifecycleClien D Sending lifecycle 2 to service 2024-02-08 15:47:44.609 11101-11242 SessionLifecycleService D Activity foregrounding at 109094171. 2024-02-08 15:47:44.610 11101-11242 SessionLifecycleService D Activity backgrounding at 109094176 2024-02-08 15:47:44.625 11101-11101 AuthClient D CustomTabsManagerActivity was created with a null state.

Environment Information (please complete the following information):

Additional context I have the following dependencies: com.amazonaws:aws-android-sdk-mobile-client:2.75.0 com.amazonaws:aws-android-sdk-s3:2.75.0 com.amazonaws:aws-android-sdk-cognitoauth:2.75.0@aar isTransitive = true

tylerjroach commented 4 months ago

Thank you for the report. I'll try and reproduce this.

tylerjroach commented 4 months ago

@aleksnied Can you further demonstrate where you are calling AWSMobileClient.getInstance().showSignIn and any other Auth calls such as getSession().

I'm not able to replicate this. I had even gone into Developer options to enable "Don't keep activities" as that looked like a potential problem to enable that configuration.

Can you set a few breakpoints on HostedUIRedirectActivity onCreate and OnResume actions, as well as CustomTabsManager activity on lines: https://github.com/aws-amplify/aws-sdk-android/blob/adec7ed1ab2debb72449207ad0f6a3d995984211/aws-android-sdk-cognitoauth/src/main/java/com/amazonaws/mobileconnectors/cognitoauth/activities/CustomTabsManagerActivity.java#L69 https://github.com/aws-amplify/aws-sdk-android/blob/adec7ed1ab2debb72449207ad0f6a3d995984211/aws-android-sdk-cognitoauth/src/main/java/com/amazonaws/mobileconnectors/cognitoauth/activities/CustomTabsManagerActivity.java#L83

It may also be beneficial to set a break point on your sign in call to make sure that re-entry in the app isn't re-calling that method. Something is firing off a new request to launch the Chrome CustomTab in your application.

aleksnied commented 4 months ago

hey @tylerjroach! Thanks so much for looking into this.

I think those were some great directions to investigate so:

  1. I commented out all interactions in our code with aws SDK, I left only the call to initialize and the call to showSignIn. I even completely removed our calls to getTokens as you mentioned getSession. But just to be safe I removed all other calls we make as well. I also removed our addUserStateListener. So my code has been reduced to only what I showed in the original post. (I left our S3 code alone, probably not relevant here and not triggered in this flow)

  2. As you suggested, I put breakpoints on our call to showSignIn as well as lines 69 and 83 inside CustomTabsManagerActivity. What I saw:

    • showSignIn triggered
    • startActivity(customTabsIntent);
    • here I saw the hosted UI and proceeded to type in log in details and submit. The bug happened as usual
    • handleAuthorizationComplete(); : not triggered. No further triggers of the other two breakpoints either.
  3. I also checked my developer settings, good call, I have Don't keep activities switched off on the A53 I'm using to reproduce.

I actually forgot to mention one key part of reproducing the bug: every time I test this, I completely clear app data and browser data to start fresh. The bug does not occur if the browser has a cached session state. So for example if I experience the bug, then clear app data but leave browser alone, if I then run the app again it pings off the browser without need to re-enter credentials and here, it manages to re-open the app without fault.

tylerjroach commented 4 months ago

@aleksnied Thanks for this info! A few more things to make sure I am testing same as you.

I actually forgot to mention one key part of reproducing the bug: every time I test this, I completely clear app data and browser data to start fresh. The bug does not occur if the browser has a cached session state. So for example if I experience the bug, then clear app data but leave browser alone, if I then run the app again it pings off the browser without need to re-enter credentials and here, it manages to re-open the app without fault.

Interesting! Good to know

aleksnied commented 4 months ago

@tylerjroach

I went away and checked these out, again good call:

I tested and reproduced the bug exactly the same way on:

Samsung Galaxy A53 5G - Android 14 Samsung Galaxy A14 - Android 14 Pixel 6A API 34 Emulator - Android 14

I then switched to Hosted UI Cognito Email/Password log-in, without the third party provider. The bug looks slightly different in the browser, but effectively it's the same. The app doesn't open, and the HostedUI gets relaunched/stuck. I reproduced it on the same phones above.

To narrow down to Android 14 I tested both Third Party SSO and bare email/pwd through cognito on:

Pixel 4a - Android 13 (physical device) a few Motorolas, Android 10-11

These work absolutely fine.

tylerjroach commented 4 months ago

@aleksnied Im still having trouble replicating following the instructions provided.

Would it be possible to create a very basic shell application that just implements web ui sign in. Remove your amplifyconfiguration.json and awsconfiguration.json as I'll use my own. This may also be a good exercise to check and see if it could be specific to the current application you are working on our not.

As mentioned, I've created my own app using only initialize and showSignIn and can't reach the scenario you are describing.

aleksnied commented 4 months ago

Hey @tylerjroach

Ok, I got to the bottom of it. I created a minimal app to reproduce and couldn't. I had a hunch this could be something with the AndroidManifest, so I started introducing parameters from my app's manifest until I reproduced the issue.

Turns out the bug happens when we set android:launchMode="singleInstance" on the app's MainActivity.

Using another launchMode, like singleTask, fixes the issue. However obviously this holds implications for our app.

I think the issue is possibly caused by this undocumented change in Android 14: https://issuetracker.google.com/issues/307662564

Separate Tasks are being launched in separate windows, looks like it's a bug.

Here's another discussion of this in different words, it seems to imply that latest QPR Beta updates to Android 14 have addressed this. I've tried to get a hold of the system image for emulator unsuccessfully to test. In any case, these are only quickly available to Beta users and Pixel devices, and supposedly they can make it to other manufacturer's phones very slowly or not at all.

https://issuetracker.google.com/issues/288400064

I'm happy to share my minimal code with you if needed, but if you simply add android:launchMode="singleInstance" to your MainActivity in the Manifest you should see the problem (Assuming you are not on QPR Android 14 but vanilla stable 14)

Let me know if you can reproduce, and what you think the implications are. Sounds to me that until Android is fixed, I might have to rely on singleTask?

I've found this discussed in various threads for libraries that handle redirecting, and some seem to have found workarounds, so maybe there is a way.

tylerjroach commented 4 months ago

@aleksnied Thank you for the detailed research. I was able to replicate the scenario you have described. It does appear that singleInstance is problematic in Android 14 with these changes, but it also appears this is a platform bug and not an SDK bug. I have a suggested workaround you may want to test. I haven't tested it enough to verify that it is production ready, but it was working in my testing.

It is ok to leave MainActivity as "singleInstance" as long as it is not the activity that launches the custom tab. You can create a headless activity where its only purpose is to launch the custom tab.

class AuthLauncherActivity: Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val hostedUIOptions = HostedUIOptions.builder()
            .scopes("openid", "email", "profile")
            .identityProvider("providerName")
            .build()
        val signInUIOptions = SignInUIOptions.builder()
            .hostedUIOptions(hostedUIOptions)
            .build()
        AWSMobileClient.getInstance().showSignIn(
            this,
            signInUIOptions,
            object : Callback<UserStateDetails> {
                override fun onResult(details: UserStateDetails) {
                    Log.d(MainActivity.TAG, "onResult: " + details.userState)
                }

                override fun onError(e: Exception?) {
                    Log.e(MainActivity.TAG, "onError: ", e)
                }
            })
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        finish()
    }
}

Add to manifest:

<activity android:name=".AuthLauncherActivity" android:exported="false">

Your MainActivity can launch the headless fragment when sign in is clicked, instead of completing the action itself.

startActivity(
    Intent(
        this@MainActivity, 
        AuthLauncherActivity::class.java
    )
)
aleksnied commented 4 months ago

Thanks @tylerjroach! I will try it as soon as I have a little time and report back here.

aleksnied commented 3 months ago

Hi, just to give you an update, the above seemed to work, but I decided to go with reworking things in my app to go with "singleTop" for my main activity after all :)

tylerjroach commented 3 months ago

Great! Glad to hear it!