Providing a “Sign-in with Google” functionality using Scala


Continuing our series on providing authentication via third party OAuth/Open ID providers, in this post we look at Google. We have already covered sign in with Facebook and Sign in with Twitter in the past.

We walk through a step by step scenario to make it work for a Lift based application. Most of the steps would be the same for Play as well.

1) Register your app with Google – App must be registered through the APIs Console. The result of this registration process is a set of values that are known to both Google and your application (e.g. client_id, client_secret, JavaScript origins, redirect_uri, etc.).

2) Next step is to form the authentication URL which would be hit on google. The URL would be of the form

https://accounts.google.com/o/oauth2/auth?
scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&
state=%2Fprofile&
redirect_uri=https%3A%2F%2Foauth2-login-demo.appspot.com%2Foauthcallback&
response_type=token&
client_id=812741506391.apps.googleusercontent.com

Here, you would be getting the client ID and the redirect URL from the Google APP that you have registered. In some cases, you might want to add more than one redirect URL with the Google app registration. This is particularly useful for scenarios which involve local testing and you might have to give a local URL like http://localhost:8080/google/callback

In our case we send details to the authentication URL from our scala code

/**
   * Dispatch requests for twitter. We are interested in
   * twitter/authenticate, twitter/callback and twitter/logout
   */
  def matcher: LiftRules.DispatchPF = {
    case req @ Req("google" :: "authenticate" :: Nil, _, GetRequest) =>
      () => signUpRedirect(req)
    case req @ Req("google" :: "catchtoken" :: Nil, _, GetRequest) =>
      () => processCallBack(req)
  }

  /**
   * Calls Google API to authenticate the user.
   * Post authentication Google would send the token back on callbackURL
   * @See OAuth2 with Google https://developers.google.com/accounts/docs/OAuth2UserAgent
   */
  def signUpRedirect(req: Req): Box[LiftResponse] = {
    val callbackURL = req.hostAndPath + "/google/callback"
    val url = new GoogleBrowserClientRequestUrl(Props.get("google.client.id").openOr(""),
      callbackURL, Arrays.asList("https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile")).
      setState("/").build()
    S.redirectTo(url);

  }

As you would see, when we call /google/authenticate, we end up calling method signUpRedirect. In this method, we make a call to the google authentication URL with the details like, clientID, callbackURL (where do we want google to send back the access token), scope array (list of URLs that we would like to access when we are granted access).

3) We define the dispatcher in Lift’s Boot.scala so that it can understand the incoming request for /google/authenticate.

LiftRules.dispatch.append(GoogleDispatcher.matcher)

4) Once we get to the google URL for authentication, google provides a challenge to the user for his credentials and then redirects the request to the callbackURL that we have specified. Assume that the callback URL in our case is /google/callback

5) Now we need to handle the Google response at this URL /google/callback. The response is available to us as a fragment in the following format

https://oauth2-login-demo.appspot.com/oauthcallback#access_token=1/fFBGRNJru1FQd44AzqT3Zg&token_type=Bearer&expires_in=3600

6) In order to access the fragment we need Javascript to handle it, retrieve the access_token and pass it to the server. We use the following html the location /google/callback.
For Lift, we include the following in Sitemap.scala

//Google menu
  val googleCallback = MenuLoc(Menu.i("GoogleCallback") / "google" / "callback" >> Hidden)

The javascript for accessing the access_token is

<script type="text/javascript" language="javascript">// <![CDATA[
	// First, parse the query string
	var params = {}, queryString = location.hash.substring(1), regex = /([^&=]+)=([^&]*)/g, m;
	while (m = regex.exec(queryString)) {
		params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
	}

	// And send the token over to the server
	var req = new XMLHttpRequest();
	// consider using POST so query isn't logged
	req.open('GET', 'http://' + window.location.host + '/google/catchtoken?'
			+ queryString, true);

	req.onreadystatechange = function(e) {
		if (req.readyState == 4) {
			if (req.status == 200) {
				window.location = params['state']
			} else if (req.status == 400) {
				alert('There was an error processing the token.')
			} else {
				alert('something else other than 200 was returned')
			}
		}
	};
	req.send(null);
// ]]></script>

7) As you would notice, we are sending back details to the server on the URL /google/catchtoken If you look back at the dispatch rules, the dispatch for
/google/catchtoken would call the processCallBack method.

 case req @ Req("google" :: "catchtoken" :: Nil, _, GetRequest) =>
      () => processCallBack(req)

8) The processCallBack method does the following

