November 22, 2014
Hot Topics:

Implement OAuth-based Social Network Logins in Grails

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:

  • Google+  52%
  • Facebook 17%
  • Twitter:     16%
  • GitHub:     10%
  • LinkedIn   5%

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:

  • ServiceBuilder - an abstract factory that lets you build service for a concrete OAuth provider, giving your app apiKey and apiSecret.
  • Verifier - OAuth verifier code sent by the OAuth provider, which is used to create the access token.
  • OAuthRequest - request to OAuth provider, which is signed by the access token.

You can look at the Getting Started page for more detail.

To implement authentication we need to implement the following:

  • With ServiceBuilder create and redirect to the authorization URL to OAuth provider.
  • Implement callback action, which extracts an access token from a verifier code given by the OAuth provider.
  • Create and sign an OAuth request to receive user information (*).
  • Inject user principal into the Spring Security context.

(*) - 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:

Grails_OAuth

OK, so we would write AuthController to handle signin by the social account and callback from OAuth provider:

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



}

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+:

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

}

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+:

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)

    }



}

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:

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)

    }

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)

About the Author

Piotr JagielskiPiotr Jagielski is a Lead Developer at TouK.


Tags: Java, social network, OAuth, security




Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Rocket Fuel