TL;DR

An extension to the example of using Mojolicious::Plugin::Authentication.

In previous post Mojolicious::Plugin::Authentication example we took a look at an example for Mojolicious::Plugin::Authentication. I mean, it’s in the title - no big deal.

The example suggests that for every method that we want to put behind the authentication check we should do the following:

get '/private' => sub ($c) {
    return $c->redirect_to('/login') unless $c->is_user_authenticated;
    return $c->render(template => 'private');
};

i.e. do the check with is_user_authenticated() and act accordingly (e.g. by redirecting to the /login page).

As you might have already thought at this point, this is both verbose and error prone. What if we forget to add that check?

Using a condition

If you just don’t like the verbosity and you’re fine with getting a Not Found error when the user tries to access a private page (i.e. you don’t want to redirect to the /login page), you can just use a condition:

get '/private' => (authenticated => 1 ) => sub ($c) {
    return $c->render(template => 'private');
};

From the application’s point of view, this route does not even exist if the authentication condition is not fulfilled. For this reason, the /private endpoint will yield a Not Found error to unauthenticated users.

This still does not solve our problem with being error-prone though - we might forget to put the conditions. Or we might be lazy enough to not want to repeat it for all private endpoints.

Using under

It is possible to use under for fun and profit to install an intermediate checkpoint and then place all routes in a sub-tree.

Here is an example:

under '/authenticated' => sub ($c) {
   return 1 if $c->is_user_authenticated;
   $c->redirect_to('/login');
   return 0;
};
get what => sub ($c) {
   return $c->render(template => 'private', page => 'authenticated/what');
};
get ever => sub ($c) {
   return $c->render(template => 'private', page => 'authenticated/ever');
};

The intermediate stop is at /authenticated. Here, we check with is_user_authenticated() and return 1 if we are fine - this signals to the system that we passed this intermediate stop and we can move further in the route selection. Otherwise, we set the redirection to the login page and return 0, to signal that the route analysis should stop here.

In [Mojolicious::Lite][] applications, all routes that come after the under are assumed to be… under it. So the get what => ... route is actually translated into a GET to /authenticated/what and so on. The route analysis will arrive here only if the intermediate stop above was successful, so all routes after the under are safe and will trigger a redirection to the /login page if the user is not authenticated.

There is still a slight space for some information leak anyway. Trying to hit a non-existent route (like /authenticated/whatever) would not yield a redirection to the /login page, but a Not Found error. This somehow exposes our endpoints, and we might want to keep them… private.

If this is a concern, you can add the following final route:

get '*' => sub ($c) { return $c->render(status => 404) };

In this way we are putting a catchall that will eventually complain with a Not Found error, but this time only to authenticated users.

The complete example

I’m planning to keep the complete example and possibly expand it if needed, here’s the current status: