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).

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).

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 ALL filter for the protected path expression with the CSRFHandler.

// add a CSRF token generator and validator
ALL("/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>