Fork me on GitHub

You can secure your application or only some parts using a filter (a RouteHandler). Remember that routes are matched in the order they are added/defined so put your security filter in front of regular routes (regular routes are endpoint routes for a request).

Simple

I will show you a simple implementation for a security filter.

// authentication filter
GET("/contact.*", routeContext -> {
    if (routeContext.getSession("username") == null) {
        routeContext.setSession("originalDestination", routeContext.getRequest().getContextUriWithQuery());
        routeContext.redirect("/login");
    } else {
        routeContext.next();
    }
});

// show contacts page
GET("/contacts", routeContext -> routeContext.render("contacts"));

// show contact page for the contact with id specified as path parameter
GET("/contact/{id}", routeContext -> {
    int id = routeContext.getParameter("id").toInt(0);
    Contact contact = (id > 0) ? contactService.getContact(id) : new Contact();
    routeContext.setLocal("contact", contact);

    routeContext.render("contact")
});

// show login page
GET("/login", routeContext -> response.render("login"));

// process login submit
POST("/login", routeContext -> {
    String username = routeContext.getParameter("username").toString();
    String password = routeContext.getParameter("password").toString();
    if (authenticate(username, password)) {
        String originalDestination = routeContext.removeSession("originalDestination");
        routeContext.resetSession();

        routeContext.setSession("username", username);
        routeContext.redirect(originalDestination != null ? originalDestination : "/contacts");
    } else {
        routeContext.flashError("Authentication failed");
        routeContext.redirect("/login");
    }
});

// a dump implementation for authenticate method
private boolean authenticate(String username, String password) {
    return !username.isEmpty() && !password.isEmpty();
}

The content for login (freemarker engine) can be:

<html>
    <head>
        <title>Login</title>
    </head>
    <body>
        <#if flash.hasError()>
            ${flash.getError()}
        </#if>

        <form method="post" action="/login">
            <input placeholder="Username" name="username">
            <input placeholder="Password" name="password" type="password">
            <input type="submit" value="Login">
        </form>
    </body>
</html>

In above code I want to protect all pages (contacts, contact) for the Contact domain entity.
The authentication tests to see if the ‘username’ attribute is present in the session object. If ‘username’ is present than call the regular route with routeContext.next() else redirect to the login page.
I added originalDestination attribute because after authentication process I want to continue with the original destination (original url).

PAC4J integration

Since version 1.7.0, Pippo comes with a new module, pippo-pac4j, that add PAC4J support in Pippo. In few words, PAC4J is a Java security engine that supports most authentication mechanism (OAuth, SAML, CAS, LDAP, SQL, …) and authorization mechanism (Roles/permissions, Anonymous/remember me/(fully) authenticated, CORS, CSRF, …).
The integration with PAC4J in Pippo is easy and straightforward.
The pippo-pac4j module comes with three components (routes/filters):

  • Pac4jSecurityHandler (before filter) that protects an URL
  • Pac4jLogoutHandler that handles the (application + identity provider) logout process
  • Pac4jCallbackHandler that finishes the login process for an indirect client

Below, I present you some snippet code to have a more clear image about this integration:

public class DemoApplication extends Application
{

    @Override
    protected void onInit()
    {
       // PAC4J config
        Config config = getPac4jConfig();

        // authentication filter
        ANY(securePaths(), new Pac4jSecurityHandler(config, "FormClient"));

        // login
        GET("/login", routeContext -> {
            String error = routeContext.getParameter("error").toString();
            if (error != null)
            {
                // and error from PAC4J authentication
                routeContext.flashError(getMessages().get("login.invalid", routeContext));
                routeContext.redirect("login");
            }
            else
            {
                // render "login" template (it's a form)
                routeContext.render("login");
            }
        });
        Pac4jCallbackHandler callbackHandler = new Pac4jCallbackHandler(config);
        callbackHandler.setRenewSession(false);
        POST("/login", callbackHandler);

        // logout
        Pac4jLogoutHandler logoutHandler = new Pac4jLogoutHandler(config, "/");
        logoutHandler.setDestroySession(true);
        GET("/logout", logoutHandler);
        
        // OTHER BUSSINES ROUTES    
    }
    
    @SuppressWarnings("unchecked")
    private static Optional<CommonProfile> getUserProfile(RouteContext routeContext)
    {
        PippoWebContext webContext = new PippoWebContext(routeContext);
        ProfileManager manager = new ProfileManager(webContext);

        return manager.get(true);
    }
    
}    

First of all you must create a PAC4J’s config. A possible solution is to create a static getPac4jConfig method with an implementation like:

private static Config getPac4jConfig() {
    // create a client (form based)
    FormClient formClient = new FormClient("http://localhost:8888/login", myUsernamePasswordAuthenticator);
    
    // create the config object
    Config config = new Config("http://localhost:8888/login", formClient);
    
    // set http action handler
    config.setHttpActionAdapter(PippoNopHttpActionAdapter.INSTANCE);
    
    return config;
}

Other solution is to define all settings related to PAC4J in your application.properties and to use SettingsConfigFactory class:

Config config = new SettingsConfigFactory(getPippoSettings()).build();

On GET /login route is rendered the login form. The form contains fields like username and password, an a POST /login action.

If you want to see a more complex example please see the pippo-demo-pac4j module.
A possible integration with LDAP/AP is presented here.

Cross-Site Request Forgery (CSRF) Protection

Pippo includes a simple CSRF handler which will automatically generate a CSRF token on GET requests, if there is no token in the current session, and verify that POST requests include the session’s CSRF token.

Using this handler is straight-forward.

1. Add an ANY filter for the protected path expression with the CSRFHandler.

// add a CSRF token generator and validator
ANY("/books.*", new CSRFHandler());

2. Add a _csrf_token / ${csrfToken} hidden input value on all forms that are POSTed to this protected path expression

<html>
    <body>
        <form method="post" action="/books/5/rename">
            <input type="hidden" name="_csrf_token" value="${csrfToken}">
            <input placeholder="Enter a new book title" name="bookTitle">
            <input type="submit" value="Rename">
        </form>
    </body>
</html>