http://www.developer.com/http://www.developer.com/security/implement-oauth-based-social-network-logins-in-grails.html
By Piotr Jagielski Grails Spring Security is a great plugin. It allows you to set up authorization for your app in just a few lines in Grails configuration. So, users can register to your website, click on a confirmation link received by e-mail and login with username and password. User experience? Another password to remember or even worse – another public website with the same password provided... So here comes OAuth. As almost everybody on the planet has an account on one of the social networks, why can't we ask this network to authorize the user of our application? A user logs in with his/her credentials to Facebook/Google+/whatever (called OAuth provider) and gives access permission to our application. OAuth provider redirects to your website saying that user has been authorized and gives you some basic information about the user, with no password being stored in your application's database! OK, so we have Facebook and Twitter authentication plugins, but what about other providers? Recently, I've released a website for developers. Do all developers have Facebook accounts? Here are stats from my site: All of these are OAuth providers. But what about the Grails' plugins? After Googling a bit I've found Grails' Inviter plugin by Tomas Lin. It allows you to import your friends' contacts and send them an e-mail invitation to your website. Underneath, the plugin uses the scribe-java library – an open source implementation of OAuth 1/2 protocol. So why not use scribe-java for the authentication of users from various social networks in your application? Scribe is sort of a low-level implementation of OAuth. Here's a list of its main elements: You can look at the Getting Started page for more detail. To implement authentication we need to implement the following: (*) - you can also extract user data, e.g. from a Facebook cookie, but using a separate OAuth request is much more readable and compatible with all providers. Here is a diagram showing the details of communication between browser, out controller (AuthController), scribe library and OAuth provider: OK, so we would write AuthController to handle signin by the social account and callback from OAuth provider: As you can see - besides storing some data into the session, the main logic is implemented in GrailsOAuthService class - a kind of wrapper for the scribe-java api. Here is a sample implementation for Google+: Last but not least, we need to extract the user data by accessing the providers specific URL with a request signed with an access token. URLs are listed in the table below: PROVIDER URL Facebook https://graph.facebook.com/me Google+ https://www.googleapis.com/oauth2/v1/userinfo Twitter http://api.twitter.com/1/account/verify_credentials.json LinkedIn http://api.linkedin.com/v1/people/~ GitHub https://github.com/api/v2/json/user/show The structure of the returned user profile is also provider specific - mostly returned by some JSON object. Here's example code for Google+: The last part is injecting the principal object into Spring Security. Suppose we use the standard grails-spring-security model classes to store user data, this is an example code: And we have fully authenticated UserDetails object with @Secured annotation compatible authorities. The Grails project with all source code is hosted at GitHub: https://github.com/TouK/grails-oauth-scribe-example. There is also a working demo on CloundFoundry: http://grails-oauth-scribe-example.cloudfoundry.com/ Enjoy! (This article is based on a blog post on http://grailsblog.pl and http://touk.pl/blog)
Implement OAuth-based Social Network Logins in Grails
August 2, 2012
class AuthController {
SpringSecuritySigninService springSecuritySigninService
def signin = {
GrailsOAuthService service = resolveService(params.provider)
if (!service) {
redirect(url: '/')
}
session["${params.provider}_originalUrl"] = params.originalUrl
def callbackParams = [provider: params.provider]
def callback = "${createLink(action: 'callback', absolute: 'true', params: callbackParams)}"
def authInfo = service.getAuthInfo(callback)
session["${params.provider}_authInfo"] = authInfo
redirect(url: authInfo.authUrl)
}
def callback = {
GrailsOAuthService service = resolveService(params.provider)
if (!service) {
redirect(url: '/')
}
AuthInfo authInfo = session["${params.provider}_authInfo"]
def requestToken = authInfo.requestToken
def accessToken = service.getAccessToken(authInfo.service, params, requestToken)
session["${params.provider}_authToken"] = accessToken
def profile = service.getProfile(authInfo.service, accessToken)
session["${params.provider}_profile"] = profile
def uid = profile.uid
User user = User.findByOauthIdAndOauthProvider(uid, params.provider)
if (user) {
springSecuritySigninService.signIn(user)
redirect(uri: (session["${params.provider}_originalUrl"] ?: '/') - request.contextPath)
} else {
redirect(controller: 'user', action: 'registerOAuth', params: params)
}
}
private def resolveService(provider) {
def serviceName = "${provider as String}AuthService"
grailsApplication.mainContext.getBean(serviceName)
}
}
class GoogleAuthService extends GrailsOAuthService {
@Override
OAuthService createOAuthService(String callbackUrl) {
def builder = createServiceBuilder(GoogleApi20,
grailsApplication.config.auth.google.key as String,
grailsApplication.config.auth.google.secret as String,
callbackUrl)
return builder.grantType(OAuthConstants.AUTHORIZATION_CODE)
.scope('https://www.googleapis.com/auth/userinfo.profile
https://www.googleapis.com/auth/userinfo.email')
.build()
}
AuthInfo getAuthInfo(String callbackUrl) {
OAuthService authService = createOAuthService(callbackUrl)
new AuthInfo(authUrl: authService.getAuthorizationUrl(null),
service: authService)
}
Token getAccessToken(OAuthService authService, Map params, Token
requestToken) {
Verifier verifier = new Verifier(params.code)
authService.getAccessToken(requestToken, verifier)
}
}
public abstract class GrailsOAuthService {
static transactional = false
OAuthService oauthService
def grailsApplication
@Override
ServiceBuilder createServiceBuilder(Class provider, String apiKey,
String secretKey, String callbackUrl) {
def ServiceBuilder builder = new
ServiceBuilder().provider(provider)
.apiKey(apiKey).apiSecret(secretKey)
.callback(callbackUrl)
return builder
}
abstract AuthInfo getAuthInfo(String callbackUrl)
abstract Token getAccessToken(OAuthService authService, Map params,
Token requestToken)
abstract OAuthProfile getProfile(OAuthService authService, Token
accessToken)
}
class AuthInfo {
OAuthService service
String authUrl
Token requestToken
}
class AuthController {
SpringSecuritySigninService springSecuritySigninService
def signin = {
GrailsOAuthService service =
resolveService(params.provider)
if (!service) {
redirect(url: '/')
}
session["${params.provider}_originalUrl"] =
params.originalUrl
def callbackParams = [provider: params.provider]
def callback = "${createLink(action: 'callback',
absolute: 'true', params: callbackParams)}"
def authInfo = service.getAuthInfo(callback)
session["${params.provider}_authInfo"] =
authInfo
redirect(url: authInfo.authUrl)
}
def callback = {
GrailsOAuthService service =
resolveService(params.provider)
if (!service) {
redirect(url: '/')
}
AuthInfo authInfo =
session["${params.provider}_authInfo"]
def requestToken = authInfo.requestToken
def accessToken = service.getAccessToken(authInfo.service,
params, requestToken)
session["${params.provider}_authToken"] =
accessToken
def profile = service.getProfile(authInfo.service,
accessToken)
session["${params.provider}_profile"] = profile
def uid = profile.uid
User user = User.findByOauthIdAndOauthProvider(uid,
params.provider)
if (user) {
springSecuritySigninService.signIn(user)
redirect(uri: (session["${params.provider}_originalUrl"]
?: '/') - request.contextPath)
} else {
redirect(controller: 'user', action: 'registerOAuth',
params: params)
}
}
private def resolveService(provider) {
def serviceName = "${provider as
String}AuthService"
grailsApplication.mainContext.getBean(serviceName)
}
}
OAuthProfile getProfile(OAuthService authService, Token
accessToken) {
OAuthRequest request = new OAuthRequest(Verb.GET,
'https://www.googleapis.com/oauth2/v1/userinfo')
authService.signRequest(accessToken, request)
def response = request.send()
def user = JSON.parse(response.body)
def login =
"${user.given_name}.${user.family_name}".toLowerCase()
new OAuthProfile(username: login, email: user.email, uid:
user.id, picture: user.picture)
}
About the Author
Piotr Jagielski is a Lead Developer at TouK.