February 25th, 2013 by Matyas Danter

Setting up Google OAuth2 with Java

oauth_logo

For all of you who are trying to figure out how to integrate with Google’s single sign-on functionality, this article might be for you. I’ve taken the liberty of condensing all of the actual logic required to perform OAuth Google login, and provided it as a class and a JSP (seen below). In order to follow along better, I suggest cloning the example GitHub repository, and deploying to the application to your server of choice.

Assumptions

  • Familiarity with object oriented programming, Java, Maven, and Java EE
  • An IDE, it helps if you are comfortable using one (i.e. Eclipse)
  • Java application server listening on localhost:8080

Prerequisites

1google_oauth_access
  • Google API Access credentials (Client ID, Client Secret). Set it up here https://code.google.com/apis/console/
  • Set up allowed Redirect URIs at Google API → API Access. Input: http://localhost:8080/OAuth2v1/index.jsp
  • The source code referenced in this article from GitHub.
  • A positive outlook on life.

Please use the link above to set up API Access Credentials. When you are finished, you will see a similar page to the one below. Use your Client ID/Secret from this page to replace the values of the String constants in GoogleAuthHelper.java.

Usage

  1. Add Client ID, and Client Secret parameters to GoogleAuthHelper.java
  2. Compile the project:
    $ mvn clean install
  3. Deploy war to application server
  4. Browse to: http://localhost:8080/OAuth2v1/
  5. Click "log in with google" on top of the page

1. Add Client ID and Secret

Replace the constants following constant values in GoogleAuthHelper.java with the values provided to you by Google API Access.

/**
 * Please provide a value for the CLIENT_ID constant before proceeding, set this up at https://code.google.com/apis/console/
 */
private static final String CLIENT_ID = "YOUR ID HERE";
 
/**
 * Please provide a value for the CLIENT_SECRET constant before proceeding, set this up at https://code.google.com/apis/console/
 */
private static final String CLIENT_SECRET = "SUPER SECRET SAUCE";

2, 3, & 4. Compile the Project, Deploy the WAR, Open that Browser

This is a Maven based project, so issue a Maven install command to build the project and assemble the war file from the root of the project (where the pom.xml file is located).

$ mvn clean install

When Maven is finished creating the web archive, deploy it to your favorite server and navigate to http://localhost:8080/OAuth2v1/

2click_auth

5. Click "log in with google"

Now that your app server is running, the application is deployed, and your web browser is pointed at the application’s context root, you will see a page similar to the one below. I double dog dare you to click that log in button. You know you want to.

3result

After successful authentication you will see the page below, but there are a few important things to notice:

  1. The URL changed, now it contains two request parameters, state, and code.
  2. The page contains JSON output of your google account’s profile information.

Source Code

The authentication is possible thanks to the GoogleAuthorizationCodeFlow class. This class uses the Builder pattern to provide most of its functionality. GoogleAuthHelper’s no-argument constructor initializes the Flow using your client ID, secret, and other constants. The buildLoginUrl() method constructs the Google authentication URL based on the CALLBACK_URI and returns it as a Java String. This CALLBACK_URI must match one of the redirect URIs that you set up at Google’s API Access page. Upon successful authentication, OAuth2 will redirect you to CALLBACK_URI, and append the state and code GET request parameters to it. Please note, that the state request parameter has two purposes, one is to help differentiate authentication providers (i.e. Facebook OAuth, Google OAuth, or your own custom OAuth provider), the other and more important purpose is to pass an anti-forgery state token.

We need to use the code GET request parameter as the input for the getUserInfoJson(String authCode) method. If all is well, this method will return a JSON encoded Google profile as a Java String.

Here is the basic code that you can snip into your project:

<%
/*
 * The GoogleAuthHelper handles all the heavy lifting, and contains all "secrets"
 * required for constructing a google login url.
 */
