Sign In/My Account | View Cart  
advertisement


Listen Print

Integrating mod_perl with Apache 2.1 Authentication

by Geoffrey Young
July 08, 2003

Scratching Your Own Itch

Some time ago I became intrigued with Digest authentication, which uses the same general mechanism as the familiar Basic authentication scheme but offers significantly more password security without requiring an SSL connection. At the time it was really just an academic interest—while some browsers supported Digest authentication, many of the more popular ones did not. Furthermore, even though the standard Apache distribution came with modules to support both Basic and Digest authentication, Apache (and thus mod_perl) only offered an API for interacting with Basic authentication. If you wanted to use Digest authentication, flat files were the only password storage medium available. With both of these restrictions, it seemed impractical to deploy Digest authentication in all but the most limited circumstances.

Fast forward two years. Practically all mainstream browsers now support Digest authentication, and my interest spawned what is now Apache::AuthDigest, a module that gives mod_perl 1.0 developers an API for Digest authentication that is very similar to the Basic API that mod_perl natively supports. The one lingering problem is probably not surprising—Microsoft Internet Explorer. As it turns out, using the Digest scheme with MSIE requires a fully RFC-compliant Digest implementation, and Apache::AuthDigest was patterned after Apache 1.3's mod_digest.c, which is sufficient for most browsers but not MSIE.

Related Reading

Practical mod_perl
By Stas Bekman, Eric Cholet

Table of Contents
Index
Sample Chapter

Read Online--Safari Search this book on Safari:
 

Code Fragments only

In my mind, opening up Digest authentication through mod_perl still needed work to be truly useful, namely full RFC compliance to support MSIE. Wading through RFCs is not how I like to spend my spare time, so I started searching for a shortcut. Because Apache 2.0 did away with mod_digest.c and replaced it with the fully compliant mod_auth_digest.c, I was convinced that there was something in Apache 2.0 I could use to make my life easier. In Apache 2.1, the development version of the next generation Apache server, I found what I was looking for.

In this article, we're going to examine a mod_perl module that provides Perl support for the new authentication provider hooks in Apache 2.1. These authentication providers make writing Basic authentication handlers easier than it has been in the past. At the same time, the new provider mechanism opens up Digest authentication to the masses, making the Digest scheme a real possibility for filling your dynamic authentication needs. While the material is somewhat dense, the techniques we will be looking at are some of the most interesting and powerful in the mod_perl arsenal. Buckle up.

To follow along with the code in this article, you will need at least mod_perl version 1.99_10, which is currently only available from CVS. You will also need Apache 2.1, which is also only available from CVS. Instructions for obtaining the sources for both can be found here. When compiling Apache, keep in mind that the code presented here only works under the prefork MPM — making it thread-safe is the next step in the adventure.

Authentication Basics

Because there is lots of material to cover, we'll skip over the requisite introductory discussion of HTTP authentication, the Apache request cycle, and other materials that probably already familiar and skip right to the mod_perl authentication API. In both mod_perl 1.0 and mod_perl 2.0, the PerlAuthenHandler represents Perl access to the Apache authentication phase, where incoming user credentials are traditionally matched to those stored within the application. A simple PerlAuthenHandler in mod_perl 2.0 might look like the following.

package My::BasicHandler;

use Apache::RequestRec ();
use Apache::Access ();

use Apache::Const -compile => qw(OK DECLINED HTTP_UNAUTHORIZED);

use strict;

sub handler {
  my $r = shift;

  # get the client-supplied credentials
  my ($status, $password) = $r->get_basic_auth_pw;

  # only continue if Apache says everything is OK
  return $status unless $status == Apache::OK;

  # user1/basic1 is ok
  if ($r->user eq 'user1' && $password eq 'basic1') {
    return Apache::OK;
  }

  # user2 is denied outright
  if ($r->user eq 'user2') {
    $r->note_basic_auth_failure;
    return Apache::HTTP_UNAUTHORIZED;
  }

  # all others are passed along to the Apache default
  # handler, which reads from the AuthUserFile
  return Apache::DECLINED;
}

1;

