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

Android 12: Authentication fails when screen orientation changes (AuthenticationException) #530

Closed
alexRoussiere opened this issue Nov 16, 2021 · 5 comments
Labels
bug This points to a verified bug in the code closed:stale Issue or PR has not seen activity recently

Comments

@alexRoussiere
Copy link

alexRoussiere commented Nov 16, 2021

Describe the problem

The authentication via browser seems to have an issue on Android 12 only if the process ends with a screen orientation different than the client's app's Activity that is hosting it.
For example, when my LoginActivity is in portrait when I call WebAuthProvider.login(), and I finish the authentication process in the browser in landscape, then I am getting an AuthenticationException back with isCanceled == true in my LoginActivity

I've also tested this scenario with Android 11, Android 10, and Android 8.0 and it is not happening. So my guess is that it has to do with something Android 12 specific.

What was the expected behavior?

Expected behavior would be to get a onSuccess callback from the WebAuthProvider no matter the screen's orientation

Reproduction

Steps to reproduce

In my LoginActivity, I'm doing a simple login call that you can trigger just from clicking on a button:

WebAuthProvider.login(getAuth0Account())
            .withScope("openid profile")
            .withScheme(getAuth0Scheme())
            .withParameters("mapOf(Pair("prompt", "login"))")
            .withCustomTabsOptions(getCustomOptions()) //This just shows the title, and set a toolbarColor
            .withAudience(getAuth0Audience())
            .start(activity, callback)

For the callback, you can just add logs in them to see what is going on:

val callback = object : Callback<Credentials, AuthenticationException> {
            override fun onFailure(error: AuthenticationException) {
                Log.e("HostActivity", error.getDescription())
            }

            override fun onSuccess(result: Credentials) {
                Log.d("HostActivity", "Authentication SUCCESS")
            }
        }

To make it even easier to test, in the app manifest, add android:screenOrientation="portrait" within your host activity.

Now:

  • Run the host activity, which for me is LoginActivity, on Android 12. Should be in portrait only, if you added the line in the manifest
  • Trigger the login call
  • You should be pushed to the phone's browser (mine is Chrome)
  • Rotate the phone so that the browser is in landscape.
  • Login
  • Then the browser closes and you're back on the host activity screen. The screen has rotated back to portrait and you should have gotten an error log: HostActivity: The user closed the browser app and the authentication was canceled
    • I just wanna add that I tried without the screenOrientation="portrait" in the manifest and I am getting the same result if I start with the host activity in landscape, and login in the browser in portrait.

My findings while debugging

Below are what I have been seeing while trying to debug:

  • The difference between staying on the same screen's orientation and changing, is that the onNewIntent in AuthenticationActivity does not get called on Android 12 if the orientation changes. Which I think is the issue because the intent with the authentication data is lost (or not set), which ends up with an intent in the onResume() of the AuthenticationActivity having its data null, which triggers the RESULT_CANCELED.

    • On devices with a version below Android 12, the onNewIntent does get called.
  • I am also having another error on Android 12 when coming back to the host app (which you might see as well):

E/ActivityThread: Performing pause of activity that is not resumed: {myPackage.LoginActivity}
    java.lang.RuntimeException: Performing pause of activity that is not resumed: {myPackage.LoginActivity}
        at android.app.ActivityThread.performPauseActivity(ActivityThread.java:4983)
        at android.app.ActivityThread.handlePauseActivity(ActivityThread.java:4948)
        at android.app.servertransaction.PauseActivityItem.execute(PauseActivityItem.java:47)
        at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)
  • I wonder if that has something to do with it. When trying to research that, I found that answer (from a long time ago so Idk how much we can still trust it). But it mentions onResume being called while the screen animation is happening, which maybe is the case when the phone is rotating the host activity's screen.

Just for the record, the full error stackTrace I get from the auth0 library when the error happens is the following:

com.auth0.android.authentication.AuthenticationException: An error occurred when trying to authenticate with the server.
        at com.auth0.android.provider.OAuthManager.resume(OAuthManager.kt:79)
        at com.auth0.android.provider.WebAuthProvider.resume(WebAuthProvider.kt:70)
        at com.auth0.android.provider.AuthenticationActivity.deliverAuthenticationResult$auth0_release(AuthenticationActivity.kt:84)
        at com.auth0.android.provider.AuthenticationActivity.onResume(AuthenticationActivity.kt:53)

