ClearPress Framework

Authentication

As of r327 ClearPress has shipped with modules in the ClearPress::authenticator:: namespace. These are helpers designed to help authenticate users using various different mechanisms. At the time of writing there are four mechanisms:

db.pm
principally for MySQL-backed accounts
ldap.pm
for LDAP and Active Directory
passwd.pm
for /etc/passwd (and NIS) support
session.pm
for cookie-based sessions
db, ldap and passwd verify username and password credentials whilst session.pm verifies encrypted cookies from web-requests.

For simplicity let's assume the authentication procedure goes a little like this:

  1. Registered user "U" comes to app
  2. U enters username and password and submits login form
  3. username and password are checked in database (db.pm)
  4. app serves welcome page with encrypted session cookie containing username+password
  5. Further page requests from U return the session cookie
  6. Each further page request decodes the session cookie and uses the username contained within (session.pm)

As well as the authenticator modules there are several other components required:

  1. login page
  2. logout page
  3. a modified decor.pm for cookie handling
  4. a 'user' data-model object
  5. a modified controller.pm for building information about the requestor (the 'user' data model corresponding to the authenticated user)

login
#!/usr/bin/env perl -T
use strict;
use warnings;
use myapp::decor qw($AUTH_COOKIE $TOPSECRETKEY);
use ClearPress::authenticator::session;
use ClearPress::authenticator::db;

main();
exit;

sub main {
  my $decor    = myapp::decor->new();
  my $cgi      = $decor->cgi();
  my $username = $cgi->param('cred_0');
  my $password = $cgi->param('cred_1');

  if(!$username || !$password) {
    #########
    # force a logout
    #
    my $cookie = $cgi->cookie(
      -name    => $AUTH_COOKIE,
      -value   => q[],
      -expires => '-1d',
    );

    $decor->username(q[]);
    print qq[Set-Cookie: $cookie\n];
    print $decor->header();
    print login_form($decor);
    print $decor->footer();
    return 1;
  }

  my $db = ClearPress::authenticator::db->new();
  my $user_info = $db->authen_credentials({
    username => $username,
    password => $password,
  });

  if($user_info) {
    $decor->username($username);
    my $session     = ClearPress::authenticator::session->new({key => $TOPSECRETKEY});
    my $encoded     = $session->encode_token($user_info);
    my $auth_cookie = $cgi->cookie(
      -name  => $AUTH_COOKIE,
      -value => $encoded,
    );

    print qq[Set-Cookie: $auth_cookie\n];

    print $decor->header();
    print qq[<h2>Welcome back, $username</h2>];
    print $decor->footer();
    return 1;
  }

  #########
  # force a logout
  #
  my $cookie = $cgi->cookie(
    -name    => $AUTH_COOKIE,
    -value   => q[],
    -expires => '-1d',
  );

  $decor->username(q[]);

  print qq[Set-Cookie: $cookie\n];
  print $decor->header();
  print q[<h2>Login failed. Please try again</h2>];
  print login_form($decor);
  print $decor->footer();

  return;
}

sub login_form {
  my $decor = shift;

  return <<'EOT';
&lt;h2&gt;Login with a registered account&lt;/h2&gt;
&lt;fieldset&gt;
 &lt;legend&gt;Login&lt;/legend&gt;
 &lt;form method="post" action="$ENV{SCRIPT_NAME}"&gt;
  &lt;p&gt;
   &lt;label for="cred_0"&gt;Email address&lt;/label&gt;
   &lt;input type="text" name="cred_0" id="cred_0"/&gt;
  &lt;/p&gt;
  &lt;p&gt;
   &lt;label for="cred_1"&gt;Password&lt;/label&gt;
   &lt;input type="password" name="cred_1" id="cred_1"/&gt;
  &lt;/p&gt;
  &lt;p class="actions"&gt;&lt;input type="submit" value="Log in"/&gt;&lt;/p&gt;
 &lt;/form&gt;
&lt;/fieldset&gt;
EOT
}
logout
#!/usr/bin/perl -T
use strict;
use warnings;
use myapp::decor qw($AUTH_COOKIE);

my $decor  = myapp::decor->new();
my $cgi    = $decor-&gt;cgi();
my $cookie = $cgi->cookie(
                          -name    =&gt; $AUTH_COOKIE,
                          -value   =&gt; q[],
                          -expires =&gt; '-1d',
                         );
$decor->username(q[]);
print qq[Set-Cookie: $cookie\n];
print $decor-&gt;header();
print q[&lt;h2&gt;Thanks for visiting, come again soon!&lt;/h2&gt;];
print $decor-&gt;footer();
myapp/decor.pm

Strictly this should inherit from authdecor.pm but at the time of writing it's not quite ready yet

package myapp::decor;
use strict;
use warnings;
use base qw(Exporter ClearPress::decorator);
use ClearPress::authenticator::session;
use Readonly;

Readonly::Scalar our $AUTH_COOKIE  =&gt; 'myapp_sso';
Readonly::Scalar our $TOPSECRETKEY =&gt; 'top_secret_key_goes_here';
our @EXPORT_OK = qw($AUTH_COOKIE $TOPSECRETKEY);

sub username {
  my ($self, $username) = @_;

  if(defined $username) {
    $self-&gt;{username} = $username;
  }

  if(defined $self-&gt;{username}) {
    return $self-&gt;{username};
  }

  my $auth   = ClearPress::authenticator::session-&gt;new({
                                                        key =&gt; $TOPSECRETKEY,
                                                       });
  my $cgi    = $self-&gt;cgi();
  my $cookie = $cgi-&gt;cookie($AUTH_COOKIE);

  if(!$cookie) {
    #########
    # no auth cookie. don't bother trying to decrypt
    #
    return;
  }

  my $ref = $auth-&gt;authen_token($cookie);
  if(!$ref) {
    #########
    # Failed to authenticate session token
    #
    return;
  }

  return $ref-&gt;{username};
}
1;
 
myapp/model/user.pm
package myapp::model::user;
use strict;
use warnings;
use base qw(ClearPress::model);

__PACKAGE__-&gt;mk_accessors(fields());

sub fields {
  return qw(id_user username realname pass);
}

sub secondary_key {
  #########
  # support load-by-username as well as the default load-by-id
  #
  return 'username';
}
1;
 
myapp/controller.pm (partial)

There can often be many 'use' statements at the top of the controller.pm - omitted here for clarity

package myapp::controller;
use strict;
use warnings;
use base qw(ClearPress::controller);

sub decorator {
  my ($self, $util) = @_;
  my $decor         = myapp::decor->new({
                                         stylesheet =&gt; [qw(/myapp.css)],
                                        });

  my $requestor = myapp::model::user->new({
                                           util     =&gt; $util,
                                           username =&gt; $decor->username,
                                          });
  $util-&gt;requestor($requestor);
  return $decor;
}

1;
 

Caveats