Although simple and impractical, this handler illustrates the API nicely. The process begins with a call to get_basic_auth_pw(), which does a few things behind the scenes. If a suitable Basic Authorization header is found, get_basic_auth_pw() will parse and decode the header, populate the user slot of the request record, and return OK along with the user-supplied password in clear text. Any value other than OK should be immediately propagated back to Apache, which effectively terminates the current request.

The next step in the process is where the real authentication logic resides. Our handler is responsible for digging out the username from $r->user() and applying some criteria for determining whether the user-supplied credentials are acceptable. If they are, the handler simply returns OK and the request is allowed to proceed. If they are not, the handler has a decision to make: either call note_basic_auth_failure() and return HTTP_UNAUTHORIZED (which is the same as the old AUTH_REQUIRED) to indicate failure, or return DECLINED to pass authentication control to the next authentication handler.

For the most part, the mod_perl API is identical to the API Apache offers to C module developers. The benefit that mod_perl adds is the ability to easily extend authentication beyond Apache's default flat-file mechanism to the areas where Perl support is strong, such as relational databases or LDAP. However, despite the versatility and strength programming the authentication phase offered, I never liked the look and feel of the API. While in some respects the process is dictated by the nuances of RFC 2617 and the HTTP protocol itself, the interface always struck me as somewhat inconsistent and difficult for new users to grasp. Additionally, as already mentioned, the API covers only Basic authentication, which is a real drawback as more and more browsers support the Digest scheme.

Apparently I wasn't alone in some of these feelings. Apache 2.1 has taken steps to improve the overall process for module developers. The result is a new, streamlined API that focuses on a new concept: authentication providers.

Authentication Providers in Apache 2.1

While in Apache 2.0 module writers were responsible for a large portion of the authentication logic—calling routines to parse and set authentication headers, digging out the user from the request record, and so on — the new authentication mechanism in Apache 2.1 delegates all HTTP and RFC logic out to two standard modules. mod_auth_basic handles Basic authentication and is enabled in the default Apache build. The standard mod_auth_digest, not enabled by default, handles the very complex world of Digest authentication. Regardless of the authentication scheme you choose to support, these modules are responsible for the details of parsing and interpreting the incoming request headers, as well as generating properly formatted response headers.

Of course, managing authentication on an HTTP level is only part of the story. What mod_auth_basic and mod_auth_digest leave behind is the job of digging out the server-side credentials and matching them to their incoming counterpart. Enter authentication providers.

Authentication providers are modules that supply server-side credential services to mod_auth_basic or mod_auth_digest. For instance, the default mod_authn_file digs the username and password out of the flat file specified by the AuthUserFile directive, similar to the default mechanism in Apache 1.3 and 2.0. An Apache 2.1 configuration that explicitly provides the same flat file behavior as Apache 2.0 would look similar to the following.

<Location /protected>
  Require valid-user
  AuthType Basic
  AuthName realm1

  AuthBasicProvider file

  AuthUserFile realm1
</Location>

The new part of this configuration is the AuthBasicProvider directive, which is implemented by mod_auth_basic and used to specify the provider responsible for managing server-side credentials. There is also a corresponding AuthDigestProvider directive if you have mod_auth_digest installed.

While it could seem as though Apache 2.1 is merely adding another directive to achieve essentially the same results, the shift to authentication providers adds significant value for module developers: a new API that is far simpler than before. Skipping ahead to the punch line, programming with new Perl API for Basic authentication, which follows the Apache API almost exactly, would look similar to the following.

package My::BasicProvider;

use Apache::Const -compile => qw(OK DECLINED HTTP_UNAUTHORIZED);

use strict;

sub handler {
  my ($r, $user, $password) = @_;

  # user1/basic1 is ok
  if ($user eq 'user1' && $password eq 'basic1') {
    return Apache::OK;
  }

  # user2 is denied outright
  if ($user eq 'user2') {
    return Apache::HTTP_UNAUTHORIZED;
  }

  # all others are passed along to the next provider
  return Apache::DECLINED;
}

1;