/**
   * Call back from Google post authentication.
   * Login the authenticated user or 'create and login' new user.
   */
  def processCallBack(req: Req): Box[LiftResponse] = {

    // fetch user info object form Google
    val userInfo = validateTokenAndFetchUser(req)

    // process the obtained user information
    createOrLoginUser(userInfo)
    S.redirectTo("/")
  }

  /**
   * Using Google client libraries to fetch the information.
   * @See http://stackoverflow.com/questions/11328832/how-to-validate-google-oauth2-token-from-java-code
   */
  private def validateTokenAndFetchUser(req: Req) = {
    val transport = new NetHttpTransport()
    val jsonFactory = new JacksonFactory()
    val accessToken = req.param("access_token").open_!
    val refreshToken = req.param("access_token").open_!

    // TODO GoogleAccessProtectedResource is marked as deprecated, need to check the alternate
    val requestInitializer = new GoogleAccessProtectedResource(accessToken, transport, jsonFactory,
      Props.get("google.client.id").openOr(""), Props.get("google.client.secret").openOr(""), refreshToken)

    // set up global Oauth2 instance
    val oauth2 = new Oauth2.Builder(transport, jsonFactory, requestInitializer).setApplicationName("Knoldus").build()

    oauth2.userinfo().get().execute()

  }

Using the GoogleAccessProtectedResource (which is marked deprecated in draft 10, please suggest alternate) we pass on the details to verify the token and get the userinfo object.

8) Once we have the userInfo object, we can extract details from it and validate if this user already exists in the system and just needs to be logged in or does the user need to be created and logged in


  /**
   * If the user exists then login else create user.
   */
  private def createOrLoginUser(userInfo: Userinfo) = {
    User.findByEmail(userInfo.getEmail) match {
      case Full(user) => // needs merging
        User.logUserIn(user, true, true)

      case _ => // new user; send to register page with form pre-filled
        val user = User
        User.id(new ObjectId)
        user.name(userInfo.getName)
        user.email(userInfo.getEmail)
        user.username(userInfo.getEmail)
        user.verified(userInfo.getVerifiedEmail)
        user.save
        User.logUserIn(user, true, true)
    }
  }

9) This would complete the login with Google and the user can access functionality of your webapp.

The gist of the code can be accessed here.

Knoldus is a niche Scala and Enterprise Java consulting company based in New Delhi, India. For any query please contact us at info@knoldus.com or provide your details here

About Vikas Hazrati

Vikas is the Founding Partner @ Knoldus which is a group of software industry veterans who have joined hands to add value to the art of software development. Knoldus does niche Reactive and Big Data product development on Scala, Spark and Functional Java. Knoldus has a strong focus on software craftsmanship which ensures high-quality software development. It partners with the best in the industry like Lightbend (Scala Ecosystem), Databricks (Spark Ecosystem), Confluent (Kafka) and Datastax (Cassandra). To know more, send a mail to hello@knoldus.com or visit www.knoldus.com
This entry was posted in Architecture, Scala, Web and tagged , , , . Bookmark the permalink.

4 Responses to Providing a “Sign-in with Google” functionality using Scala

  1. santo says:

    Regards

    Congratulations on your post, this very interesting
    I want to do some tests with your code
    But I have some questions, I hope you can help me
    In my file, LiftProject.scala, I have the following:

    override def libraryDependencies = Set (
         …………
         …………
         …………
         “com.google.api.client”% “google-api-client”% “1.4.1-beta”
       ) + + Super.libraryDependencies

    but I’m missing the following libraries:

    import
    com.google.api.client.googleapis.auth.oauth2.GoogleBrowserClientRequestUrl
    import com.google.api.services.oauth2.Oauth2
    import com.google.api.client.googleapis.auth.oauth2.draft10.GoogleAccessProtectedResource
    import com.google.api.services.oauth2.model.Userinfo

    Can you tell me the repository, to get the packages that I need, please?

    • Hi Santo, this is the only library that we ended up having as a dependency
      “com.google.apis” % “google-api-services-oauth2” % “v2-rev9-1.7.2-beta”,

      and the resolver was

      resolvers += “Google Api client” at “http://mavenrepo.google-api-java-client.googlecode.com/hg/”

  2. sanxelsanto says:

    Regards

    Congratulations for your post, this very interesting
    I want to do some tests with your code
    But I have some questions, I hope you can help me
    In my file, LiftProject.scala, I have the following:
    override def libraryDependencies = Set (
         …………
         …………
         …………
         “com.google.api.client”% “google-api-client”% “1.4.1-beta”
       ) + + Super.libraryDependencies

    but I’m missing the following libraries:

    import
    com.google.api.client.googleapis.auth.oauth2.GoogleBrowserClientRequestUrl
    import com.google.api.services.oauth2.Oauth2
    import com.google.api.client.googleapis.auth.oauth2.draft10.GoogleAccessProtectedResource
    import com.google.api.services.oauth2.model.Userinfo

    Can you tell me the repository, to get the packages that I need, please?

  3. Sebastian says:

    Sternberg told me earlier this month that this is meant to be another effort to use Google+ sign-in “to give users a better experience around the web.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s