11.3. Authentication

HTTP Basic Authentication is the most common type of authentication supported at the level of HTTP. The exchange works like this:

  1. The browser makes a request for a URL.
  2. The page is protected by Basic Authentication, so the server replies with a 401 Unauthorized status code. The response has a WWW-Authenticate header that specifies the authentication method ("basic") and the realm. "Realm" here is jargon for a string that identifies the locked-off area, which the browser is about to use in the next step.

  3. The browser displays an "enter your username and password for realm" dialog box. Figure 11-1 shows the dialog box for a part of www.unicode.org whose realm name is "Unicode-MailList-Archives."

  4. The browser requests the URL again, this time with an Authorization header that encodes the username and password.

  5. If the username and password are verified, the server sends the document in a normal successful HTTP response. If the username and password aren't correct, we go back to step 2.

Figure 11-1: Authentication dialog box

Figure 11-1. Authentication dialog box

11.3.1. Comparing Cookies with Basic Authentication

Like cookies, LWP implements HTTP Basic Authentication with attributes of an LWP::UserAgent object. There are basic differences, however.

There's no such thing as an explicit HTTP error message that means "you needed to send me a proper cookie, so try again!". The "Register Now!" page that the New York Times site returned is not an error in any HTTP sense; as far as the browser is concerned, it asked for something, and got it.

LWP's interface for HTTP cookies and HTTP Basic Authentication is different. To get an LWP::UserAgent browser object to implement cookies, one assigns it an object of class HTTP::Cookies (or a subclass), which represents a little database of cookies that this browser knows about. But there is no corresponding class for groups of username/password pairs, although I informally refer to the set of passwords that a user agent can consult as its "key ring."

11.3.2. Authenticating via LWP

To add a username and password to a browser object's key ring, call the credentials method on a user agent object:

$browser->credentials(
  'servername:portnumber',
  'realm-name',
  'username' => 'password'
);

In most cases, the port number is 80, the default TCP/IP port for HTTP. For example:

my $browser = LWP::UserAgent->new;
$browser->agent('ReportsBot/1.01');

$browser->credentials(
  'reports.mybazouki.com:80',
  'web_server_usage_reports',
  'plinky' => 'banjo123'
);

my $response = $browser->get(
  'http://reports.mybazouki.com/this_week/'
);

One can call the credentials method any number of times, to add all the server-port-realm-username-password keys to the browser's key ring, regardless of whether they'll actually be needed. For example, you could read them all in from a datafile at startup:

my $browser = LWP::UserAgent->new( );
if(open(KEYS, "< keyring.dat")) {
  while(<KEYS>) {
    chomp;
    my @info = split "\t", $_, -1;
    $browser->credential(@info) if @info == 4;
  };
  close(KEYS);
}

11.3.3. Security

Clearly, storing lots of passwords in a plain text file is not terribly good security practice, but the obvious alternative is not much better: storing the same data in plain text in a Perl file. One could make a point of prompting the user for the information every time,[5] instead of storing it anywhere at all, but clearly this is useful only for interactive programs (as opposed to a programs run by crontab, for example).

[5] In fact, Ave Wrigley wrote a module to do exactly that. It's not part of the LWP distribution, but it's available in CPAN as LWP::AuthenAgent. The author describes it as "a simple subclass of LWP::UserAgent to allow the user to type in username/password information if required for authentication."

In any case, HTTP Basic Authentication is not the height of security: the username and password are normally sent unencrypted. This and other security shortcomings with HTTP Basic Authentication are explained in greater detail in RFC 2617. See the the Preface for information on where to get a copy of RFC 2617.