As you can see, not only are the incoming username and password supplied in the argument list, removing the need for get_basic_auth_pw() and its associated checks, but gone is the need to call note_basic_auth_failure() before returning HTTP_UNAUTHORIZED. In essence, all that module writers need to be concerned with is validating the user credentials against whatever back-end datastore they choose. All in all, the API is a definite improvement. To add even more excitement, the API for Digest authentication looks almost exactly the same (but more on that later).

Because the new authentication provider approach represents a significant change in the way Apache handles authentication internally, it is not part of the stable Apache 2.0 tree and is instead being tested in the development tree. Unfortunately, until the provider mechanism is backported to Apache 2.0, or an official Apache 2.2 release, it is unlikely that authentication providers will be supported by core mod_perl 2.0. However, this does not mean that mod_perl developers are out of luck—by coupling mod_perl's native directive handler API with a bit of XS, we can open up the new Apache provider API to Perl with ease. The Apache::AuthenHook module does exactly that.

Introducing Apache::AuthenHook

Over in the Apache C API, authentication providers have a few jobs to do: they must register themselves by name as a provider while supplying a callback interface for the schemes they wish to support (Basic, Digest, or both). In order to open up the provider API to Perl modules our gateway module Apache::AuthenHook will need to accomplish these tasks as well. Both of these are accomplished at the same time through a call to the official Apache API function ap_register_provider.

Usually, mod_perl provides direct access to the Apache C API for us. For instance, a Perl call to $r->get_basic_auth_pw() is proxied off to ap_get_basic_auth_pw—but in this case ap_register_provider only exists in Apache 2.1 and, thus, is not supported by mod_perl 2.0. Therefore, part of what Apache::AuthenHook needs to do is open up this API to Perl. One of the great things about mod_perl is the ease at which it allows itself to be extended even beyond its own core functionality. Opening up the Apache API past what mod_perl allows is relatively easy with a dash of XS.

Our module opens with AuthenHook.xs, which is used to expose ap_register_provider through the Perl function Apache::AuthenHook::register_provider().

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "mod_perl.h"
#include "ap_provider.h"
#include "mod_auth.h"

...

static const authn_provider authn_AAH_provider =
{
  &check_password,
  &get_realm_hash,
};

MODULE = Apache::AuthenHook    PACKAGE = Apache::AuthenHook

PROTOTYPES: DISABLE

void
register_provider(provider)
  SV *provider

  CODE:

    ap_register_provider(modperl_global_get_pconf(),
                         AUTHN_PROVIDER_GROUP,
                         SvPV_nolen(newSVsv(provider)), "0",
                         &authn_AAH_provider);

Let's start at the top. Any XS module you write will include the first three header files, while any mod_perl XS extension will require at least #include "mod_perl.h". The remaining two included header files are specific to what we are trying to accomplish—ap_provider.h defines the ap_register_provider function, while mod_auth.h defines the AUTHN_PROVIDER_GROUP constant we will be using, as well as the authn_provider struct that holds our callbacks.

Skipping down a bit, we can see our implementation of Apache::AuthenHook::register_provider(). The MODULE and PACKAGE declarations place register_provider() into the Apache::AuthenHook package. Following that is the definition of the register_provider() function itself.

As you can see, register_provider() accepts a single argument, a Perl scalar representing the name of the provider to register, making its usage something akin to the following.

Apache::AuthenHook::register_provider('My::BasicProvider');

The user-supplied name is then used in the call to ap_register_provider from the Apache C API to register My::BasicProvider as an authentication provider.

The twist in the process is that our implementation of ap_register_provider registers Apache::AuthenHook's callbacks (the check_password and get_realm_hash routines not shown here) for each Perl provider. In essence, this means that Apache::AuthenHook will be acting as go-between for Perl providers. Much in the same way that mod_perl proper is called by Apache for each phase and dispatches to different Perl*Handlers, Apache::AuthenHook will be called by Apache's authentication modules and dispatch to the appropriate Perl provider at runtime.

If this boggles your mind a bit, not to worry, it is only being presented to give you a feel for the bigger picture and show how easy it is to open up closed parts of the Apache C API with mod_perl and just a few lines of XS. However, the fun part of Apache::AuthenHook (and the part that you are more likely to use in your own mod_perl modules) is handled over in Perl space.

Pages: 1, 2, 3

Next Pagearrow