final GoogleAuthHelper helper = new GoogleAuthHelper();
if (request.getParameter("code") == null
	|| request.getParameter("state") == null) {
	/*
	 * initial visit to the page
	 */
	out.println("<a href='" + helper.buildLoginUrl() + "'>log in with google</a>");

	/*
	 * set the secure state token in session to be able to track what we sent to google
	 */
	session.setAttribute("state", helper.getStateToken());


} else if (request.getParameter("code") != null && request.getParameter("state") != null && request.getParameter("state").equals(session.getAttribute("state"))) {

	session.removeAttribute("state");

	/*
	 * Executes after google redirects to the callback url.
	 * Please note that the state request parameter is for convenience to differentiate
	 * between authentication methods (ex. facebook oauth, google oauth, twitter, in-house).
	 * 
	 * GoogleAuthHelper()#getUserInfoJson(String) method returns a String containing
	 * the json representation of the authenticated user's information. 
	 * At this point you should parse and persist the info.
	 */

	out.println(helper.getUserInfoJson(request.getParameter("code")));
}
%>
GoogleAuthHelper.javaView Complete File
public final class GoogleAuthHelper {

	/**
	 * Please provide a value for the CLIENT_ID constant before proceeding, set this up at https://code.google.com/apis/console/
	 */
	private static final String CLIENT_ID = "YOUR ID HERE";
	/**
	 * Please provide a value for the CLIENT_SECRET constant before proceeding, set this up at https://code.google.com/apis/console/
	 */
	private static final String CLIENT_SECRET = "SUPER SECRET SAUCE";

	/**
	 * Callback URI that google will redirect to after successful authentication
	 */
	private static final String CALLBACK_URI = "http://localhost:8080/OAuth2v1/index.jsp";
	
	// start google authentication constants
	private static final Iterable<String> SCOPE = Arrays.asList("https://www.googleapis.com/auth/userinfo.profile;https://www.googleapis.com/auth/userinfo.email".split(";"));
	private static final String USER_INFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo";
	private static final JsonFactory JSON_FACTORY = new JacksonFactory();
	private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
	// end google authentication constants

	private String stateToken;
	
	private final GoogleAuthorizationCodeFlow flow;
	
	/**
	 * Constructor initializes the Google Authorization Code Flow with CLIENT ID, SECRET, and SCOPE 
	 */
	public GoogleAuthHelper() {
		flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT,
				JSON_FACTORY, CLIENT_ID, CLIENT_SECRET, SCOPE).build();
		generateStateToken();
	}

	/**
	 * Builds a login URL based on client ID, secret, callback URI, and scope 
	 */
	public String buildLoginUrl() {
		
		final GoogleAuthorizationCodeRequestUrl url = flow.newAuthorizationUrl();
		
		return url.setRedirectUri(CALLBACK_URI).setState(stateToken).build();
	}
	
	/**
	 * Generates a secure state token 
	 */
	private void generateStateToken(){

		SecureRandom sr1 = new SecureRandom();

		stateToken = "google;"+sr1.nextInt();

	}

	/**
	 * Accessor for state token
	 */
	public String getStateToken(){
		return stateToken;
	}

	/**
	 * Expects an Authentication Code, and makes an authenticated request for the user's profile information
	 * @return JSON formatted user profile information
	 * @param authCode authentication code provided by google
	 */
	public String getUserInfoJson(final String authCode) throws IOException {

		final GoogleTokenResponse response = flow.newTokenRequest(authCode).setRedirectUri(CALLBACK_URI).execute();
		final Credential credential = flow.createAndStoreCredential(response, null);
		final HttpRequestFactory requestFactory = HTTP_TRANSPORT.createRequestFactory(credential);
		// Make an authenticated request
		final GenericUrl url = new GenericUrl(USER_INFO_URL);
		final HttpRequest request = requestFactory.buildGetRequest(url);
		request.getHeaders().setContentType("application/json");
		final String jsonIdentity = request.execute().parseAsString();

		return jsonIdentity;

	}
}

Next Steps

If you haven’t already downloaded the code and run it, I suggest you do that before bringing it into your codebase. There are a few Maven dependencies in the POM that you will need to include.

From here on, you may parse this information using Jackson, and persist it to a database or other data store. These are the basic building blocks with which you should be able to “get the stuff done.”

Resources and Links

About the author:

Matyas Danter is a Senior Associate at LiquidHub currently working at a large financial institution as a Production Support Specialist. He is interested in cryptography, software development awesomeness, and enterprise web applications.

