javaplayframeworkdeadbolt

Play Framework and Deadbolt redirect onAuthFailure


I have implemented my own Authenticator from Play Framework and DeadboltHandler from Deadbolt.

Using the methods onUnauthorized respective onAuthFailure I can send users that are not logged in to the "login page" instead of the actual page they are trying to access.

However, instead of sending a user directly to the "login page", I want to specify what page the user should be sent to depending on which page the user tries to reach. For example, if the user tries to access /settings the user should be redirected to the login page. If the user tries to access /player/1 the user should be redirected to another page, say, "create user" page.

I was hoping that there is some smart way to do this with annotations, something like: @someannotation(redirect = route/id) so I can redirect to the relevant route if the user is not logged in, else to the standard "login page".

Any one got any ideas?

Code snippet example for controller and route method:

@Security.Authenticated(Secured.class)
@SubjectPresent(content = "createuser")
@DeferredDeadbolt
public class Settings extends Controller {

    @SubjectPresent(content = "login")
    @CustomRestrict(value = { @RoleGroup({ UserRole.player}), @RoleGroup(UserRole.server_owner) })
    public static Result settings() {

Code snippet example for DeadboltHandler onAuthFailure:

@Override
    public F.Promise<Result> onAuthFailure(Http.Context context, String content) {
        return F.Promise.promise(new F.Function0<Result>() {
            @Override
            public Result apply() throws Throwable {
                System.out.println(content);

Solution

  • There are a couple of different ways you can do this.

    Approach 1: Repurpose the content value

    In this approach, you can use the content value of the constraint annotations to give a hint to the handler. You can use a class-level constraint to define the default redirect, e.g. go to the login page, and method-level constraints to override the default redirect. All constraints have the content value, I'm just using SubjectPresent as an example; you can also mix constraints, e.g. have SubjectPresent at the class level and Restrict at the method level.

    @SubjectPresent(content = "login")
    public class FooController extends Controller {
    
        public Result settings() {
            // ...
        }
    
        public Result somethingElse() {
            // ...
        }
    
        @SubjectPresent(content = "create-user")
        public Result viewUser() {
            // ...
        }
    }
    

    In your DeadboltHandler implementation, you would then need a test on the content:

    public CompletionStage<Result> onAuthFailure(final Http.Context context,
                                                 final Optional<String> content) {
        return CompletableFuture.supplyAsync(() -> content.map(redirectKey -> {
            final Result result;
            if ("login".equals(redirectKey)) {
                result = [redirect to login action]
            }
            else if ("create-user".equals(redirectKey)) {
                result = [redirect to create user action]
            } else {
                result = [redirect to default authorization failure action]
            }
        }).orElseGet(() -> [redirect to default authorization failure action]), executor);
    }
    

    Approach 2: Use the ROUTE_PATTERN tag

    Instead of specifying keys in the constraint annotations, you can instead use the route specified in the request to determine the requested action.

    public CompletionStage<Result> onAuthFailure(final Http.Context context,
                                                 final Optional<String> content) {
        final String route = requestHeader.tags().get(Router.Tags.ROUTE_PATTERN);
        // examine the route and work out what you want to do
    }