O'Reilly logo

Embedding Perl in HTML with Mason by Ken Williams, Dave Rolsky

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

User Authentication and Authorization

One problem that web sites have to solve over and over is user authentication and authorization. These two topics are related but not the same, as some might think. Authentication is the process of figuring out if someone is who he says he is, and usually involves checking passwords or keys of some sort. Authorization comes after this, when we want to determine whether or not a particular person is allowed to perform a certain action.

There are a number of modules on CPAN intended to help do these things under mod_perl. In fact, Apache has separate request-handling phases for both authentication and authorization that mod_perl can handle. It is certainly possible to use these modules with Mason.

You can also do authentication and authorization using Mason components (as seen in Chapter 8). Authentication will usually involve some sort of request for a login and password, after which you give the user some sort of token (either in a cookie or a session) that indicates that he has been authenticated. You can then check the validity of this token for each request.

If you have such a token, authorization simply consists of checking that the user to whom the token belongs is allowed to perform a given action.

Using Apache::AuthCookie

The Apache::AuthCookie module, available from CPAN, handles both authentication and authorization via mod_perl and can be easily hooked into Mason. Let’s just skip all the details of configuring Apache::AuthCookie, which requires various settings in your server config file, and show how to make the interface to Mason.

Apache::AuthCookie requires that you create a “login script” that will be executed the first time a browser tries to access a protected area. Calling this a script is actually somewhat misleading since it is really a page rather than a script (though it could be a script that generates a page). Regardless, using a Mason component for your login script merely requires that you specify the path to your Mason component for the login script parameter.

We’ll call this script AuthCookieLoginForm-login.comp, as shown in Example 11-6.

Example 11-6. AuthCookieLoginForm-login.comp

<html>
<head>
<title>Mason Book AuthCookie Login Form</title>
</head>
<body>
<p>
Your attempt to access this document was denied
(<% $r->prev->subprocess_env("AuthCookieReason") %>).  Please enter
your username and password.
</p>

<form action="/AuthCookieLoginSubmit">
<input type="hidden" name="destination" value="<% $r->prev->uri %>">
<table align="left">
 <tr>
  <td align="right"><b>Username:</b></td>
  <td><input type="text" name="credential_0" size="10" maxlength="10"></td>
 </tr>
 <tr>
  <td align="right"><b>Password:</b></td>
  <td><input type="password" name="credential_1" size="8" maxlength="8"></td>
 </tr>
 <tr>
  <td colspan="2" align="center"><input type="submit" value="Continue"></td>
 </tr>
</table>
</form>

</body>
</html>

This component is a modified version of the example login script included with the Apache::AuthCookie distribution.

The action used for this form, /AuthCookieLoginSubmit, is configured as part of your AuthCookie configuration in your httpd.conf file.

That is about it for interfacing this module with Mason. The rest of authentication and authorization is handled by configuring mod_perl to use Apache::AuthCookie to protect anything on your site that needs authorization. A very simple configuration might include the following directives:

PerlSetVar MasonBookLoginScript /AuthCookieLoginForm.comp

<Location /AuthCookieLoginSubmit>
  AuthType MasonBook::AuthCookieHandler
  AuthName MasonBook
  SetHandler perl-script
  PerlHandler MasonBook::AuthCookieHandler->login
</Location>

<Location /protected>
  AuthType MasonBook::AuthCookieHandler
  AuthName MasonBook
  PerlAuthenHandler MasonBook::AuthCookieHandler->authenticate
  PerlAuthzHandler MasonBook::AuthCookieHandler->authorize
  require valid-user
</Location>

The MasonBook::AuthCookieHandler module would look like this:

package MasonBook::AuthCookieHandler;

use strict;

use base qw(Apache::AuthCookie);

use Digest::SHA1;

my $secret = "You think I'd tell you?  Hah!";

sub authen_cred {
    my $self = shift;
    my $r = shift;
    my ($username, $password) = @_;

    # implementing _is_valid_user() is out of the scope of this chapter
    if ( _is_valid_user($username, $password) ) {
        my $session_key =
          $username . '::' . Digest::SHA1::sha1_hex( $username, $secret );
        return $session_key;
    }
}

sub authen_ses_key {
    my $self = shift;
    my $r = shift;
    my $session_key = shift;

    my ($username, $mac) = split /::/, $session_key;

    if ( Digest::SHA1::sha1_hex( $username, $secret ) eq $mac ) {
        return $session_key;
    }
}

This provides the minimal interface an Apache::AuthCookie subclass needs to provide to get authentication working.

Authentication Without Cookies

But what if you don’t want to use Apache::AuthCookie? Your site may need to work without using cookies.

