Tuesday, July 22, 2014

Google APIs : checking the scopes contained in an OAuth2 access token

When you've stored an OAuth2 access/refresh token couple for a long time, you might not be sure what scopes it was giving access to.

In that case, just pass the access token to the tokeninfo endpoint :

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

The output looks like this :

{
 "issued_to": "407408718192.apps.googleusercontent.com",
 "audience": "407408718192.apps.googleusercontent.com",
 "user_id": "1170123456778279183758",
 "scope": "https://www.googleapis.com/auth/userinfo.email",
 "expires_in": 3585,
 "email": "someone@yourdomain.com",
 "verified_email": true,
 "access_type": "offline"
}

Of course you can also do this with a library. In Java :

Oauth2 oauth2 = new Oauth2.Builder(new NetHttpTransport(), new JacksonFactory(), null)
                  .setApplicationName(ProbeClient.APPLICATION_NAME)
                  .build();
return oauth2.tokeninfo().setAccessToken(yourAccessToken).execute();

You'll need the following dependency :

<dependency>
            <groupId>com.google.apis</groupId>
            <artifactId>google-api-services-oauth2</artifactId>
            <version>v1-rev76-1.18.0-rc</version>
</dependency>

Thursday, July 3, 2014

How to provide seamless Single Sign On on Google Apps

Context

Before OAuth, in order to authenticate a user an application would redirect him to the OpenID login page.


It was possible to avoid the consent window by registering your the application through the Google Apps Console.


However, OpenID is going to be deprecated and replaced by OAuth2. This document details how to achieve seamless Single Sign On (no consent window) with OAuth2 through the GApps Marketplace.

OAuth authentication flow



1. Redirect the user to the following URL :
   https://accounts.google.com/o/oauth2/auth   ?client_id=815629710953-nd6a8ofur4prtau84mt67r9h25hvkd4d.apps.googleusercontent.com
     &response_type=code
     &scope=email+profile
     &redirect_uri=https://localhost:8080
     &state=12345678


This is standard OAuth behavior, here are the different parts :
  • client_id : a client_id generated in the API Console
  • response_type : always keep "code"
  • scope : keep "email+profile", otherwise the seamless SSO does not work
  • redirect_uri : URL of your application
  • state : a random string generated by your app to ensure that nobody is stealing the account info


2. Google will bypass the consent window and send the user to the redirect_uri, passing in parameters the same state and the code. In our example :
   https://localhost:8080/?state=12345678&code=4/PE4pFdMWdSU89L5BxCfQYl7rrCe4.sg3ll1ncyWAadJfo-QBMszv26aO4jQI


3. Check that the state is valid, typically by checking with a value generated from the user's session. Exchange the code against a refresh and access token. This is standard OAuth behavior, executed server side, here is the request :


HTTP POST to https://accounts.google.com/o/oauth2/token HTTP/1.1


code=4%2FNa7-XZQEVy4rSpoWHI0g-eanF2pW.os7UPIcmZBUWdJfo-QBMszs6qtm4jQI&redirect_uri=https%3A%2F%2Flocalhost:8080&client_id=815629710953-nd6a8ofur4prtau84mt67r9h25hvkd4d.apps.googleusercontent.com&scope=&client_secret=YOURCLIENTSECRET&grant_type=authorization_code


4. Here's a typical answer from Google :


{
 "access_token": "ya29.MQ...UlmQ7g",
 "token_type": "Bearer",
 "expires_in": 3600,
 "refresh_token": "1/V09...4C8",
 "id_token": "eyJhbGciOi...WlOvPsnHeBEuChaeCziau_MggFWoBCuozy0ZoBdXXc"


}


From this answer, the best option is to JWT-decode the "id_token". You can use this online tool for testing. Here's the result :


