Integrating mod_perl with Apache 2.1 Authentication
by Geoffrey YoungJuly 08, 2003
Scratching Your Own Itch
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
|
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.