Posted in Java, JBoss, OpenSource, Technology

20 Comments

  1. Failed to resolve artifact says:

    Hi,

    your article is very helpful, thank you!

    Having a positive outlook on life I am now trying out to compile the current https://github.com/mdanter/OAuth2v1.git project!

    I did not perform any source code change yet, but it fails with exception: Failed to resolve artifact

    Hereafter part of the maven feedback (I remove some of it to make it more concise):

    Missing:
    ———-
    1) com.google.code.findbugs:jsr305:jar:${project.jsr305.version}
    2) com.google.guava:guava-jdk5:jar:${project.guava.version}
    3) org.apache.httpcomponents:httpclient:jar:${project.httpclient.version}
    4) xpp3:xpp3:jar:${project.xpp3.version}
    5) org.codehaus.jackson:jackson-core-asl:jar:${project.jackson-core-asl.version}
    6) com.google.oauth-client:google-oauth-client:jar:${project.oauth.version}
    6 required artifacts are missing.

    for artifact:
    com.danter.auth.google:OAuth2v1:war:0.0.1-SNAPSHOT

    from the specified remote repositories:
    google-api-services (http://google-api-client-libraries.appspot.com/mavenrepo),
    jbossOrgRepo (https://repository.jboss.org/nexus/content/groups/public-jboss/),
    central (http://repo1.maven.org/maven2)

    I am using the following versions of Maven/Java:
    Apache Maven 2.0.11 (r909250; 2010-02-12 06:55:50+0100)
    Java version: 1.6.0_26

    Just wondering if anything like Maven properties defining ${project.jsr305.version}, ${project.guava.version},… are missing or if I overlooked some information or prerequisites ?

    Patrick

    1. Patrick,

      This is usually an indication that you don’t have access to the repositories that contain the libraries. Please take a look at this thread on stackoverflow: http://stackoverflow.com/questions/5409802/how-shoud-i-install-missing-artifacts-in-a-maven-project

      1. Patrick says:

        Matyas,
        thank you for your pointer to stackoverflow. I successfully "git clone" and "mvn clean install" your project in one go from another computer and network! The only other difference being that I use Maven 3.0.3 which should not matter.
        From my other location I had to explicitly add some dependencies to have their Maven artifact "version" resolved! As follow:
        <dependency>
        <groupId>com.google.code.findbugs</groupId>
        <artifactId>jsr305</artifactId>
        <version>1.3.9</version>
        </dependency>
        <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava-jdk5</artifactId>
        <version>13.0</version>
        </dependency>
        <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.0.1</version>
        </dependency>
        <dependency>
        <groupId>xpp3</groupId>
        <artifactId>xpp3</artifactId>
        <version>1.1.4c</version>
        </dependency>
        <dependency>
        <groupId>org.codehaus.jackson</groupId>
        <artifactId>jackson-core-asl</artifactId>
        <version>1.9.9</version>
        </dependency>
        <dependency>
        <groupId>com.google.oauth-client</groupId>
        <artifactId>google-oauth-client</artifactId>
        <version>1.13.1-beta</version>
        </dependency>

        Finally following your instructions I had the "log in with google" work in less than 15min! Great job!

        Thanks again,

        Patrick

      2. Thank you, Patrick! Thank you for providing this information for readers who may run into the same issue.

  2. Jake Dsouza says:

    Thank you very much , I spent a long time trying to figure out oauth with java and this is very nicely written.

  3. Waqas Ahmed says:

    Hi Matyas Danter, Need your help….

    First of all i would like to thank you for that helpfull code. I need to get the OAuth token information from that code behalf that google response after user authentication to an application.

    When i get the user information by that code

    out.println(helper.getUserInfoJson(request.getParameter("code")));

    how to setup the google accesstoken in my session, if session has stored user access token then set accessToken ( you may be fimiliar with that google function, i don’t know how to set that function in this example) and without showing Login with google button fetching user information or what we want.

    For reference i need to know you Stackoverflow example. (when user loggin with google accounts stackoverflow keeps google user access long-lived even user logged out from google other websites.)

    Another reference is "https://oauth2-login-demo.appspot.com/&quot;

    Thanks in advance

    1. Hi Waqas Ahmed,

      request.getParameter("code")

      is the auth token that you may store in the session for repeated requests. This token does expire after a certain amount of time so you may not want to persist it or if you do you need to handle the Exception when the token expires and modify it. For more information, please take a look at the Basic Steps section under subsections 3. "Send Access Token to an API" and 4. "Refresh the Access Token (optional)" in the official documentation from google at https://developers.google.com/accounts/docs/OAuth2

  4. romu31 says:

    if you are lazzy it is even works with Jetty on http://localhost:8080
    and use

    mvn jetty:run

    private static final String CALLBACK_URI = "http://localhost:8080";
    
    <plugin>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-maven-plugin</artifactId>
      <version>9.0.0.v20130308</version>
      </plugin>
  5. Neeraj Kumar says:

    I am getting – com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request { "error" : "invalid_client" } at flow.newTokenRequest(authCode).setRedirectUri(CALLBACK_URI).execute(),please help

    1. Neeraj Kumar says:

      It was due to CLIENT_SECRET mismatch .

  6. how to seperate the values we received as user identity so that we can store data in database says:

    #java

  7. tushar says:

    Hi

    I am getting Connection refused: connect when calling the execute method of getUserInfoJson(). I am behind a proxy, is this causing problems?

    1. tushar says:

      Added the following JVM args to fix the problem.

      http.proxyHost
      http.proxyPort
      https.proxyHost
      https.proxyPort

  8. Sheet says:

    Hello,
    I managed to run example project using Eclipse and Struts2,
    but now i am quite confused:
    in getUserInfoJson we obtain access token,
    what should i do next?
    my problem is that i want to (for example)
    display database content which is dependant to UserID,
    how should i parse user info into another .jsp?
    just make a string out of it?
    Best regards
    S

  9. Ranjith says:

    Hello, Need your help Please.. My application is prompting each time for having offline access. Could you please help me how to configure this.
    Lets say i have implemented for an appln login. in that case asking for offline access each time does not make sense. Please let me know if i have to do something for this.

    Thanking you in advance.

  10. kamal says:

    Could someone explain hoe the application getting started or where is the entry point ….

  11. David K says:

    When we try to run this on JB 7, we’re getting: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

    What certificate do we need to install in JBoss? One from Google? Confused on this part. I know how to install the certs, just not sure of the cert to install to get this running. :(

    1. Khoyendra says:

      I’m also getting this issue
      have you fixed this issue, may I know how to fix this?

  12. guilherme says:

    Thanks you very much! Worked like a charm

  13. Khoyendra says:

    ‘m trying to run this code but I’m getting SSLHandshakeException.

    Once login success in google when I’m clicking on access button getting this below exception.
    Can you please help me what certificate do I need to install

    Exception -

    org.apache.jasper.JasperException: An exception occurred processing JSP page /index.jsp at line 86
    
    
    83: * At this point you should parse and persist the info.
    84: */
    85: 
    86: out.println(helper.getUserInfoJson(request.getParameter("code")));
    87: 
    88: out.println("");
    89: }
    
    
    Stacktrace:
    org.apache.jasper.servlet.JspServletWrapper.handleJspException(JspServletWrapper.java:568)
    org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:460)
    org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:390)
    org.apache.jasper.servlet.JspServlet.service(JspServlet.java:334)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    
    
    root cause
    
    javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    sun.security.ssl.Alerts.getSSLException(Unknown Source)
    sun.security.ssl.SSLSocketImpl.fatal(Unknown Source)
    sun.security.ssl.Handshaker.fatalSE(Unknown Source)
    sun.security.ssl.Handshaker.fatalSE(Unknown Source)
    sun.security.ssl.ClientHandshaker.serverCertificate(Unknown Source)
    sun.security.ssl.ClientHandshaker.processMessage(Unknown Source)

Leave a Comment




Please note: In order to submit code or special characters, wrap it in

[code lang="xml"][/code]
(for your language) - or your tags will be eaten.

Please note: Comment moderation is enabled and may delay your comment from appearing. There is no need to resubmit your comment.