{
   "sub": "109177816070718727151",
   "cid": "407408718192.apps.googleusercontent.com",
   "iss": "accounts.google.com",
   "email_verified": true,
   "id": "109177816070718727151",
   "at_hash": "rAxrTmUjD7rk-5H2EJFnZw",
   "exp": 1403860943,
   "azp": "407408718192.apps.googleusercontent.com",
   "iat": 1403857043,
   "verified_email": true,
   "token_hash": "rAxrTmUjD7rk-5H2EJFnZw",
   "email": "david.hatanian@revevol.eu",
   "hd": "revevol.eu",
   "aud": "407408718192.apps.googleusercontent.com"
}


There you go ! you have the email of the user, and in "id" this is a unique id internal to Google, that will never change even if the email changes. It's also the G+ id for G+ users. Now you can store that in the user's session.


Configuration steps

Here are all the required setup steps to perform the authentication process described above :


  1. Create an API project through the API Console
  2. Enable the G+ API and the Marketplace SDK





  1. Generate an OAuth2 webapp credential. Put your application URL as a registered redirect URI.





  1. Setup the marketplace SDK. (click on the cog wheel next to the Marketplace SDK name) You must provide icons and basic info on your app. You will need to choose either "Universal Navigation Link" or "Drive App". See below for the pros and cons, but we recommend "Universal Navigation Link".





  1. Go to the Chrome Webstore web developer dashboard, and upload a zip file composed of :
    1. A manifest.json
    2. 16px and 128px icons for your app


Here's an example of manifest.json :
{
   "name": "Revevol SSO to marketplace",
   "version": "0.1",
"manifest_version": 2,
   "description": "Demonstration of the seamless SSO with the marketplace SDK",
   "icons": {
   "128": "images/pdf-icon-128x128.png",
       "16": "images/Pdf_16x16_Crystal_SVG.png"
   },
   "container": ["DOMAIN_INSTALLABLE"],
   "api_console_project_id": "815629710953",
"app": { "launch": { "web_url": "https://yourapplication.com" } }
}


  1. Limit the access to the marketplace application to a defined list of trusted testers. Publish it. Here is what you can see in the Chrome Webstore developer dashboard after publication :



  1. After publication (can take up to 60 minutes), as a domain admin, install the app from the Chrome Webstore onto the domain by going on the application’s URL in the Chrome Web Store. To do this, click on the “Integrate with Google” button.





Limitations

There are two important limitations with this process :


Iframes : You cannot redirect to the OAuth screen in an iframe (like in a sites gadget). In this case you will have to display a button to the users, on which they will have to click. The button will open an authentication popup, the user will be seamlessly authenticated. Then your javascript code must automatically close the popup and refresh the iframe.


More apps menu : When setting up the marketplace SDK, you have the choice between "Navigation link" and "Drive App". If you choose "Navigation Link" the app will show in the "More" menu on the top right. If you choose the "Drive App" then the app will show in the "Manage Apps" menu of Drive. We haven't found a way yet to remove all mentions of the app, and have reported a feature request to Google for this.





Example project

Testing

An example project is available on Github here : https://github.com/dhatanian/demo-marketplace-sso

This project contains the source code for a very simple Java App Engine web application that displays the email of the user :




It is deployed on App Engine on this URL : https://revevol-marketplace-sso-demo.appspot.com/secured


To experience seamless SSO, remember to install the app on your domain through the Chrome Web Store before accessing the URL above. Here’s the app’s URL in the Chrome Web Store : https://chrome.google.com/webstore/detail/demo-of-sso-to-marketplac/fajllpdcdhkpkhnhknjnkfignojcbojd

To compile the sample project

To compile and deploy this project, you will need Maven. Maven is the build tool in charge of retrieving all the required libraries and compiling the project for us.




After the installation of Maven, you can run the following commands :

  • To deploy the project on a server local to your workstation : mvn appengine:devserver
  • To deploy the project on App Engine : mvn appengine:update .
    • This will update the default revevol-marketplace-sso-demo application.
    • But you can specify a different one like this : mvn appengine:update -Dappengine.appId=myappid
  • To generate an Eclipse project : mvn eclipse:eclipse .
    • Maven will generate the required Eclipse project files
    • After this, you can use the “File > Import” feature of Eclipse to import your project