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