Tuesday, 10 December 2013

Seamless Sign-In Across Devices

At an event last week Ade, Lee and I hosted a discussion about cross-device sign-on, where we covered not only what it was, but why its interesting and how it works. One conclusion we came to was that minimising the number of times your users have to see the sign-in button and make an active choice to sign-in can really help smooth the experience of using an application. However, there are a couple of pitfalls that can mean you don’t feel the benefits from the feature.

What do we mean by cross device sign-in?

Once a third party is involved, authentication is no longer just about exchanging some credentials for a cookie or identifier of some kind. Unfortunately, a lot of the time we do exactly that: treat the sign-in as simply being a replacement for sending a username and password across to the server which can return a cookie. Once the user has a cookie, they are app-identified, and hence can use the site as normal.

The problem is that this approach basically treats auth as a one time deal - once its done, the apps interaction with Google is ended (bar any API calls). What this ignores is that the user consented to grant access to the app, and that consent persists and extends beyond the scope of the app's session cookie.

In the 'exchange' model, if the user signs in on their web browser at work they execute the flow and get a cookie. If they go home and sign in on their personal laptop then the flow starts again: credentials, lookup, cookie. However, in the Google+ Sign-In case, this flow can be short circuited as the user consent is still valid. This means the user has effectively already signed-in on their home machine, and should just be able to go the URL and carry on with what they were doing!

How does this work?

We can’t share cookies between the two machines in the above scenario, but they may well have access to a common third party - the user’s Google account. If the user is in an environment where they have an active session with Google, we should be able to shortcut the sign-in process (if they have previously given consent). Right now, that shortcut works on Android and on the the web. On Android the active session with Google is stored in the AccountManager of the device, and on the web it requires that they are signed in to their Google account in the browser session - which could be for Gmail, YouTube, or any other Google service they are using.

The flow in these environments therefore looks a bit different (in the no-app-cookie state):

  1. Test for existing consent with Google
  2. If connected, establish an app sessions
  3. If not connected, present sign-in options as normal

This works even in a multi-sign-in environment where other sign-in options are available. If you want to encourage users to connect other providers, it is easy to give them the option to associate extra profiles with their application account.

How do you implement this

  1. Use the Google+ SDKs: PlusClient in Google Play services for Android, the Google+ Javascript SDK for web.
  2. Make app settings consistent across platforms
  3. Test for consent anywhere the user can enter the site or app
  4. Handle the callback

Each of these points can be the source of a problem.

Using the Google+ SDKs.

On both platforms, there are other options for sign-in. These include redirect based OAuth 2.0, using gapi.auth.authorize in Javascript and using GoogleAuthUtil or a web-based sign-in on Android. In general, the best approach is to use the Google+ SDK functionality directly.

On Android, the PlusClient should be connected in the activity's onStart, which triggers the sign-in test as soon as the activity is started. If the user is not signed in, the ConnectionResult is just stored for later error resolution.

On the web, defining page level configuration in meta tags means the sign-in test will be triggered as soon as plusone.js/platform.js is loaded. The callback will be triggered whether or not the user is signed in, where you can easily test for the current state.

Consistent App Settings

Primarily this is ensuring that you are asking for the same OAuth 2.0 scopes everywhere. This should include the plus.login scope used for Google+ Sign-In. Its important to make sure requested scopes match in all environments. If they don’t, the user will have to consent again for the variations. This can be quite subtle - in at least one case I know of an app was asking for the ability to manage contacts on the web, but on Android just the ability to view contacts. Its also possible to miss scopes between different buttons in a web environment if not using page level config - particularly in easily overlooked places, such as interactive posts.

The best way to do this is to centralise on both platforms into standard includes. Because the scopes are just a string, you could even potentially have a build system source the config from the same place.

Testing for consent

Another common problem is only putting the sign-in button or connecting the client on a login page or activity. This means that the user will be treated as signed out until they go to the login page, at which point they’re suddenly signed in. This is not a good experience, and means they have been using a system anonymously until then. If using a roadblock type sign-in pattern this is less of an issue, but its definitely better to be flexible about where you present the sign-in button and test for consent anywhere that a user can enter the app.

This is why on Android we recommend that you setup your PlusClient somewhere available throughout your application - such as in a base activity, a fragment, or a service. You don’t necessarily have to present UI everywhere, but it is important to do the PlusClient.connect.

On the web, including the page level config and the platform.js file will perform an immediate check for sign-in when the page loads. Its important to make sure the page level config is defined before platform.js is loaded, so it is picked up properly. Alternatively, the sign-in button can be rendered in a hidden element in order to force a check.

Handling the callback

There are a few apps and sites out there that trigger the consent test, but for various reasons don’t actually take any action on the result. Since this may cause the users to see the “welcome back” toast, it can be an unusual user experience.

Make sure that anywhere you test for consent you also provide the callback code that properly establishes an app session. Again, centralising this in a standard Javascript include or a Android base activity can make life much easier.

What about sign-out

One legitimate concern with this seamless sign-in is around sign-out. If you want to offer your users the ability to sign out, but seamlessly sign people in, then you could end up with a situation where you clear their cookie only to have them seamlessly signed in and right back into the site.

Because of the ongoing relationship, there are two levels of connection that can be modified separately. The first one is the device specific user state. That is what we think of as sign-out. For example, I might sign out of a web site, but still be signed in to the matching Android app, or the site on another machine. For this purpose, there are device specific methods: gapi.auth.signOut on the web and PlusClient.clearDefaultAccount on Android.

However, there is also the consent between the user and the application. This is the “disconnect” case that apps should provide, to allow users to sever the connection between the app and their Google account (though the relationship between the user and the app could of course continue independently). Disconnecting will revoke access, which will stop further sign-in across all platforms. On Android, there is a helper method: PlusClient.revokeAccessAndDisconnect. On the web, it’s a little more involved as it requires making a call to the token revocation endpoint with an access token, which could be executed from client or server.

What about user switching

One question that came up during the session was how to deal with user switching? Some applications are more likely to see multiple users on the same computer. The best way of dealing with this, particularly if the pool of users is stable (such as with a family computer) is to encourage users to use multiple browser profiles (for example, with Chrome), or device profiles on Android.

If the use case is likely to be a more temporarily shared environment (such as an internet cafe or similar), it's worth reminding the user about what system they signed in with, to prompt them to properly sign out when they have finished their session. This is a good practice in any case, and can be done elegantly, as seen here with musiXmatch.

What about iOS

One final question is, what about iOS? Well, at the moment there is no way for an app to access a user's Google session without switching to another application. Usually this is the Google+ app, or Chrome/Safari. However, once a user has done this (by pressing the sign-in button), a long-lived token will be stored in the keychain of the device for that app, so the user shouldn't have to sign-in again. While this is very easy to implement it does mean that the user has to actively sign-in, so can't be seamless signed in on first use.