| Plack-Middleware-Auth-Digest documentation | Contained in the Plack-Middleware-Auth-Digest distribution. |
Plack::Middleware::Auth::Digest - Digest authentication
enable "Auth::Digest", realm => "Secured", secret => "blahblahblah",
authenticator => sub {
my ($username, $env) = @_;
return $password; # for $username
};
# Or return MD5 hash of "$username:$realm:$password"
enable "Auth::Digest", realm => "Secured", secret => "blahblahblah",
password_hashed => 1,
authenticator => sub { return $password_hashed };
Plack::Middleware::Auth::Digest is a Plack middleware component that
enables Digest authentication. Your authenticator callback is called using
two parameters: a username as a string and the PSGI $env hash. Your callback
should return a password, either as a raw password or a hashed password.
A callback that takes a username and PSGI $env hash and returns a password
for the user, either in a plaintext password or a MD5 hash of
"username:realm:password" (quotes not included) when
password_hashed option is enabled.
A boolean (0 or 1) to indicate whether authenticator callback
returns passwords in a plaintext or hashed. Defaults to 0 (plaintext).
A string to represent the realm. Defaults to restricted area.
Server secret text string that is used to sign nonce. Required.
Time-to-live seconds to prevent replay attacks. Defaults to 60.
This middleware expects that the application has a full access to the headers sent by clients in PSGI environment. That is normally the case with standalone Perl PSGI web servers such as Starman or HTTP::Server::Simple::PSGI.
However, in a web server configuration where you can't achieve this
(i.e. using your application via mod_perl, CGI or FastCGI), this
middleware does not work since your application can't know the value
of Authorization: header.
If you use Apache as a web server and CGI or mod_perl to run your PSGI application, you can use mod_rewrite to pass the Authorization header to the application with the rewrite rule like following.
RewriteEngine on
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
Yuji Shimada <xaicron@cpan.org>
Tatsuhiko Miyagawa
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
| Plack-Middleware-Auth-Digest documentation | Contained in the Plack-Middleware-Auth-Digest distribution. |
package Plack::Middleware::Auth::Digest; use 5.008001; use strict; use warnings; use parent qw/Plack::Middleware/; use Plack::Util::Accessor qw/realm authenticator password_hashed secret nonce_ttl/; use MIME::Base64 (); use Digest::MD5 (); use Digest::HMAC_SHA1 (); our $VERSION = '0.04'; sub hash { Digest::MD5::md5_hex(join ":", @_); } sub prepare_app { my $self = shift; if ($self->authenticator && ref $self->authenticator ne 'CODE') { die 'authenticator should be a code reference'; } unless ($self->secret) { die "Auth::Digest secret key is not set."; } } sub call { my ($self, $env) = @_; my $auth = $env->{HTTP_AUTHORIZATION} or return $self->unauthorized; if ($auth =~ /^Digest (.*)/) { my $auth = $self->parse_challenge($1) || {}; $auth->{method} = $env->{REQUEST_METHOD}; if ($auth->{uri} ne $env->{REQUEST_URI}) { return [ 400, ['Content-Type', 'text/plain'], [ "Bad Request" ] ]; } my $password = $self->authenticator->($auth->{username}, $env); if ( defined $password && $self->valid_nonce($auth) && $self->digest($password, $auth) eq $auth->{response}) { if ($self->stale_nonce($auth)) { return $self->unauthorized(stale => "true"); } $env->{REMOTE_USER} = $auth->{username}; return $self->app->($env); } } return $self->unauthorized; } sub parse_challenge { my($self, $header) = @_; my $auth; while ($header =~ /(\w+)\=("[^\"]+"|[^,]+)/g ) { $auth->{$1} = dequote($2); } return $auth; } sub dequote { my $s = shift; $s =~ s/^"(.*)"$/$1/; $s =~ s/\\(.)/$1/g; $s; } sub digest { my($self, $password, $auth) = @_; my $hashed = $self->password_hashed ? $password : hash($auth->{username}, $auth->{realm}, $password); return hash($hashed, @{$auth}{qw(nonce nc cnonce qop)}, hash("$auth->{method}:$auth->{uri}")); } sub unauthorized { my $self = shift; my %params = @_; my $body = '401 Authorization required'; my $realm = $self->realm || "restricted area"; my $nonce = $self->generate_nonce(time); my $algorithm = 'MD5'; my $qop = 'auth'; my $challenge = qq|Digest realm="$realm", nonce="$nonce", algorithm=$algorithm, qop="$qop"|; $challenge .= qq(, stale=true) if $params{stale}; return [ 401, [ 'Content-Type' => 'text/plain', 'Content-Length' => length $body, 'WWW-Authenticate' => $challenge, ], [ $body ], ]; } sub valid_nonce { my($self, $auth) = @_; my($time, $digest) = split / /, MIME::Base64::decode_base64($auth->{nonce}); $auth->{_nonce_time} = $time; # cache for stale check return $time && $digest && $digest eq $self->hmac($time); } sub stale_nonce { my($self, $auth) = @_; $auth->{_nonce_time} < time - ($self->nonce_ttl || 60); } sub generate_nonce { my($self, $time) = @_; my $nonce = MIME::Base64::encode_base64(join " ", $time, $self->hmac($time)); chomp $nonce; return $nonce; } sub hmac { my($self, $time) = @_; Digest::HMAC_SHA1::hmac_sha1_hex($time, $self->secret); } 1; __END__