First, we will show an example authentication system that uses only Mason and passes the authentication token around via the URL (actually, via a session).

This example assumes that we already have some sort of session system that passes the session ID around as part of the URL, as discussed previously.

We start with a quick login form. We will call this component login_form.html, as shown in Example 11-7.

Example 11-7. login_form.html

<%args>
 $username => ''
 $password => ''
 $redirect_to => ''
 @errors => ( )
</%args>
<html>
<head>
<title>Mason Book Login</title>
</head>

<body>

% if (@errors) {
<h2>Errors</h2>
%   foreach (@errors) {
<b><% $_ | h %></b><br>
%   }
% }

<form action="login_submit.html">
<input type="hidden" name="redirect_to" value="<% $redirect_to %>">
<table align="left">
 <tr>
  <td align="right"><b>Login:</b></td>
  <td><input type="text" name="username" value="<% $username %>"></td>
 </tr>
 <tr>
  <td align="right"><b>Password:</b></td>
  <td><input type="password" name="password" value="<% $password %>"></td>
 </tr>
 <tr>
  <td colspan="2" align="center"><input type="submit" value="Login"></td>
 </tr>
</table>
</form>

</body>
</html>

This form uses some of the same techniques we saw in Chapter 8 to prepopulate the form and handle errors.

Now let’s make the component that handles the form submission. This component, called login_submit.html and shown in Example 11-8, will check the username and password and, if they are valid, place an authentication token into the user’s session.

Example 11-8. login_submit.html

<%args>
 $username
 $password
 $redirect_to
</%args>
<%init>
 if (my @errors = check_login($username, $password) {
     $m->comp( 'redirect.mas',
                path => 'login_form.html',
                query => { errors => \@errors,
                           username => $username,
                           password => $password,
                           redirect_to => $redirect_to } );
 }
 
 $MasonBook::Session{username} = $username;
 $MasonBook::Session{token} =
     Digest::SHA1::sha1_hex( 'My secret phrase', $username );
 
 $m->comp( 'redirect.mas',
           path => $redirect_to );
</%init>

This component simply checks (via magic hand waving) whether the username and password are valid and, if so, generates an authentication token that is added to the user’s session. To generate this token, we take the username, which is also in the session, and combine it with a secret phrase. We then generate a MAC from those two things.

The authentication and authorization check looks like this:

if ( $MasonBook::Session{token} ) {
    if ( $MasonBook::Session{token} eq
         Digest::SHA1::sha1_hex( 'My secret phrase',
                                 $MasonBook::Session{username} ) {

        # ... valid login, do something here
    } else {
        # ... someone is trying to be sneaky!
    }
} else { # no token
     my $wanted_page = $r->uri;

     # Append query string if we have one.
     $wanted_page .= '?' . $r->args if $r->args;

     $m->comp( 'redirect.mas',
                path => '/login/login_form.html',
                query => { redirect_to => $wanted_page } );
}

We could put all the pages that require authorization in a single directory tree and have a top-level autohandler in that tree do the check. If there is no token to check, we redirect the browser to the login page, and after a successful login the user will return, assuming she submitted valid login credentials.

Access Controls with Attributes

The components we saw previously assumed that there are only two access levels, unauthenticated and authenticated. A more complicated version of this code might involve checking that the user has a certain access level or role.

In that case, we’d first check that we had a valid authentication token and then go on to check that the user actually had the appropriate access rights. This is simply an extra step in the authorization process.

Using attributes, we can easily define access controls for different portions of our site. Let’s assume that we have four access levels, Guest, User, Editor, and Admin. Most of the site is public and viewable by anyone. Some parts of the site require a valid login, while some require a higher level of privilege.

We implement our access check in our top-level autohandler, /autohandler, from which all other components must inherit in order for the access control code to be effective.

<%init>
 my $user = get_user( );  # again, hand waving

 my $required_access = $m->base_comp->attr('required_access');

 unless ( $user->has_access_level($required_access) ) {
    # ... do something like send them to another page
 }

 $m->call_next;
</%init>
<%attr>
 required_access => 'Guest'
</%attr>

It is crucial that we set a default access level in this autohandler. By doing this, we are saying that, by default, all components are accessible by all people, since every visitor will have at least Guest access.

We can override this default elsewhere. For example, in a component called /admin/autohandler, we might have:

<%attr>
 required_access => 'Admin'
</%attr>

As long as all the components in the /admin/ directory inherit from the /admin/autohandler component and don’t override the required_access attribute, we have effectively limited that directory (and its subdirectories) to admin users only. If we for some reason had an individual component in the /admin/ directory that we wanted editors to be able to see, we could simply set the required_access attribute for that component to 'Editor' .

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required