73

I used Facebook login to identify users. When a new user comes, I store their userID in my database. Next time they come, I recognized their Facebook ID and I know which user it is in my database.

Now I am trying to do the same with Google's OAuth2, but how can I recognize the users?

Google sends me several codes and tokens (access_token, id_token, refresh_token), however none of them are constant. Meaning if I log out and log back in 2 minutes later, all 3 values have changed. How can I uniquely identify the user?

I am using their PHP client library: https://code.google.com/p/google-api-php-client/

Nathan H
  • 48,033
  • 60
  • 165
  • 247

6 Answers6

105

As others have mentioned, you can send a GET to https://www.googleapis.com/oauth2/v3/userinfo, using the OAuth2 bearer token you just received, and you will get a response with some information about the user (id, name, etc.).

It's also worth mentioning that Google implements OpenID Connect and that this user info endpoint is just one part of it.

OpenID Connect is an authentication layer on top of OAuth2. When exchanging a authorization code at Google's token endpoint, you get an access token (the access_token parameter) as well as an OpenID Connect ID token (the id_token parameter).

Both these tokens are JWT (JSON Web Token, https://datatracker.ietf.org/doc/html/draft-ietf-oauth-json-web-token).

If you decode them, you'll get some assertions, including the id of the user. If you link this ID to a user in your DB, you can immediately identify them without having to do an extra userinfo GET (saves time).

As mentioned in the comments, these tokens are signed with Google's private key and you may want to verify the signature using Google's public key (https://www.googleapis.com/oauth2/v3/certs) to make sure they are authentic.

You can see what's in a JWT by pasting it at https://jwt.io/ (scroll down for the JWT debugger). The assertions look something like:

{
    "iss":"accounts.google.com",
    "id":"1625346125341653",
    "cid":"8932346534566-hoaf42fgdfgie1lm5nnl5675g7f167ovk8.apps.googleusercontent.com",
    "aud":"8932346534566-hoaf42fgdfgie1lm5nnl5675g7f167ovk8.apps.googleusercontent.com",
    "token_hash":"WQfLjdG1mDJHgJutmkjhKDCdA",
    "iat":1567923785,
    "exp":1350926995
}

There are also libraries for various programming languages to programatically decode JWTs.

PS: to get an up to date list of URLs and features supported by Google's OpenID Connect provider you can check that URL: https://accounts.google.com/.well-known/openid-configuration.

Community
  • 1
  • 1
Christophe L
  • 13,725
  • 6
  • 33
  • 33
  • 1
    Yes, but in order to use this information safely, you must verify the signiature of the JWT, for which you need the public key. Any idea where google makes their public keys available? – jazmit Mar 16 '13 at 21:29
  • 7
    OK, found it: https://www.googleapis.com/oauth2/v1/certs I should also point out that doing the verification is absolutely necessary, otherwise an attacker could easily sign in to your application using any already-registered google account. – jazmit Mar 16 '13 at 21:44
  • @jamesinchina And how do you think this attack is possible if the info itself comes directly from Google, not from attacker? – stroncium Oct 07 '14 at 13:56
  • @stroncium: right, in the authorization code flow, the token comes directly from Google so verifying the signature could be overkill. OTOH, in the implicit flow (and couple other flows I think), the ID token can be passed as a URL parameter and would need to be verified. – Christophe L Oct 07 '14 at 19:42
  • 12
    I don't think this complete accurate. The access_token is NOT a JWT token, – JoseOlcese Jun 19 '15 at 18:21
  • 1
    Google seems to have removed their JWT decoder but there's an easy to use one available here: https://jwt.io/ – mcsheffrey Dec 23 '16 at 15:27
  • 1
    If you don't care to paste your _cryptographic tokens_ to some _random website_ then you could also pipe the id token through `tr '._-' '\n/+' | sed '2s|$|===|p;d' | base64 -D`. The `2` in the `sed` selects the 2nd part of the tuple, which is probably what you want. – phs Sep 07 '17 at 23:56
  • 1
    @phs jwt.io is not a "random" website, it is maintained by Auth0, one of the creators of the JWT standard – dan Feb 20 '18 at 16:39
33

I inserted this method into google-api-php-client/src/apiClient.php:

public function getUserInfo() 
{
    $req = new apiHttpRequest('https://www.googleapis.com/oauth2/v1/userinfo');
    // XXX error handling missing, this is just a rough draft
    $req = $this->auth->sign($req);
    $resp = $this->io->makeRequest($req)->getResponseBody();
    return json_decode($resp, 1);  
}

Now I can call:

$client->setAccessToken($_SESSION[ 'token' ]);
$userinfo = $client->getUserInfo();

It returns an array like this (plus e-mail if that scope has been requested):

Array
(
    [id] => 1045636599999999999
    [name] => Tim Strehle
    [given_name] => Tim
    [family_name] => Strehle
    [locale] => de
)

The solution originated from this thread: https://groups.google.com/forum/#!msg/google-api-php-client/o1BRsQ9NvUQ/xa532MxegFIJ

Christian
  • 27,509
  • 17
  • 111
  • 155
Tim Strehle
  • 380
  • 2
  • 6
  • 9
    Note that Google has recently changed the response and the `id_token` now contains the static identifier in key `sub` instead of in key `id` as before and in the example above. AFAIK, this change is their interpretation of OpenID Connect protocol. Unfortunately, as I'm writing this the responses seems somewhat random: sometimes it's `id` and sometimes it's `sub` so I need to support both. – Mikko Rantalainen Mar 04 '15 at 07:10
  • I think `/oauth2/v1/userinfo` will give you `id`, and `/oauth2/v3/userinfo` will give you `sub` (note the different versions!) Maybe it changes if you don't provide a version? – gengkev Jun 16 '20 at 03:39
22

It should be mentioned, that the OpenID Connect API returns no id attribute anymore.

It's now the sub attribute which serves as a unique user identification.

See Google Dev OpenID Connect UserInfo

blang
  • 2,090
  • 2
  • 18
  • 17
  • 2
    Not sure this is right. Despite what the docs state, if i restart my auth server, clear the cache and re-login, the 'sub' is different. – JsAndDotNet Dec 19 '16 at 22:08
  • Did anyone else have HockeyJ's issue with 'sub' changing for the same login? – dardawk Jul 11 '17 at 01:38
  • Yes, I have the same problem that `sub` is different. Just asked this question in another thread https://stackoverflow.com/questions/53421907/can-google-user-id-be-changed – Ilya Dzivinskyi Nov 21 '18 at 23:33
2

"Who is this?" is essentially a service; you have to request access to it as a scope and then make a request to the Google profile resource server to get the identity. See OAuth 2.0 for Login for the details.

Ryan Culpepper
  • 10,495
  • 4
  • 31
  • 30
  • This seems to be most technically correct, but it lacks the extra detail that the currently accepted answer has. Merge the two answers together and you have gold. – Emmaly Oct 08 '12 at 03:36
1

Altough JWTs can be validated locally with the public key, (Google APIs Client Library downloads and caches they public keys automatically) checking the token on Google's side via the https://www.googleapis.com/oauth2/v1/tokeninfo endpoint is necessary to check if the access for the applicaton has been revoked since the creation of the token.

Orange
  • 156
  • 1
  • 4
0

Java version

OAuth2Sample.java

honzajde
  • 2,270
  • 3
  • 31
  • 36