File Coverage

blib/lib/Mojolicious/Plugin/HttpBasicAuth.pm
Criterion Covered Total %
statement 39 40 97.5
branch 8 8 100.0
condition 12 19 63.1
subroutine 8 9 88.8
pod 1 1 100.0
total 68 77 88.3


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::HttpBasicAuth;
2              
3 1     1   775 use Mojo::Base 'Mojolicious::Plugin';
  1         2  
  1         8  
4 1     1   176 use Mojo::ByteStream;
  1         1  
  1         41  
5 1     1   17 use Mojo::Util qw{b64_encode b64_decode};
  1         1  
  1         454  
6              
7             our $VERSION = '0.12';
8              
9             sub register {
10 1     1 1 34 my ($plugin, $app, $user_defaults) = @_;
11              
12 1         2 push @{ $app->renderer->classes }, __PACKAGE__;
  1         5  
13              
14 1         16 my %defaults = %$user_defaults;
15 1   50     6 $defaults{realm} //= 'WWW';
16             $defaults{validate} //= sub {
17 0     0   0 die('please define a validate callback');
18 1   50     7 };
19             $defaults{invalid} //= sub {
20 126     126   166 my $controller = shift;
21             return (
22 126         1336 json => { json => { error => 'HTTP 401: Unauthorized' } },
23             html => { template => 'auth/basic' },
24             any => { data => 'HTTP 401: Unauthorized' }
25             );
26 1   50     6 };
27              
28             $app->renderer->add_helper(
29             basic_auth => sub {
30 162     162   1454972 my $controller = shift;
31 162   50     529 my $params = shift // {};
32 162         882 my %options = (%defaults, %$params);
33              
34             # Sent credentials
35 162   100     651 my $auth = b64_decode($plugin->_auth_header($controller) || '');
36              
37             # No credentials entered
38 162 100       838 return $plugin->_unauthorized($controller, $options{realm}, $options{invalid}) unless ($auth);
39              
40             # Verification within callback
41 144 100 66     995 return 1 if $options{validate} and $options{validate}->($controller, split(/:/, $auth, 2), $options{realm});
42              
43             # Not verified
44 108         1258 return $plugin->_unauthorized($controller, $options{realm}, $options{invalid});
45             }
46 1         3 );
47             }
48              
49             sub _auth_header {
50 162     162   257 my $plugin = shift;
51 162         197 my $controller = shift;
52 162   66     433 my $auth = $controller->req->headers->authorization || $controller->req->env->{'X_HTTP_AUTHORIZATION'} || $controller->req->env->{'HTTP_AUTHORIZATION'};
53              
54 162 100 66     5292 if ($auth && $auth =~ m/Basic (.*)/) {
55 144         335 $auth = $1;
56             }
57              
58 162         684 return $auth;
59             }
60              
61             sub _unauthorized {
62 126     126   175 my ($plugin, $controller, $realm, $callback) = @_;
63              
64 126         420 $controller->res->code(401);
65 126         1317 $controller->res->headers->www_authenticate("Basic realm=\"$realm\"");
66 126         3158 $controller->respond_to($callback->($controller));
67              
68             # Only render if not already rendered
69 126 100       146834 if ($controller->tx) {
70 84         444 $controller->rendered;
71             }
72              
73 126         2827 return;
74             }
75              
76             1;
77              
78             =pod
79              
80             =encoding utf8
81              
82             =head1 NAME
83              
84             Mojolicious::Plugin::HttpBasicAuth - Http-Basic-Authentication implementation for Mojolicious
85              
86             =head1 SYNOPSIS
87              
88             # in your startup
89             $self->plugin(
90             'http_basic_auth', {
91             validate => sub {
92             my $c = shift;
93             my $loginname = shift;
94             my $password = shift;
95             my $realm = shift;
96             return 1 if($realm eq 'Evergreen Terrace' && $loginname eq 'Homer' && $password eq 'Marge');
97             return 0;
98             },
99             realm => 'Evergreen Terrace'
100             }
101             );
102              
103             # in your routes
104             sub index {
105             my $self = shift;
106             return unless $self->basic_auth(\%options);
107             $self->render();
108             }
109              
110             # or bridged
111             my $foo = $r->bridge('/bridge')->to(cb => sub {
112             my $self = shift;
113             # Authenticated
114             return unless $self->basic_auth({realm => 'Castle Bridge', validate => sub {return 1;}});
115             });
116             $foo->route('/bar')->to(controller => 'foo', action => 'bar');
117              
118              
119             =head1 DESCRIPTION
120              
121             L is a implementation of the Http-Basic-Authentication
122              
123             =head1 OPTIONS
124              
125             L supports the following options.
126              
127             =head2 realm
128              
129             $self->plugin('http_basic_auth', {realm => 'My Castle!'});
130              
131             HTTP-Realm, defaults to 'WWW'
132              
133             =head2 validate
134              
135             $self->plugin('http_basic_auth', {
136             validate => sub {
137             my $c = shift;
138             my $loginname = shift;
139             my $password = shift;
140             my $realm = shift;
141             return 1 if($realm eq 'Springfield' && $loginname eq 'Homer' && $password eq 'Marge');
142             return 0;
143             }
144             });
145              
146             Validation callback to verify user. This option is B.
147              
148             =head2 invalid
149              
150             $self->plugin('http_basic_auth', {
151             invalid => sub {
152             my $controller = shift;
153             return (
154             json => { json => { error => 'HTTP 401: Unauthorized' } },
155             html => { template => 'auth/basic' },
156             any => { data => 'HTTP 401: Unauthorized' }
157             );
158             }
159             });
160              
161             Callback for invalid requests, default can be seen here. Return values are dispatched to L
162              
163             =head1 HELPERS
164              
165             L implements the following helpers.
166              
167             =head2 basic_auth
168              
169             return unless $self->basic_auth({realm => 'Kitchen'});
170              
171             All default options can be overwritten in every call.
172              
173             =head1 METHODS
174              
175             L inherits all methods from
176             L and implements the following new ones.
177              
178             =head2 register
179              
180             my $route = $plugin->register(Mojolicious->new);
181             my $route = $plugin->register(Mojolicious->new, {realm => 'Fort Knox', validate => sub {
182             return 0;
183             }});
184              
185             Register renderer and helper in L application.
186              
187             =head1 SEE ALSO
188              
189             L, L, L.
190              
191             =head1 AUTHOR
192              
193             Patrick Grämer Epgraemer@cpan.orgE
194             , L.
195              
196             =head1 CONTRIBUTOR
197              
198             Markus Michel Emmichel@cpan.orgE
199             , L.
200              
201             =head1 COPYRIGHT
202              
203             Copyright 2015 Patrick Grämer
204              
205             =head1 LICENSE
206              
207             This library is free software; you can redistribute it and/or modify
208             it under the same terms as Perl itself.
209              
210             =cut
211              
212             __DATA__