68

I'm developing a website that is primarily accessed via an app, and I want to use OAuth2 for user registration and authentication. Since it is an Android app I will start using Google's OAuth2 stuff, since it provides a decent UI on Android.

Google states that "You can choose to use Google's authentication system as a way to outsource user authentication for your application. This can remove the need to create, maintain, and secure a username and password store." which is what I want to do. However when I go through all their examples and whatnot, I can only find stuff about having a website or an app authenticate a user against Google's services.

And indeed, when I go to register my app ("client") with Google's OAuth2 there are options for website clients and "installed" clients (i.e. a mobile app) but not both. I can create two separate clients but I read the OAuth2 draft and I think there will be a problem, which I will now explain.

Here's how I did envisage it working:

OAuth2 flow diagram

  1. User asks MyApp to access his private data.
  2. App uses Android's AccountManager class to request an access token for Google's APIs.
  3. Android says to user "The app 'MyApp' wants access to your Basic Information on Google. Is this ok?"
  4. User says yes.
  5. AccountManager connects to Google's OAuth2 server using the credentials stored on the phone, and asks for an access token.
  6. Access token (which follows the green lines) is returned.
  7. AccountManager returns the access token to MyApp.
  8. MyApp sends a request to MySite for the user's private data, including the access token.
  9. MySite needs to verify the user, using the access token. It validates the token as described here, with Google - "Google, is this token valid?".
  10. Now, what I want to happen is that Google says "Yes, whoever gave it to you is indeed that user.", but what I think will actually happen (based on the OAuth2 draft and Google's documentation) is that it will say "No way! That token is only valid for MyApp, and you're MySite. GTFO!".

So how should I do this? And PLEASE don't say "Use OpenID" or "Don't use OAuth2" or other similarly unhelpful answers. Oh and I would really like to keep using the nice AccountManager UI rather than crappy popup WebViews

Edit

Provisional answer (I will report back if it works!) from Nikolay is that it should actually work, and Google's servers won't care where the access token came from. Seems a bit insecure to me, but I will see if it works!

Update

I implemented this pattern with Facebook instead of Google and it totally works. The OAuth2 server doesn't care where the access token comes from. At least Facebook's doesn't, so I assume Google's doesn't either.

In light of that it is a very very bad idea to store access tokens! But we also don't want to have to hit Facebook/Google's servers to check authentication for every request since it will slow everything down. Probably the best thing is to add an additional authentication cookie for your site that you hand out when their access token is validated, but a simpler way is just to treat the access token like a password and store a hash of it. You don't need to salt it either since access tokens are really really long. So the steps above become something like:

9. MySite needs to verify the user, using the access token. First it checks its cache of hashed valid access tokens. If the hash of the token is found there it knows the user is authenticated. Otherwise it checks with Google as described here, with Google - "Google, is this token valid?".

10. If Google says the access token is invalid, we tell the user to GTFO. Otherwise Google says "Yes that is a valid user" and we then check our registered user database. If that Google username (or Facebook id if using Facebook) is not found we can create a new user. Then we cache the hashed value of the access token.

j0k
  • 22,600
  • 28
  • 79
  • 90
Timmmm
  • 88,195
  • 71
  • 364
  • 509
  • Curious - did above approach work out for you in the end? – Mathias Conradt Sep 30 '12 at 13:42
  • I haven't actually implemented it yet, sorry! – Timmmm Oct 01 '12 at 12:11
  • 1
    See updated question: It worked at least for Facebook. – Timmmm Oct 06 '12 at 15:04
  • 3
    I've implemented it this way as well, it's working. Google API, Google Play Services on Android, and re-using the token on server-side in my Spring3 app. – Mathias Conradt Oct 06 '12 at 16:00
  • 10
    +1 for well explained question including very helpful diagram. Would like to give another +1 for sharing the results... – sven Mar 19 '13 at 16:05
  • 2
    Thanks. One update I didn't add: facebook actually do recommend storing the access tokens in the database, since you need them to do stuff on the facebook servers (assuming you want to). It probably isn't such a bad idea as storing passwords, since if the database is compromised (and you realise) you can invalidate all the leaked access tokens. – Timmmm Mar 19 '13 at 22:17

6 Answers6

5

I just posted an answer to a similar StackOverflow question.

Google calls this Hybrid Apps and explains how an "Android app obtains offline access for Web back-end".

The gist of it is that you'll have to pass a massaged scope string into GoogleAuthUtil.getToken in order to get it to return an Authorization Code (not an OAuth2 Token). That Authorization Code can be passed from your mobile app to your server and be exchanged for an OAuth2 Token and Refresh Token, according to this schematic.

The scope parameter needs to look something like this:

oauth2:server:client_id:<your_server_client_it>:api_scope:<scope_url_1> <scope_url_2> ...
Community
  • 1
  • 1
Wolfram Arnold
  • 7,159
  • 5
  • 44
  • 64
  • 1
    Hey @Wolfram Arnold, this used to work for me, but only a few days ago it stopped working and now it throws this error `com.google.android.gms.auth.GoogleAuthException: Unknown.` Any idea why? Is there something wrong with my scopes? https://gist.github.com/lawloretienne/7351151 – Etienne Lawlor Nov 07 '13 at 16:34
  • Sorry, I'm not sure. I haven't tried this in a while. Google must have changed something :-( – Wolfram Arnold Nov 09 '13 at 02:16
2

You can use the access token retrieved by the mobile application anywhere else. Drive SDK has a nice and simple intro that goes through the flow on https://developers.google.com/drive/quickstart-android

Burcu Dogan
  • 9,153
  • 4
  • 34
  • 34
2

it describes exactly what you want: https://developers.google.com/identity/protocols/CrossClientAuth

1

You probably need OpenID Connect, which uses OAuth tokens for authentication. As for AccountManager, the current OAuth support is a bit hacky, the new Google Play Services, set to be released 'soon' should hopefully make this better. See here for a demo.

JSK NS
  • 3,346
  • 2
  • 25
  • 42
Nikolay Elenkov
  • 52,576
  • 10
  • 84
  • 84
  • 1
    I can't see how that OpenID Connect demo is any different from just using OAuth2 as I was planning. I mean it doesn't mention OpenID at all... – Timmmm Jul 25 '12 at 14:18
  • You get a login dialog, you get a token, you can validate id and get the user identity (email address, etc.). Step 4 is you step 10, except that it actually works :) Details about the whole idea here: http://openid.net/connect/ And, yes I agree it's not ideal, but it's the best there is currently (AFAIK, of course). – Nikolay Elenkov Jul 26 '12 at 01:07
  • Oh right, so you're saying I can do what I was suggesting and it will actually work? Google's OAuth2 servers won't complain about the access token being "stolen"? – Timmmm Jul 26 '12 at 19:06
  • 1
    Haven't used it for a real world project, but yes. The token is for the authentication/user info service, so no one should complain :) – Nikolay Elenkov Jul 27 '12 at 01:03
  • @NikolayElenkov What values can be used for the 'scope' parameter of the getToken() method of the Google Play Services you mentioned? In your linke, http://oauthssodemo.appspot.com/step/1, I see info about the scope for OAuth/OpenID, but when I use these scopes with Google Play Services, I'm always getting "GLS error: Unknown / GoogleAuthException" errors. Unfortunately nothing to be found in the docs of the GoogleAuthUtil class. Thanks! – Mathias Conradt Sep 30 '12 at 05:29
  • Nevermind, I found it at: https://developers.google.com/android/google-play-services/reference/com/google/android/gms/common/Scopes – Mathias Conradt Sep 30 '12 at 05:34
  • Not sure what you are trying to do, but that document only lists the Google+ scope. The scope of pretty much any Google service should work (as long as it supports OAuth2), but you have to check each service's docs for the actual scope. – Nikolay Elenkov Sep 30 '12 at 05:56
  • Just getting familiar with the Google Play Services, and wondering why the doc only lists Google+ scope, when all others basically work as well. I guess the doc is still work in progress. – Mathias Conradt Sep 30 '12 at 13:35
  • They define a constant because the current version comes with an integrated G+ client and you need to get a token to use it. – Nikolay Elenkov Sep 30 '12 at 14:02
  • Google has finally put up a pretty good guide for using Play Services to implement this: [Verifying Back-End Calls from Android Apps](http://android-developers.blogspot.com/2013/01/verifying-back-end-calls-from-android.html) – ejegg Aug 21 '13 at 14:36
  • Hey @Nikolay, this used to work for me, but only a few days ago it stopped working and now it throws this error com.google.android.gms.auth.GoogleAuthException: Unknown. Any idea why? gist.github.com/lawloretienne/7351151 – Etienne Lawlor Nov 07 '13 at 09:00
1

At least with Google, the access token eventually expires. This is why the android AccountManager has the invalidateAuthToken method--the cached access token has expired, and you need to tell the AccountManager to stop giving you the old one and instead get a new one. This makes it somewhat safer to cache the token, as the token itself doesn't give you eternal access as that user. Instead, when valid, it merely says "at some point in the recent past, this token was acquired by a trusted source."

Here are a couple of things I've found helpful when working with tokens. The first is Google's tokeninfo endpoint. The token itself is just base64-encoded JSON. This means it isn't encrypted, so you need to be sure to be using HTTPS for communication. However, it also means that you can examine the token and have a better idea of what's going on.

https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=

If your token was "abcdef", you would navigate to:

https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=abcdef

and Google would unpack the token for you. It is a simple JSON object that includes an "expires_in" field telling you the number of seconds for which the token is still valid. At 6:03 in the video below you can see the unpacked token:

https://developers.google.com/events/io/sessions/383266187

That video includes a thorough overview of OAuth2 and is well worth watching in its entirety if you're going to be dealing with OAuth and tokens. The speaker also discusses other forms of Oauth2 tokens, that are not access tokens, that do not expire.

Another useful resource is the OAuth Playground. This lets you do basic things like request scopes, make up requests, and get back tokens. This link seems to work sporadically, and on Chrome I had to install the Oauth Playground app:

https://developers.google.com/oauthplayground/

And here is a tutorial by Tim Bray, the speaker in the video, explaining how to use access tokens to communicate to a server from an Android app. This was useful to me because I began to understand how the different things in the Google API Console work together:

http://android-developers.blogspot.in/2013/01/verifying-back-end-calls-from-android.html

With regards to the actual answer to your question, I would say you never need to cache the access token on the server. As explained in the Verifying Back End Calls from Android link above, verifying a token is almost always a fast static call, meaning there's no reason to cache the tokens:

The libraries can cache the Google certs and only refresh them when required, so the verification is (almost always) a fast static call.

Finally, you can indeed use the AccountManager to get access tokens. However, Google now instead encourages the use of the GoogleAuthUtil class in the Play Services library instead:

In a nutshell what's the difference from using OAuth2 request getAuthToken and getToken

Here note the comment by Tim Bray, the same guy yet again from the above links, saying that they are putting their efforts into the GoogleAuthUtil route. Note, however, that this means you would be limited to Google authentication. I believe that the AccountManager could be used to get, for example, a Facebook token instead--not the case with GoogleAuthUtil.

Community
  • 1
  • 1
user1978019
  • 3,008
  • 1
  • 29
  • 38
  • Hey @user1978019, this used to work for me, but only a few days ago it stopped working and now it throws this error `com.google.android.gms.auth.GoogleAuthException: Unknown.` Any idea why? Is there something wrong with my scopes? https://gist.github.com/lawloretienne/7351151 – Etienne Lawlor Nov 07 '13 at 16:35
  • Sorry toobsco42, just saw this. Did you get it resolved? – user1978019 Feb 24 '14 at 19:17
0

When we had a need to do something similar on a non-google OAuth Server, we kept the tokens in a DB on the website. The app would then use web services to request the token when needed to request data.

The user could do the OAuth registration on either the web or app. They shared the same application token, so they could share the same access token. After the registration we would store the access and refresh tokens in the DB for use from whichever app needed it.

Mark S.
  • 3,849
  • 4
  • 20
  • 22
  • By "application token" do you mean "client id"? Because there isn't any way to make `AccountManager` use the same client id as my website, as far as I can tell. Also how do you authenticate the app when it asks your web server for the user's access token? Cheers. – Timmmm Jul 25 '12 at 14:20
  • This was a couple years ago and we were using OAuth 1.0 at the time. The system was designed so that the user could authenticate via the website OR the mobile app by using the same application token in both. After the authentication, the access token was stored in a DB behind the website. Whenever the user logged into the application (web or app) a request was made to the DB (via Web Service for the app) for the access token. If there was no access token, then the registration flow took over. As for authenticating the web service, we used WS-Security on the SOAP web service. – Mark S. Jul 25 '12 at 15:59
  • Well maybe I'm misunderstanding, but what's to stop my rogue app asking your web service for someone else's access token? Anyway it sounds roughly similar to what I was going to do, except I guess it is not possible now with OAuth2 because of the client IDs. The only thing I can think to do is have my site pretend it is also the app. Seems hacky though. – Timmmm Jul 25 '12 at 16:04
  • You would need to have the certificate for my WS-Security web service header. Without that certificate, you can't call my web service. – Mark S. Jul 25 '12 at 22:20
  • Can't I extract the certificate from your app? – Timmmm Jul 27 '12 at 13:40
  • 1
    Sounds insecure! Mind if I ask the name of the app/website? You know, for... research... – Timmmm Jul 28 '12 at 01:47
  • Project was cancelled prior to release. Although there was a username/password that was required to get into the app/website before you ever got to the website. – Mark S. Jul 28 '12 at 02:21