1package Plack::Middleware::Auth::Basic;
2use strict;
3use parent qw(Plack::Middleware);
4use Plack::Util::Accessor qw( realm authenticator );
5use Scalar::Util;
6use MIME::Base64;
7
8sub prepare_app {
9    my $self = shift;
10
11    my $auth = $self->authenticator or die 'authenticator is not set';
12    if (Scalar::Util::blessed($auth) && $auth->can('authenticate')) {
13        $self->authenticator(sub { $auth->authenticate(@_[0,1]) }); # because Authen::Simple barfs on 3 params
14    } elsif (ref $auth ne 'CODE') {
15        die 'authenticator should be a code reference or an object that responds to authenticate()';
16    }
17}
18
19sub call {
20    my($self, $env) = @_;
21
22    my $auth = $env->{HTTP_AUTHORIZATION}
23        or return $self->unauthorized;
24
25    # note the 'i' on the regex, as, according to RFC2617 this is a
26    # "case-insensitive token to identify the authentication scheme"
27    if ($auth =~ /^Basic (.*)$/i) {
28        my($user, $pass) = split /:/, (MIME::Base64::decode($1) || ":"), 2;
29        $pass = '' unless defined $pass;
30        if ($self->authenticator->($user, $pass, $env)) {
31            $env->{REMOTE_USER} = $user;
32            return $self->app->($env);
33        }
34    }
35
36    return $self->unauthorized;
37}
38
39sub unauthorized {
40    my $self = shift;
41    my $body = 'Authorization required';
42    return [
43        401,
44        [ 'Content-Type' => 'text/plain',
45          'Content-Length' => length $body,
46          'WWW-Authenticate' => 'Basic realm="' . ($self->realm || "restricted area") . '"' ],
47        [ $body ],
48    ];
49}
50
511;
52
53__END__
54
55=head1 NAME
56
57Plack::Middleware::Auth::Basic - Simple basic authentication middleware
58
59=head1 SYNOPSIS
60
61  use Plack::Builder;
62  my $app = sub { ... };
63
64  builder {
65      enable "Auth::Basic", authenticator => \&authen_cb;
66      $app;
67  };
68
69  sub authen_cb {
70      my($username, $password, $env) = @_;
71      return $username eq 'admin' && $password eq 's3cr3t';
72  }
73
74=head1 DESCRIPTION
75
76Plack::Middleware::Auth::Basic is a basic authentication handler for Plack.
77
78=head1 CONFIGURATION
79
80=over 4
81
82=item authenticator
83
84A callback function that takes username, password and PSGI environment
85supplied and returns whether the authentication succeeds. Required.
86
87Authenticator can also be an object that responds to C<authenticate>
88method that takes username and password and returns boolean, so
89backends for L<Authen::Simple> is perfect to use:
90
91  use Authen::Simple::LDAP;
92  enable "Auth::Basic", authenticator => Authen::Simple::LDAP->new(...);
93
94=item realm
95
96Realm name to display in the basic authentication dialog. Defaults to I<restricted area>.
97
98=back
99
100=head1 LIMITATIONS
101
102This middleware expects that the application has a full access to the
103headers sent by clients in PSGI environment. That is normally the case
104with standalone Perl PSGI web servers such as L<Starman> or
105L<HTTP::Server::Simple::PSGI>.
106
107However, in a web server configuration where you can't achieve this
108(i.e. using your application via Apache's mod_cgi), this middleware
109does not work since your application can't know the value of
110C<Authorization:> header.
111
112If you use Apache as a web server and CGI to run your PSGI
113application, you can either a) compile Apache with
114C<-DSECURITY_HOLE_PASS_AUTHORIZATION> option, or b) use mod_rewrite to
115pass the Authorization header to the application with the rewrite rule
116like following.
117
118  RewriteEngine on
119  RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
120
121=head1 AUTHOR
122
123Tatsuhiko Miyagawa
124
125=head1 SEE ALSO
126
127L<Plack>
128
129=cut
130