Wednesday, November 7, 2012

Google Services Authentication Using OAuth2

Google APIs use the OAuth 2.0 protocol for Authentication and authorization. Google supports several OAuth2.0 flows that cover common web server, JavaScript, device, installed application, and server to server scenarios.
Today we will discuss how to use OAuth 2.0 for installed application. In a very first step, you will have to register your application with google account. Here are the steps to register application:

  • Visit this https://code.google.com/apis/console with your google account and create Project. 
  • In services tab, select services which you want to use.
  • In API access tab, "Create an OAuth 2.0 client ID". Please make sure that you select correct Application Type which is "Installed application" in our case and Installed application type is "Other". When you have completed it, you will get following information "Client ID", "Client secret", "Redirect URIs". These information will be needed for authentication


For this project, you are needed following three types of libraries:

I will suggest to have a look at https://developers.google.com/accounts/docs/OAuth2 for general idea how OAuth2 authentication works. Here is the summary:
  • Register Your application with Google
  • Redirect a browser to a URL
  • Get the Response and parse token.
  • Send the Token to the Google API you wish to access.
Now create a java project in your favorite IDE and include above downloaded libraries in your project's classpath. Create a class and paste following code:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.javainnovations.google;

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.java6.auth.oauth2.FileCredentialStore;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.data.spreadsheet.SpreadsheetEntry;
import com.google.gdata.data.spreadsheet.SpreadsheetFeed;
import com.google.gdata.util.ServiceException;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author Amer
 */
public class GoogleAuthentication {
    
    private final String CLIENT_ID = "CLIENT_ID";
    private final String CLIENT_SECRET = "CLIENT_SECRETS";
    private List scopes;
    
    private NetHttpTransport transport;
    private JacksonFactory gsonFactory;
    
    private SpreadsheetService service;
    
    public GoogleAuthentication(){
        transport = new NetHttpTransport();
        gsonFactory = new JacksonFactory();
        
        scopes = new ArrayList();
        scopes.add("https://spreadsheets.google.com/feeds");
        scopes.add("https://docs.google.com/feeds");
    }
    
    
    public static void main(String args[]) throws IOException, MalformedURLException, ServiceException{
        GoogleAuthentication auth = new GoogleAuthentication();
        auth.init();
        auth.printWorkSheets();
    }
    
    public void init() throws IOException{
        Credential credential = authenticate();
        service = initSpreadsheetService(credential);
    }
    
    public Credential authenticate() throws IOException{
        GoogleAuthorizationCodeFlow.Builder builder = new GoogleAuthorizationCodeFlow.Builder(transport,gsonFactory,CLIENT_ID,CLIENT_SECRET,scopes);
        File cfile = new File("oauth2.json");
        cfile.createNewFile();
        FileCredentialStore credentialStore = new FileCredentialStore(cfile, gsonFactory);
        builder.setCredentialStore(credentialStore);
        GoogleAuthorizationCodeFlow flow = builder.build();
        return new AuthorizationCodeInstalledApp(flow, new LocalCallbackServer()).authorize("user");
    }
    
    public SpreadsheetService initSpreadsheetService(Credential credential) {
        SpreadsheetService service = new SpreadsheetService("MySpreadsheet");
        service.setOAuth2Credentials(credential);
        return service;
    }
    
    public void printWorkSheets()throws MalformedURLException, IOException, ServiceException{
        URL SPREADSHEET_FEED_URL = new URL("https://spreadsheets.google.com/feeds/spreadsheets/private/full");
        SpreadsheetFeed feed = service.getFeed(SPREADSHEET_FEED_URL, SpreadsheetFeed.class);
        List spreadsheets = feed.getEntries();
        System.out.println("No of spreadsheets -> "+spreadsheets.size());

        
    }
    
}
Most Important method is "authenticate". Let's discuss it line by line:

  • In first line GoogleAuthorizationCodeFlow.Builder was used to input CLIENT_ID, CLIENT_SECRET, list of scopes. CLIENT_ID and CLIENT_SECRET is provided by google while registering the application
  • In next few lines, a FileCredentialStore is created. This will store the credentials info returned by google after authentication to a file and will be used later on to communicate to google services. Since its critical information so place it to some secure place like user's account folder in your operation system.
  • GoogleAuthorizationCodeFlow.Builder.build() method creates GoogleAuthorizationCodeFlow which will be input as a authentication info object to AuthorizationCodeInstalledApp class
  • AuthorizationCodeInstalledApp is mainly responsible to send request to google server for authentication with all credentials. Its constructor takes an object which implements VerificationCodeReceiver interface. Google will send code in a response to implementation of this interface. This will act as callback server. I have provided below a socket based test implementation. This is just for reference purpose.
  • Once code is received, it is sent to google again to get credential information containing access_code. which will be stored to file by FileCredentialStore as discussed above.
  • If everything goes fine, it will retrurn Credential object. This object is passed to Google Service object like SpreadhSheetService and in all further communication, this credential information will be exchanged with google server to authenticate request from valid user. See initService method for details. Rest of the thing is straight forward.

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.javainnovations.google;

import com.google.api.client.extensions.java6.auth.oauth2.VerificationCodeReceiver;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Amer
 */
public class LocalCallbackServer implements VerificationCodeReceiver {

    volatile String code;
    private final int LOCAL_SERVER_PORT = 10006;

    @Override
    public synchronized String waitForCode() {

        try {
            this.wait();
        } catch (Exception ex) {
        }
        System.out.println("returning code is -> " + code);
        return code;

    }

    @Override
    public String getRedirectUri() {

        new Thread(new MyThread()).start();
        return "http://localhost:"+LOCAL_SERVER_PORT;
    }

    @Override
    public void stop() {
    }

    class MyThread implements Runnable {

        @Override
        public void run() {
            try {
                //    return GoogleOAuthConstants.OOB_REDIRECT_URI;
                ServerSocket ss = new ServerSocket(LOCAL_SERVER_PORT);
                System.out.println("server is ready...");
                Socket socket = ss.accept();
                System.out.println("new request....");
                InputStream is = socket.getInputStream();
                StringWriter writer = new StringWriter();
                String firstLine = null;

                InputStreamReader isr = new InputStreamReader(is);
                StringBuilder sb = new StringBuilder();
                BufferedReader br = new BufferedReader(isr);
                String read = br.readLine();
                firstLine = read;
                OutputStream os = socket.getOutputStream();
                PrintWriter out = new PrintWriter(os, true);
                
                StringTokenizer st = new StringTokenizer(firstLine, " ");
                st.nextToken();
                String codeLine = st.nextToken();
                st = new StringTokenizer(codeLine, "=");
                st.nextToken();
                code = st.nextToken();
                
                out.write("RETURNED CODE IS "+code+"");
                out.flush();
//                is.close();
                
                socket.close();

                
                System.out.println("Extracted coded is " + code);

                synchronized (LocalCallbackServer.this) {
                    LocalCallbackServer.this.notify();
                }
                System.out.println("return is " + sb.toString());

            } catch (IOException ex) {
                Logger.getLogger(LocalCallbackServer.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}


2 comments:

SRK said...

Can I have the dependencies used I cannot access setOAuth2Credentials()

Ofer Yuval said...

Thanks. Very helpful !
All is working fine down to
FileCredentialStore credentialStore = new FileCredentialStore(cfile, gsonFactory);

which throws "lArgumentException: no JSON input found".
I am trying to figure out what's wrong.
Any help would be appreciated.