Search code examples
javaauthenticationplayframeworkplayframework-2.5deadbolt-2

Restricting Access to method in Play Framework with Authorization - Java


I am having trouble grasping the idea of authorization in PlayFramework (version 2.5). My situation is I have a REST API method getUser and I want to restrict its access by performing authorization with a token that is coming in custom request header named "X-Authorization". Now my controller code looks like that:

package controllers;

import models.User;
import org.bson.types.ObjectId;
import play.mvc.*;
import org.json.simple.*;
import views.html.*;

public class ApiController extends Controller {

    public Result getUser(String userId) {

        User user = User.findById(new ObjectId(userId));
        JSONObject userG = new JSONObject();

        //Some code to append data to userG before return
        return ok(userG.toJSONString());
    }
}

The route URL is defined like this:

GET    /api/user/:id    controllers.ApiController.getUser(id)

Option 1 could be to check the Authorization token inside the method getUser and also check for other credentials but I want to restrict access before even it get calls getUser method. As in future I will be adding more method calls to this REST API. So I will be reusing the same authorization to those future REST APIs as well.

I found there is authorization available in Play Framework which I am not able to understand. I tried to implement Authorization by extending class Security.Authenticator and overriding methods getUserName and onUnauthorized like this:

package controllers;

import models.Site;
import models.User;
import org.json.simple.JSONObject;
import play.mvc.Http.Context;
import play.mvc.Result;
import play.mvc.Security;

public class Secured extends Security.Authenticator {
    @Override
    public String getUsername(Context ctx) {
        String auth_key = ctx.request().getHeader("X-Authorization");
        Site site = Site.fineByAccessKey(auth_key);

        if (site != null && auth_key.equals(site.access_key)) {
            return auth_key;
        } else {
            return null;
        }
    }

    @Override
    public Result onUnauthorized(Context ctx) {

        JSONObject errorAuth = new JSONObject();
        errorAuth.put("status", "error");
        errorAuth.put("msg", "You are not authorized to access the API");

        return unauthorized(errorAuth.toJSONString());
    }
}

Then I've appended the annotation to the getUser method with @Security.Authenticated(Secured.class). It works fine and returns unauthorized error. But now I am not sure if that is the preferred way. I feel this is not the right way to do it as the name of the override of function getUsername suggests that too. I am not checking for any username in session or cookie rather only the token present in the header of request.

Also I know there is a module named Deadbolt which is used for authorization but I read its documents and I am not able to integrate it. It was relatively complex integration for a beginner like me. I was confused about how to use it. I thought about using SubjectPresent controller authorization but still I was not able to implement it successfully.

In the end what do you guys suggest that should I use Security.Authenticator the way I have implemented? Or do you suggest that I go to my first option that is checking authorization inside getUser method? Or Anyone can tell me how to implement Deadbolt in my scenario?


Solution

  • You are mixing Authorization and Authentication.

    Here is a good thread: Authentication versus Authorization

    I like this answer:

    Authentication = login + password (who you are)

    Authorization = permissions (what you are allowed to do)

    Authentication == Authorization (excluding anonymous user) if you allow doing something for all users that you know (i.e. Authenticated users)

    The main goal of Deadbolt is Authorization (already Authenticated users). Your main goal is Authentication.

    I would advise you to use Pac4J, it Authentication library not only for Play, and it has versions as for Java as for Scala. There is a good sample project: https://github.com/pac4j/play-pac4j-java-demo

    I use this library myself in my projects and the task

    As in future i will be adding more method calls to this REST api. So i will be reusing the same authorization to those future REST apis as well.

    I solve as easy as just add the configuration in the 'application.conf`:

    pac4j.security {
      rules = [
        {"/admin/.*" = {
          authorizers = "ADMIN"
          clients = "FormClient"
        }}
      ]
    }
    

    Just do not forget to add Security filter. This feature present in the example project, so just clone and try.

    Another example form the official page:

    pac4j.security.rules = [
      # Admin pages need a special authorizer and do not support login via Twitter.
      {"/admin/.*" = {
        authorizers = "admin"
        clients = "FormClient"
      }}
      # Rules for the REST services. These don't specify a client and will return 401
      # when not authenticated.
      {"/restservices/.*" = {
        authorizers = "_authenticated_"
      }}
      # The login page needs to be publicly accessible.
      {"/login.html" = {
        authorizers = "_anonymous_"
      }}
      # 'Catch all' rule to make sure the whole application stays secure.
      {".*" = {
        authorizers = "_authenticated_"
        clients = "FormClient,TwitterClient"
      }}
    ]