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

Knoldus Inc.
Knoldus - Technical Insights
4 min readAug 2, 2012

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

[code language=”text”]
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
[/code]

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

[code language=”scala”]
/**
* 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);

}
[/code]

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.

[code language=”scala”]
LiftRules.dispatch.append(GoogleDispatcher.matcher)
[/code]

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

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

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

[code language=”scala”]
//Google menu
val googleCallback = MenuLoc(Menu.i("GoogleCallback") / "google" / "callback" >> Hidden)
[/code]

The javascript for accessing the access_token is

[code language=”javascript”]
<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>
[/code]

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.

[code language=”scala”]
case req @ Req("google" :: "catchtoken" :: Nil, _, GetRequest) =>
() => processCallBack(req)
[/code]

8) The processCallBack method does the following

[code language=”scala”]
/**
* 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()

}
[/code]

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

[code language=”scala”]

/**
* 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)
}
}

[/code]

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

--

--

Knoldus Inc.
Knoldus - Technical Insights

Group of smart Engineers with a Product mindset who partner with your business to drive competitive advantage | www.knoldus.com