Using Native Mobile Facebook Authentication for a REST Grails 3 application with Spring Security Rest

If you only care about the code, check out the full Gist on GitHub: https://gist.github.com/OsaSoft/d805672062deb0da277267ca4d86b335


Sometimes while working on either job-related, study-related or hobby-related projects, I come onto problems that not even Stack Overflow seems to be able to solve for me, or the answer I find needs a little more explanation and elaboration. For problems like these I have decided a few times in the past to post some Gists on GitHub, hoping to help whoever might stumble upon my code, searching for a solution to the same, or a very similar problem. However, now I feel that Gists are not always fully self explanatory, and so have decided to, over time, write some more detailed posts about my adventures in Codeland. Here’s my first attempt at doing so.

I have been in love with Grails since I first got my hands on doing support work on an old Grails 1.3.7 project at my job. As a framework it was elegant with no need of extensive piles of xml configurations just to get a Hello World working, the structure was well organised and along with features coming from the use of the Groovy language it just blew me away. That is why ever since, whenever I had full control over the technologies I’d use in a new project, Grails has always been my first choice. For these reasons too, when I started working on a project for a student app at my university, Grails 3 with the REST profile was a no-brainer. The app, written in React native, would call the backend API and all would be good, right?


Well here is the problem I stumbled upon: In order to secure access to the backend’s API endpoints, I decided to use Spring Security, along with the amazing Spring Security Rest plugin by Álvaro Sánchez-Mariscal. However, since the frontend would be a smartphone app, it would be great if we could use the native Facebook login, where the app takes an access token from the Facebook app and uses that to authenticate.

However, the Spring Security Rest plugin only supports Facebook login using oAuth, which is more fit to be used with a JS frontend and uses redirect chains. So, what can be done to use Facebook’s native mobile login, which creates a Facebook Access Token?

The basic idea goes like this:

  1. Mobile app uses Facebook’s native login and gets Facebook Access Token
  2. Mobile app passes the Facebook Access Token to the backend app.
  3. Backend app uses Facebook Access Token to pull user information from Facebook’s API and creates/logs in user based on it.
  4. Backend app logs in the user internally and generates an oAuth acess_token, refresh_token pair
  5. Backend app sends oAuth tokens back to the mobile app.
  6. Mobile app uses oAuth tokens to authenticate with backend app on any subsequent API calls.

Let’s start with the relevant parts in our User class (the one generated by Spring Security). For convenience, let’s add a comma separated list of fields we want to access from the Facebook user (mind that in order to get them, the relevant permissions must be requested and granted in the mobile part of the login):

static final facebookFields = "id,email,first_name,last_name,picture"

Here we’re just going to get some very basic information, you can see the full list of available fields over at Facebook’s docs (https://developers.facebook.com/docs/graph-api/reference/v2.2/user)

Next, we want to be able to create a User object:

static User facebookLogin(FacebookUserInfo fbInfo) {
def user = findByUsername(fbInfo.id)
boolean isNew = false
if (!user) { //user doesnt exist yet
isNew = true
user = new User(username: fbInfo.id, password: RandomStringUtils.randomAlphanumeric(16))

}
user.with { //or other fields you want
firstname = fbInfo.firstname
lastname = fbInfo.lastname
fbPictureUrl = fbInfo.profilePictureUrl
}

if (user.isDirty() || isNew) { //if any fields changed or is a new user, save
user.save(flush: true)
}

if (!user.hasErrors()) { //give user roles if user saved properly
def userRole = Role.findByAuthority("ROLE_USER")
def facebookRole = Role.findByAuthority("ROLE_FACEBOOK")

if (!user.authorities.contains(userRole)) UserRole.create(user, userRole, true)
if (!user.authorities.contains(facebookRole)) UserRole.create(user, facebookRole, true)
}

user
}

So, what’s going on here: We pass this method a FacebookUserInfo, which is just a data container POGO with the fields we want, in this case id,email,firstname,lastname and profilePictureUrl. We try to find a user with the same Facebook id and create a new one with the passed data in case we cant find an existing user. Then we just update the data, as things such as name, picture and others might change between logins and we want to keep our end as up to date as possible. Finally we add all the roles the user is supposed to have, if they don’t have them yet.

Okay, so far so good.


Next let’s create a FacebookService that will take care of communicating with Facebook’s API. We add values for Facebook’s API url and our app key to load from config:

@Value(‘${facebook.graph.api.url}’) String fbApiUrl @Value(‘${facebook.graph.api.key}’) String fbAppId

Add in a RESTClient and inilize it to Facebook’s API url

RESTClient client

@PostConstruct
void init() {
client = new RESTClient(fbApiUrl)
}

Next, we add themethod that will call the API to get all the user information:

def getUserInfo(String fbAcessToken) {
def resp = client.get(path: "me", query: [access_token: fbAcessToken, fields: User.facebookFields])
if (resp.status != 200) {
log.error "Error getting user information! $resp.status: $resp.data"
} else {
resp.data
}
}

Note we’re getting User.facebookFields here. I take them from the static field we defined in our User class, since I want the fields be same for all situations, but you could pull them out of a dabase, config file, etc.

We also want another method here, to check if the access token we got is for our app:

boolean isTokenForApp(String fbAccessToken) {
def resp = client.get(path: "app", query: [access_token: fbAccessToken])

resp.status == 200 && resp.data.id == fbAppId
}

Ok, so we’ve got the foundation done. We can get user information from Facebook based on a valid access token and we can create a User based on the info we get. Now to put it all together under an endpoint that our app can call. Let’s put this under a FacebookController

@Secured("permitAll")
class FacebookController {
static responseFormats = ['json']
static allowedMethods = [auth: "POST"]

def restAuthenticationEventPublisher
def userDetailsService
def tokenGenerator
def tokenStorageService
def accessTokenJsonRenderer

def facebookService

Let’s quickly go through the beans we want injected here.

  • restAuthenticationEventPublisher will pass our authentication token to all relevant parts of the authentication system, so that our app will be able to authenticate with our own oAuth tokens
  • userDetailsService will get us the UserDetails object we will need to create our tokens
  • tokenGenerator will generate our tokens
  • tokenStorageService will store our tokens
  • accessTokenJsonRenderer will, you guessed it, render our tokens as a json
  • facebookService should feel familiar

Now for the authentication method itself:

def auth() {
def fbToken = request.JSON?.fb_access_token
if (!facebookService.isTokenForApp(fbToken)) {
respond([error: "Incorrect app access token"], status: HttpStatus.UNAUTHORIZED)
return
}

def userInfo = facebookService.getUserInfo(fbToken)

if (!userInfo?.id) { //something went wrong
respond([error: userInfo?.error], status: HttpStatus.UNAUTHORIZED)
return
}

userInfo = new FacebookUserInfo(
id: userInfo.id,
email: userInfo.email,
firstname: userInfo.first_name,
lastname: userInfo.last_name,
profilePictureUrl: userInfo.picture?.data?.url
)

User user = User.facebookLogin(userInfo)

if (!user || user.hasErrors()) {
respond([error: [message: "persist error", errors: user.errors]], status: HttpStatus.INTERNAL_SERVER_ERROR)
return
}

So far so good. We get the Facebook Access Token from the json payload in the request, check whether it is a valid token for our app and get user info from Facebook and based on it get an existing or new User.

Now all we need to do is get the rest of the system know we’ve authenticated a user, create some authentications token for them and register those. Finally, we’ll send them back to our mobile app:

def userDetails = userDetailsService.loadUserByUsername(user.username)

AccessToken accessToken = tokenGenerator.generateAccessToken(userDetails)
tokenStorageService.storeToken(accessToken.accessToken, userDetails)

restAuthenticationEventPublisher.publishTokenCreation(accessToken)

SecurityContextHolder.context.setAuthentication(accessToken)

render contentType: 'application/json', encoding: 'UTF-8', text: accessTokenJsonRenderer.generateJson(accessToken)
}

Now for the final touch. Since we are using the REST profile and a unconventional url for the authentication endpoint, we have to set it up in UrlMappings

class UrlMappings {

static mappings = {
post "/facebook/auth(.$format)?"(controller: "facebook", action: "auth")
}
}

And that’s it! Now we can use Facebook’s native mobil login in our Grails 3 REST application.

I hope this helps whoever finds it. If you find any mistakes or possible improvements, please let me know. You can find me on Twitter as Osa_Soft or on GitHub as OsaSoft.