but like I said, it's triggered because the intent.getData() is null.

In my case, an easy fix would have been to have the ability to lock the Authentication process to a particular orientation, which I haven't found so far, but I can see that this would not fix the issue for other people experiencing the same problem with both orientation available.

Environment

  • Version of this library used: 2.5.1
  • Any other relevant information you think would be useful: I tried on both Chrome and Firefox and saw the same behavior. I also tested that on both real devices and Android Studio emulators. Real devices were Pixel 6, Pixel 4a, Samsung J3.

I hope you can reproduce it as easily as I can, and I apologize in advance if my english is not 100% right as it is not my first language 😄

@poovamraj
Copy link
Contributor

That was a very detailed and nice bug report I would say. I was curious and tinkering with it and figured that

onResume will always be called after onNewIntent but there is no guarantee on when it will be called.

You can count on onResume() being called after this method, though not necessarily immediately after the completion this callback.

On API Level 30 you can see that onResume is called after onNewIntent when new instance of activity is created due to configuration change

Call Stack in API Level 30

onCreate Portrait: d303398
onResume Portrait: d303398
onDestroy Portrait: d303398
onCreate Landscape: d51b837
onNewIntent Landscape: d51b837
onResume Landscape: d51b837
onDestroy Landscape: d51b837

On API Level 31 onResume is called before onNewIntent, but since there is a finish call inside the onResume call we never wait to receive the onResume after the onNewIntent.

Call Stack in API Level 31

onCreate Portrait: 8d531d7
onResume Portrait: 8d531d7
onDestroy Portrait: 8d531d7
onCreate Landscape: ce4b6a8
onResume Landscape: ce4b6a8
onNewIntent Landscape: ce4b6a8
onDestroy Landscape: ce4b6a8

When I add a small delay to the finish inside onResume, onNewIntent seems to finish its promise of calling onResume

Call Stack in API Level 31 after introducing a delay

onCreate Portrait: b3feac4
onResume Portrait: b3feac4
onDestroy Portrait: b3feac4
onCreate Landscape: ce4b6a8
onResume Landscape: ce4b6a8
onNewIntent Landscape: ce4b6a8
onResume Landscape: ce4b6a8
onDestroy Landscape: ce4b6a8

Introducing a delay to wait for lifecycle callback feels like a hack though.

@alexRoussiere
Copy link
Author

Those were my observations a well 💯 I agree that the delay seems like a hack and I think I've seen some suggestions on StackOverflow about using a Handler with a post() method to handle this, which does sound hacky.
I'm not sure if the onSavedInstance is called in this scenario, but if it does we could maybe utilize it to store some data / flag to trigger the right result.

@poovamraj
Copy link
Contributor

I was able to fix this issue by locking the orientation of RedirectActivity and AuthenticationActivity. This won't affect the existing flow/features since the "Web View" we show inside the app can still handle orientation change. This is the sanest solution in my opinion since this will avoid us handling the complicated life cycle of Screen Orientation in Single Task activity without losing any ability.

The animation is one thing to check for and it isn't affected much. So I would actually like to proceed with this.

@jimmyjames jimmyjames added the bug This points to a verified bug in the code label Dec 7, 2021
@jimmyjames
Copy link
Contributor

Thanks for providing all the details here! And hi @poovamraj 👋 ! It looks like in Lock.Android we fix the activity to portrait mode, so perhaps we can consider that here as well as quick fix. Is that what you did to fix the issue?

Either way, in talking with the team, it would probably be good to spend some time investigating why this changed in API 31 and if the issue is related to the activity instance (context) passed to the WebAuthProvider, or to something different.

@stale
Copy link

stale bot commented Apr 17, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you have not received a response for our team (apologies for the delay) and this is still a blocker, please reply with additional information or just a ping. Thank you for your contribution! 🙇‍♂️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug This points to a verified bug in the code closed:stale Issue or PR has not seen activity recently
Projects
None yet
Development

No branches or pull requests

3 participants