Authorized routes

If you don't want to add authorization constraints directly into your controllers, you can instead apply them to the routes of the application.

To get started, you will need to enable DeadboltRoutePathFilterModule in your application.conf. Note that you also need DeadboltModule.

play {
  modules {
    enabled += be.objectify.deadbolt.java.DeadboltModule
    enabled += be.objectify.deadbolt.java.filters.DeadboltRoutePathFilterModule
  }
}

This module provides two of the three components required for route filtering; DeadboltFilter and FilterConstraints. The third component will be a class you write, extending be.objectify.deadbolt.java.filters.AuthorizedRoutes. This extended class defines the route constraints, and needs to be bound into the dependency injection context.

Take a simple routes file, which has two routes; one is a static route, the other is dynamic. A static route is simply one that does not contain variables.

GET     /profile                    controllers.Application.profile
GET     /view/:foo/:bar             controllers.Application.view(foo: String, bar: String)

Authorization for these routes could be defined as follows - the static route (/profile) only requires that a subject be present, while the dynamic route requires that a subject be present and have a role giving membership of a certain group.

import static be.objectify.deadbolt.java.utils.TemplateUtils.allOfGroup;
import static be.objectify.deadbolt.java.filters.Methods.GET;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Provider;
import be.objectify.deadbolt.java.filters.AuthorizedRoute;
import be.objectify.deadbolt.java.filters.AuthorizedRoutes;
import be.objectify.deadbolt.java.filters.FilterConstraints;
import be.objectify.deadbolt.java.utils.TemplateUtils;

public class MyAuthorizedRoutes extends AuthorizedRoutes {

    @Inject
    public MyAuthorizedRoutes(final Provider<FilterConstraints> filterConstraints) {
        super(filterConstraints);
    }

    @Override
    public List<AuthorizedRoute> routes() {
        return Arrays.asList(new AuthorizedRoute(GET,
                                                 "/profile",
                                                 filterConstraints.subjectPresent()),
                             new AuthorizedRoute(GET,
                                                 "/view/$foo<[^/]+>/$bar<[^/]+>",
                                                 filterConstraints.restrict(allOfGroup("someRole"))));
    }
}

One of the first things that becomes apparent on reading this code is /view/:foo/:bar is suddenly /view/$foo<[^/]+>/$bar<[^/]+>. This is because Play compiles the routes file - however, don't worry! These compiled routes will be displayed if you visit any invalid URL in development mode (I tend to use something like http://localhost:9000/@foo) - this means you can copy and paste the exact route definition straight out of the browser. Note that /profile remains unchanged - this is because it is a static route.

Taking a closer look at a single route authorization, you can see it has four parameters. There are

  • the method. All common methods are defined in be.objectify.deadbolt.java.filters.Methods. The method is simply the HTTP method name as an Optional. There is also Any, which means any method for that path pattern will cause a match.
  • the path pattern.
  • the constraint. All Deadbolt constraints, including composite constraints, can be used. Use the filterConstraints instance to create these constraints.
  • Optionally, the handler to use for this constraint. If you use the constructor that doesn't take a handler, the handler provided by HandlerCache.apply() will be used.

be.objectify.deadbolt.java.filters.FilterConstraints defines all existing Deadbolt constraint types, but if you need to use something else, you need to implement the be.objectify.deadbolt.java.filters.FilterFunction interface.

You now need to provide a binding for AuthorizedRoutes. If you already have a custom module for your other Deadbolt integrations, you can put it in there, otherwise create a new module.

import be.objectify.deadbolt.java.filters.AuthorizedRoutes;
import be.objectify.deadbolt.java.test.security.MyAuthorizedRoutes;
import play.api.Configuration;
import play.api.Environment;
import play.api.inject.Binding;
import play.api.inject.Module;
import scala.collection.Seq;

import javax.inject.Singleton;

/**
 * @author Steve Chaloner ([email protected])
 */
public class CustomDeadboltFilterHook extends Module
{
    @Override
    public Seq<Binding<?>> bindings(final Environment environment,
                                    final Configuration configuration)
    {
        return seq(bind(AuthorizedRoutes.class).to(MyAuthorizedRoutes.class).in(Singleton.class));
    }
}

You will also need to enable this module in application.conf.

Finally, you need to add DeadboltRoutePathFilter in your filter definitions. You can read more about how to use filters in Play at https://playframework.com/documentation/2.5.x/JavaHttpFilters.

import javax.inject.Inject;
import be.objectify.deadbolt.java.filters.DeadboltRoutePathFilter;
import play.http.HttpFilters;
import play.mvc.EssentialFilter;

public class Filters implements HttpFilters {

    // plus any other filters you have
    private final DeadboltRoutePathFilter deadbolt;

    @Inject
    public Filters(final DeadboltRoutePathFilter deadbolt) {
        this.deadbolt = deadbolt;
    }

    @Override
    public EssentialFilter[] filters() {
        return new EssentialFilter[]{deadbolt};
    }
}