OAuth Login Solution(2)Scala and Callback URLs
OauthService.scala Codes to Generate AuthURL/Fetch RefreshToken/Fetch Profile
package services
import java.util.Arrays;
import com.google.api.client.googleapis.auth.oauth2.{GoogleTokenResponse, GoogleAuthorizationCodeRequestUrl}
import com.jobs2careers.util.IncludeLogger
import models.{ AccountProfileResponse}
import play.api.libs.json.Json
import play.api.libs.ws.{WSResponse, WS}
import utils.IncludeOauthConfig
import scala.concurrent.duration._
import scala.concurrent.Await
import scala.concurrent.Future
import play.api.Play.current
import com.google.api.client.auth.oauth2.Credential
import com.google.api.client.auth.oauth2.TokenResponse
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow
import com.google.api.client.http.GenericUrl
import com.google.api.client.http.HttpRequest
import com.google.api.client.http.HttpRequestFactory
import com.google.api.client.http.HttpTransport
import com.google.api.client.http.javanet.NetHttpTransport
import com.google.api.client.json.JsonFactory
import com.google.api.client.json.jackson2.JacksonFactory
import com.google.api.services.gmail.GmailScopes
object OauthService extends IncludeOauthConfig with IncludeLogger{
implicit val accountProfileResponseWrites = Json.writes[AccountProfileResponse]
implicit val accountProfileResponseReads = Json.reads[AccountProfileResponse]
val callbackURL = OAUTH_CALLBACK_URI
val USER_INFO_URL = "https://www.googleapis.com/auth/userinfo.profile"
val EMAIL_INFO_URL = "https://www.googleapis.com/auth/userinfo.email"
val USER_PROFILE_URL = "https://www.googleapis.com/oauth2/v1/userinfo"
val JSON_FACTORY:JsonFactory = new JacksonFactory()
val HTTP_TRANSPORT:HttpTransport = new NetHttpTransport()
def generateAuthURL(accountCode:String):String = {
val flow:GoogleAuthorizationCodeFlow = new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET,
Arrays.asList(GmailScopes.MAIL_GOOGLE_COM, GmailScopes.GMAIL_READONLY, USER_INFO_URL, EMAIL_INFO_URL))
.setAccessType("offline")
.setApprovalPrompt("force")
.build()
val url:GoogleAuthorizationCodeRequestUrl = flow.newAuthorizationUrl()
val url_str = url.setRedirectUri(callbackURL).setState(accountCode).build()
//url_str = url_str + "&
[email protected]";
return url_str
}
def fetchRefreshToken(accessToken:String):String = {
val flow:GoogleAuthorizationCodeFlow = new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET,
Arrays.asList(GmailScopes.MAIL_GOOGLE_COM, GmailScopes.GMAIL_READONLY, USER_INFO_URL, EMAIL_INFO_URL))
.setAccessType("offline")
.setApprovalPrompt("force")
.build()
val response:GoogleTokenResponse = flow.newTokenRequest(accessToken)
.setRedirectUri(callbackURL).execute()
val refreshToken = response.getRefreshToken()
logger.debug("Refresh token system get = " + refreshToken)
return refreshToken
}
def fetchEmail(refreshToken:String): Option[String] = {
val flow:GoogleAuthorizationCodeFlow = new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET,
Arrays.asList(GmailScopes.MAIL_GOOGLE_COM, GmailScopes.GMAIL_READONLY, USER_INFO_URL, EMAIL_INFO_URL))
.setAccessType("offline")
.setApprovalPrompt("force")
.build()
logger.debug("Refresh Token = " + refreshToken)
val tokenResponse:TokenResponse = new TokenResponse()
tokenResponse.setRefreshToken(refreshToken)
val credential:Credential = flow.createAndStoreCredential(tokenResponse, null)
val requestFactory:HttpRequestFactory = HTTP_TRANSPORT.createRequestFactory(credential)
// Make an authenticated request
val genericUrl:GenericUrl = new GenericUrl(USER_INFO_URL)
val request:HttpRequest = requestFactory.buildGetRequest(genericUrl)
request.getHeaders().setContentType("application/json")
request.execute()
val accessToken = credential.getAccessToken()
logger.debug("System get the new Access Token = " + accessToken)
// {
// "id": "114122167329329897934",
// "email": “
[email protected]",
// "verified_email": true,
// "name": “Sillycat Mobile",
// "given_name": “sillycat",
// "family_name": "Mobile",
// "picture": "https://lh3.googleusercontent.com/-XdUIqdMkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/photo.jpg",
// "locale": "en",
// "hd": “gmail.com"
// }
val url = USER_PROFILE_URL + "?alt=json&access_token=" + accessToken
logger.debug("URL = " + url)
val future:Future[WSResponse] = WS.url(url).get()
val result = Await.result(future, 20 seconds)
logger.debug("Fetching the profile response = " + result.json + " accessToken = " + accessToken)
val email = result.json.asOpt[AccountProfileResponse].map { profile =>
logger.info("Fetching the profile information = " + profile)
profile.email
}
return email
}
}
The Callback URL who handle the Callback
def addSource = Action { request =>
logger.debug("The request body = " + request.body)
val accessToken = request.getQueryString("code").getOrElse("")
val accountCode = request.getQueryString("state").getOrElse("")
logger.debug("First access token system get = " + accessToken)
if(accessToken.isEmpty || accountCode.isEmpty){
val warn_msg = "Callback add source fail, request params accountCode = " + accountCode
logger.warn(warn_msg)
BadRequest(Json.obj("status" -> "Fail", "message" -> warn_msg))
}else{
val refreshToken = OauthService.fetchRefreshToken(accessToken)
val email = OauthService.fetchEmail(refreshToken).getOrElse("")
if(email.isEmpty){
val warn_msg = "Callback add source fail, fail to fetch email with refreshToken."
logger.warn(warn_msg)
BadRequest(Json.obj("status" -> "Fail", "message" -> warn_msg))
}else{
//call actor to ContextIO to add source
sillycatIOthrottler ! AccountSourceMessage(
accountCode, //accountCode
email, //email system fetch from profile
email, //username, same as email for google
refreshToken //refreshToken
)
Ok(Json.obj("status" -> "OK"))
}
}
}
The dependencies in build.sbt
"com.google.api-client" % "google-api-client" % "1.20.0",
"com.google.oauth-client" % "google-oauth-client" % "1.20.0",
"com.google.apis" % "google-api-services-gmail" % "v1-rev36-1.20.0",
References:
http://sillycat.iteye.com/blog/2274258