Search code examples
androidoauth-2.0chrome-custom-tabs

How to implement OAuth single Sign In/Sign Out with Chrome Custom Tabs


I am attempting to implement OAuth single sign in/ sign out in my current Android application.

I am using Chrome Custom Tabs

 implementation 'com.android.support:customtabs:28.0.0'

Sign in works fine, Chrome Custom tabs store the users cookies and all sign in flow work is as expected.

New Sign In:

  1. Clearing Chrome cookies
  2. Clearing App storage
  3. The Android app loads the Sign In URL with a Chrome Custom Tab
  4. The user has to enter user name and password
  5. Redirect URL with custom scheme "call backs" to my Android app and I receive a valid Access Token.

UPDATE: Since my android devices have received a Chrome update to version 72 the above has stopped working. See my related SO question

If chrome has a security policy where it doesn't allow redirects without associated user interaction, why do I receive a New Intent when I load the first URL for sign in that has prompt=none? At this point there has been no user interaction. This step is still working ok even after the update to Chrome 72 that has broken the sign in with prompt=login and in this second step there is user interaction as not only does the user have to enter their user name and password they then click on the sign in button.

When I add "consent" to both prompts, e.g.g "prompt=none consent" and then "prompt=login consent" the user can sign in, however they are presented with an "approval" screen before they are allowed to continue into my application. Why does adding "consent" make my sign in process work? and where does the "approval" screen come from?

Subsequent Sign In:

  1. The App loads the Sign In URL with a Chrome Custom Tab
  2. The Chrome Custom Tabs user cookies are employed, to "silently Sign In" the user.
  3. The user enters the Android application

I am having an issue with Signing out.

When the user clicks on the Sign Out button within my application, The app loads the sign out URL via a custom Chrome Tab but it gets "stuck". The screen the user sees is a blank white screen.

I believe this is due to the security constraints of Chrome Custom Tabs mentioned in this SO question CCT get stuck...

more precisely this statement:

Chrome enforces a policy that it will only send redirects to your app if the redirect was triggered by a user action, such as submitting a form that redirects or clicking on a link.

What I am seeing is that our users can always log in silently with no issues even though there is no User Interaction, Where as when we try to load the sign out URL with a CCT we always get "STUCK" on a blank CCT page.

What I do not understand is why the Silent Sign In works and the "programmatic" Sign Out doesn't work, while neither of them have any user interaction involved.

If I had to guess, I'd say the Sign In works as the CCT detect the User Cookies and accepts that these can only have been stored with associated User Interaction.

Where as the Sign Out doesn't have any user interaction at all.

How can I resolve my CCT Sign Out issue?

Will I need to load a URL to a web page that hosts a Sign Out button?


Solution

  • To avoid all these problems, I would suggest that you implement a staging page that will serve to handle all your OAuth2 redirects.

    AFAIK redirecting to a web page doesn't require user consent and is less subject to restrictions compared to redirecting to App.

    So your logic will change to the following:

    New Sign In:

    1. Clearing Chrome cookies
    2. Clearing App storage
    3. The Android app loads the Sign In URL with a Chrome Custom Tab
    4. The user has to enter user name and password
    5. Redirect URL to your webapp that will then redirect it to your app with whatever params it got, ie. Access Token

    Same thing for the Logout process. In your Web page you would have an automatic redirect (using JS for example) and a failover option, the user clicks a button to get back to your app. You page would look something like this:

    <!doctype html>
    <html lang="en">
    <head>
        <script type="text/javascript">
            document.addEventListener("DOMContentLoaded", function() {
                document.getElementById("redirectButton").click();
                setTimeout(function() {
                    window.location.href = "{{ path('homepage') }}";
                }, 10000);
            });
        </script>    
    </head>
    <body>
    <p>Redirecting to XXX app, it will only take few seconds...</p>
    <a href="{{ redirect_url }}" id="redirectButton">Click here to go back to XXX App</a>
    </body>
    </html>
    

    Note that this process can be fast and the user might not even see this page