Skip to main content

Secured routes

Two helpers for restricting access to a set of routes once an AuthenticationFilter is in place. Both return Response(Status.FORBIDDEN) when the access check fails. To return a 404 instead (to avoid revealing the existence of protected URLs), combine with StatusOverride and ErrorPages.

A browser application usually wants something friendlier than a bare 403 for an anonymous visitor, though - redirectAnonymousToSignIn below is a filter to place in front of securedRoutes or authenticatedRoutes that redirects an anonymous navigational GET to a sign-in page instead.

authenticatedRoutes​

Requires that a Principal is attached to the request.

val handler = authenticatedRoutes(
DashboardHandler(),
LogoutHandler()
)

securedRoutes​

Requires that the request's principal is an instance of P and passes the check predicate.

val handler = securedRoutes<AppPrincipal>(
check = { it.roles.contains("ADMIN") },
route = routes(
"/admin" GET { Response(Status.OK) }
)
)

redirectAnonymousToSignIn​

A filter for the same anonymous-visitor case securedRoutes/authenticatedRoutes answer with Status.FORBIDDEN, but redirecting to a sign-in page instead. Placed in front of one of those two, it turns an anonymous navigational GET into a redirect built by signInUrl from the incoming request - the sign-in page's location and query parameters (a return destination, a sign-up intent, anything else) are entirely up to the application. A Turbo-Frame request (carrying a Turbo-Frame header) gets a turbo-stream redirect instead of a plain one, so the top-level page navigates rather than the frame loading the sign-in page inline. Non-GET requests and requests that already carry a principal pass through to the filter chain that follows.

val handler = redirectAnonymousToSignIn { request -> "/sign-in?destination=${request.pathWithQuery()}" }
.then(
securedRoutes<AppPrincipal>(
check = { it.roles.contains("ADMIN") },
route = routes(
"/admin" GET { Response(Status.OK) }
)
)
)