2012 Feed

Options For Simple Authentication

OX - 2012-12-15

Selective Sharing

As we've seen, one of the nice things about the OX framework is how it exemplifies and amplifies the whipuptitude nature of Perl. It's easy to go from a wild idea to a single file proof-of-concept level implementation with OX, and then later refactor that prototype into your "real" application.

Personally, I find there's usually a point between "prototype" and "alpha" where I want to share my work with other people to get their feedback. That usually means putting it on an Internet-accessible server and mailing my collaborators the URL -- and that also means I need to put some sort of authentication wrapper around the prototype, so that the whole world can't see my work in progress.

One option is to wait to share your prototype until you've gotten to the point of implementing the required amount of authentication in the application itself. This may be a ways down the road, however, and it's hard to wait to share. Another option is to put some HTTP Basic-style authentication in place via Apache .htaccess files, but that requires some sysadmin style tinkering with htpasswd files and probably with your web server configuration.

Since OX builds upon Plack/PSGI, a more attractive alternative is to leverage existing Plack middleware to add authentication directly to the prototype application. We can add either HTTP Basic style authentication, or a full-blown form-based login. Let's see what each of those looks like.

Plack::Middleware::Auth::Basic

Plack::Middleware::Auth::Basic, as you might guess from the module name, is a middleware that implements basic types of authentication for your application. The module takes an authenticator configuration option, which can be an object that implements an authenticate method (with a ($username,$password) signature) or a simple callback function, which will also be called with the username and password provided in response to the authentication dialogue. This means an OX application implementing basic authentication can be as simple as:


1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
10: 
11: 
12: 
13: 
14: 

 

package MyOXsomeApp;
use OX;

router as {
  wrap 'Plack::Middleware::Auth::Basic' => (
    authenticator => literal( sub {
      my( $user, $pass ) = @_;
      return $user eq 's00per' and $pass eq 's3kr1t';
    } ),
    realm => literal( "my awesome app!" ),
  );

  route '/' => sub { "hello world!" };
};

 

The realm option being passed to the middleware is used to control what is displayed in the pop-up window that appears when the web browser prompts for credentials. Since wrap stanzas in the router statement do the same sort of Bread::Board-based service resolution as other parts of OX, you could also use attributes for the Plack::Middleware::Auth::Basic parameters, like so:


1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 

 

package MyOXsomeApp;
use OX;

has basic_auth_realm => (
    is => 'ro',
    isa => 'Str',
    value => 'My Awesome App!'
);

router as {
  wrap 'Plack::Middleware::Auth::Basic' => (
    authenticator => literal( sub {
      my ( $user , $pass ) = @_;
      return $user eq 's00per' and $pass eq 's3kr1t';
    } ),
    realm => 'basic_auth_realm',
  );

  route '/' => sub { "hello world!" };
};

 
(Note: you can download this application here.)

Plack::Middleware::Auth::Form

HTTP Basic-style authentication is great when you need a quick way to secure access to an entire application. However, if you're trying to prototype an application that will have some areas that require authentication and some that don't, it may not be the best choice. Instead, consider Plack::Middleware::Auth::Form, which adds /login and /logout endpoints to your application. You'll also want to load Plack::Middleware::Session to provide a way to store details such as the username of the current user. Plack::Middleware::Auth::Form is configured very similarly to Plack::Middleware::Auth::Basic -- the required authenticator option works exactly as described above, in fact.

Here's a simple OX application that uses form-based authentication to control access to the /admin endpoint, while allowing unauthenticated access for /.


1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 

 

package MyOXsomeApp;
use OX;

use HTTP::Throwable::Factory qw/ http_throw /;

router as {
  wrap 'Plack::Middleware::Session' => (
    store => literal( 'File' ),
  );

  wrap 'Plack::Middleware::Auth::Form' => (
    authenticator => literal( sub {
      my ( $user, $pass ) = @_;
      return $user eq 's00per' and $pass eq 's3kr1t';
    } ),
  );

  route '/' => sub { "hello world!" };

  route '/admin' => sub {
    my ( $request ) = @_;

    my $user;
    unless ( $user = $request->session->{user_id} ) {
      $request->session->{redir_to} = '/admin';
      http_throw( Found => { location => '/login' } );
    }

    return "admin section!<br/>hello $user!";
  };
};

 
(Note: you can download this application here.)

Here we have the same authenticator callback as in the previous example, but we're using Plack::Middleware::Auth::Form instead. You should be able to load the / endpoint without being prompted for a password, but attempting to load /admin should result in you being redirected to /login, which will display a simple login form.

Looking at the inline sub for the /admin route, we see that we're looking at the PSGI session hash (available via the session method on the $request object that is passed to the action) to see if there's a user_id key. If there is, that means we have a logged-in user, and can fall past the conditional and return a string to display. If we don't see a value in this key, however, that means we don't have a logged in user, and we want to display the login form instead. We can easily accomplish this by throwing a HTTP::Throwable exception, as described in Monday's article.

(N.B., if you were implementing this type of authentication in a real application, you'd want to either wrap up the code that checks for an active user and issues a redirect to /login inside a role that could be consumed by controller modules that needed this functionality, or implement the logic as a middleware.)

Next steps

When you're ready to move beyond the "prototype" stage of your application, you'll find yourself wanting to authenticate against information you've got stored in a database (or another type of data store that you've abstracted behind a model-layer module). You'll also want a login form that fits into the look-and-feel of the rest of your site, ideally one that renders and validates input via the same modules as the rest of your view layer. At that point, you'll probably want to drop Plack::Middleware::Auth::Form in favor of your own code. We'll leave that step as an exercise for the reader (at least for the moment...)

Gravatar Image This article contributed by: John SJ Anderson <john.anderson@iinteractive.com>