I have been experimenting with implementing a RESTful API in Grails. Like most APIs some of the methods require user authentication before they are allowed to be performed. There are a number of interesting HTTP based authentication/authorization schemes out there, but the most straight forward is Basic Authentication. Basic Authentication takes a Base64 encoded username:password pair and places it into the “Authorization” http header. The server then decodes the pair and uses them with it’s authentication system. It is not the most secure way to do authentication as your user name and password are basically in plain text, but the risk can be mitigated by using https.
There are a number of java packages and grails plugins that provide Basic Authentication functionality amongst other things. I thought I would walk through doing it manually within Grails, since it is fairly straightforward and provides an example of how grails filters can be used.
The main part of the approach is creating a Filter that will intercept the calls to our api and authenticate the user. In the conf directory of your grails project create a groovy class called SecurityFilters.groovy and insert the following:
class SecurityFilters {
def filters = {
basicAuth(controller:'api', action:'*') {
before = {
def authString = request.getHeader('Authorization')
if(!authString){
redirect('500')
}
def encodedPair = authString - 'Basic '
def decodedPair = new String(new sun.misc.BASE64Decoder().decodeBuffer(encodedPair));
def credentials = decodedPair.split(':')
def user = User.findByNameAndPassword(credentials[0],credentials[1])
if(user){
session.user = user
}
else{
redirect('500')
}
}
}
}
3: Define a filter called basicAuth which will filter on all controllers and all actions. You can change this to be the specific controller for your API as well as specific the actions you want to authenticate.
4: This specifies that we want a before filter, that will occur before the action is triggered.
5: Extract the value of the “Authorization” header, the value for this is “Basic username:password” where the “username:password” part is Base64 encoded.
7-9: If the request doesn’t have the authorization header we want to redirect the request to an error page, grails already has an URL mapping for “500″ that redirects to an error.gsp, this is fine for now, but you probably want to add a 401 Unauthorized error.
You can do the next few lines a number of ways, I have broken it down into steps to make it easier to follow:
11: Use groovy string-fu to get rid of the “Basic ” part of our authString by subtracting from it.
12: Base64 decode the encoded pair. We could have used grails built in codecs
13: Use a little bit more groovy fu using split() to create an array that contains username and password.
14: Query the User model to match the username and password we just extracted. You should have an authentication scheme that doesn’t involve storing your user’s passwords in plaintext.
16-21: If the user exists we store it in the session and the filter passes to the action, if not redirect with an error.
[...] Our final step is to set up an Authentication filter like the one we made for basic authentication . The only difference is that we have a number of controller actions that we want to allow to pass [...]
[...] you want. If you want a simple solution and stick with the HTTP standard you could use Basic auth. Here is a good explaination of how to implement Basic auth for Grails. Another way is too use a cookie to hold authorization [...]
I changed you code in order to log with a login and password in the configuration file.
I also add the WWW-Authenticate in order to respect the standard :
response.addHeader("WWW-Authenticate", "Basic realm=\"Secure Area\"")
class SecurityFilters {
def grailsApplication
def filters = {
basicAuth(controller: 'prospect', action: '*') {
before = {
if(!ValidationUtil.validate(request,response, log, grailsApplication)){
render(text: "User not known.")
return
}
}
}
basicAuth(controller: 'hash', action: '*') {
before = {
if(!ValidationUtil.validate(request,response, log, grailsApplication)){
render(text: "User not known.")
return
}
}
}
}
}
/**
* Basic Authentication
* return false if login failed, and true if login is successful
*
* The HTTP status code and the HTTP header of the response are set as this :
*
* "Authorization" not present in the header of the request :
* - status : 401
* - add hedaer : WWW-Authenticate: Basic realm="Secure Area"
*
* "Authorization" present, but bad login / password
* - status : 403
*
*/
class ValidationUtil{
static validate(def request, def response, def log, def grailsApplication){
try {
def authString = request.getHeader('Authorization')
if (!authString) {
response.status = 401
response.addHeader("WWW-Authenticate", "Basic realm=\"Secure Area\"")
return false
}
def encodedPair = authString - 'Basic '
def decodedPair = new String(new sun.misc.BASE64Decoder().decodeBuffer(encodedPair));
def credentials = decodedPair.split(':')
def login = grailsApplication.config.admin.security.login
def password = grailsApplication.config.admin.security.password
if (login.equals(credentials[0]) && password.equals(credentials[1])) {
return true
} else {
log.warn("User not known")
response.status = 403
return false
}
} catch (Exception e) {
log.warn("User not known", e)
response.status = 403
return false
}
}
}
Thanks for the tip. One small bug in Romains code:
if(!ValidationUtil.validate(request,response, log, grailsApplication)){
render(text: "User not known.")
return
}
}
must read
if(!ValidationUtil.validate(request,response, log, grailsApplication)){
render(text: "User not known.")
return false
}
}
in order to prevent execution of the action if the user is not logged in.