File Coverage

blib/lib/WWW/Suffit/Plugin/BasicAuth.pm
Criterion Covered Total %
statement 15 72 20.8
branch 0 30 0.0
condition 0 28 0.0
subroutine 5 12 41.6
pod 1 1 100.0
total 21 143 14.6


line stmt bran cond sub pod time code
1             package WWW::Suffit::Plugin::BasicAuth;
2 1     1   101806 use strict;
  1         9  
  1         43  
3 1     1   6 use warnings;
  1         1  
  1         53  
4 1     1   561 use utf8;
  1         240  
  1         7  
5              
6             =encoding utf8
7              
8             =head1 NAME
9              
10             WWW::Suffit::Plugin::BasicAuth - The Mojolicious Plugin for HTTP basic authentication and authorization
11              
12             =head1 SYNOPSIS
13              
14             # in your startup
15             $self->plugin('WWW::Suffit::Plugin::BasicAuth', {
16             realm => "Strict Zone",
17             authn_fail_render => {
18             status => 401,
19             json => {
20             status => 0,
21             message => "Basic authentication required!",
22             },
23             },
24             authz_fail_render => {
25             status => 403,
26             json => {
27             status => 0,
28             message => "Forbidden!",
29             },
30             },
31             });
32              
33             # in your routes
34             sub index {
35             my $self = shift;
36              
37             # Basic authentication
38             return unless $self->is_basic_authenticated({ render_by_fail => 1 });
39              
40             # Basic Authorization
41             return unless $self->is_basic_authorized({ render_by_fail => 1 });
42              
43             $self->render(...);
44             }
45              
46             # or as condition in your startup
47             $self->routes->get('/info')->requires(basic_authenticated => 1, basic_authorized => 1)
48             ->to('alpha#info');
49              
50             # or bridged in your startup
51             my $auth = $self->routes->under(sub {
52             my $self = shift;
53              
54             # Basic authentication
55             return unless $self->is_basic_authenticated({ render_by_fail => 1 });
56              
57             # Basic Authorization
58             return unless $self->is_basic_authorized({ render_by_fail => 1 });
59              
60             return 1;
61             });
62             $auth->get('/info')->to('alpha#info');
63              
64             =head1 DESCRIPTION
65              
66             The Mojolicious Plugin for HTTP basic authentication and authorization
67              
68             This plugin based on L
69              
70             =head1 OPTIONS
71              
72             This plugin supports the following options
73              
74             =head2 authn
75              
76             Authentication checker callback
77              
78             $self->plugin('WWW::Suffit::Plugin::BasicAuth', {authn => sub {
79             my ($controller, $realm, $username, $password, $params) = @_;
80              
81             # ...
82              
83             return 1; # or 0 on fail
84             }});
85              
86             The B<$params> holds options from L call directly
87              
88             =head2 authz
89              
90             Authorization checker callback
91              
92             $self->plugin('WWW::Suffit::Plugin::BasicAuth', {authz => sub {
93             my ($controller, $params) = @_;
94              
95             # ...
96              
97             return 1; # or 0 on fail
98             }});
99              
100             The B<$params> holds options from L call directly
101              
102             =head2 authn_fail_render
103              
104             Defines what is to be rendered when the authenticated condition is not met
105              
106             Set to a coderef which will be called with the following signature:
107              
108             sub {
109             my $controller = shift;
110             my $realm = shift;
111             my $resp = shift; # See authn_fail_render
112             ...
113             return $hashref;
114             }
115              
116             The return value of the subroutine will be ignored if it evaluates to false.
117             If it returns a hash reference, it will be dereferenced and passed as-is
118             to the controller's C function
119              
120             If set directly to a hash reference, that will be passed to C instead
121              
122             =head2 authz_fail_render
123              
124             Defines what is to be rendered when the authorized condition is not met
125              
126             Set to a coderef which will be called with the following signature:
127              
128             sub {
129             my $controller = shift;
130             my $resp = shift; # See authz_fail_render
131             ...
132             return $hashref;
133             }
134              
135             See also L
136              
137             =head2 realm
138              
139             $self->plugin('WWW::Suffit::Plugin::BasicAuth', {realm => 'My Castle!'});
140              
141             HTTP Realm, defaults to 'Strict Zone'
142              
143             =head1 HELPERS
144              
145             =head2 is_basic_authenticated
146              
147             This helper performs credential validation and checks the authentication status
148              
149             my $authenticated = $self->is_basic_authenticated;
150             my $authenticated = $self->is_basic_authenticated({
151             render_by_fail => 1,
152             authn => sub {
153             my ($c, $in_realm, $in_user, $in_pass, $params) = @_;
154             return 0 unless $in_user;
155             return secure_compare($in_pass, "mypass") ? 1 : 0;
156             },
157             fail_render => {
158             json => {
159             message => "Basic authentication required!",
160             },
161             status => 401,
162             },
163             });
164              
165             =over 8
166              
167             =item B
168              
169             It enables rendering the fail response. See L
170              
171             =item B
172              
173             It defines code of authentication
174              
175             =item B
176              
177             It is render parameters as L
178              
179             =back
180              
181             =head2 is_basic_authorized
182              
183             This helper checks the authorization status
184              
185             my $authorized = $self->is_basic_authorized;
186             my $authorized = $self->is_basic_authorized({
187             render_by_fail => 1,
188             authz => sub {
189             my ($c, $params) = @_;
190             return 1; # Basic authorization tsatus
191             },
192             fail_render => {
193             json => {
194             message => "Forbidden!",
195             },
196             status => 403,
197             },
198              
199             });
200              
201             =over 8
202              
203             =item B
204              
205             It enables rendering the fail response. See L
206              
207             =item B
208              
209             It defines code of authorization
210              
211             =item B
212              
213             It is render parameters as L
214              
215             =back
216              
217             =head1 METHODS
218              
219             Internal methods
220              
221             =head2 register
222              
223             This method register the plugin and helpers in L application.
224              
225             =head1 EXAMPLES
226              
227             Examples of using
228              
229             =head2 ROUTING VIA CONDITION
230              
231             This plugin exports a routing condition you can use in order to limit
232             access to certain documents to only authenticated users.
233              
234             $self->routes->get('/info')->requires(basic_authenticated => 1, basic_authorized => 1)
235             ->to('alpha#info');
236              
237             Prior to Mojolicious 9, use "over" instead of "requires."
238              
239             =head2 ROUTING VIA CALLBACK
240              
241             If you want to be able to send people to a login page, you will have to use
242             the following:
243              
244             sub index {
245             my $self = shift;
246              
247             $self->redirect_to('/login') and return 0
248             unless($self->is_basic_authenticated && $self->is_basic_authorized);
249              
250             $self->render(...);
251             }
252              
253             =head2 ROUTING VIA BRIDGE
254              
255             my $auth = $self->routes->under(sub {
256             my $self = shift;
257              
258             # Authentication
259             return unless $self->is_basic_authenticated({
260             render_by_fail => 1
261             });
262              
263             # Authorization
264             return unless $self->is_basic_authorized({
265             render_by_fail => 1
266             });
267              
268             return 1;
269             });
270             $auth->get('/info')->to('alpha#info');
271              
272             =head1 SEE ALSO
273              
274             L, L, L,
275             L, L,
276             L
277              
278             =head1 AUTHOR
279              
280             Serż Minus (Sergey Lepenkov) L Eabalama@cpan.orgE
281              
282             =head1 COPYRIGHT
283              
284             Copyright (C) 1998-2026 D&D Corporation
285              
286             =head1 LICENSE
287              
288             This program is distributed under the terms of the Artistic License Version 2.0
289              
290             See the C file or L for details
291              
292             =cut
293              
294 1     1   706 use Mojo::Base 'Mojolicious::Plugin';
  1         10156  
  1         6  
295 1     1   2274 use Mojo::Util qw/b64_decode/;
  1         194280  
  1         1191  
296              
297             our $VERSION = '1.01';
298              
299             sub register {
300 0     0 1   my ($plugin, $app, $defaults) = @_; # $self = $plugin
301 0   0       $defaults //= {};
302 0   0       $defaults->{realm} //= 'Strict Zone';
303             $defaults->{authn_fail_render_cb} = ref($defaults->{authn_fail_render}) eq 'CODE'
304             ? $defaults->{authn_fail_render}
305             : sub {
306 0     0     my $c = shift; # controller
307 0   0       my $realm = shift || $defaults->{realm};
308 0   0       my $resp = shift || $defaults->{authn_fail_render};
309 0           $c->res->headers->www_authenticate(sprintf('Basic realm="%s"', $realm));
310 0           return $resp;
311 0 0         };
312             $defaults->{authz_fail_render_cb} = ref($defaults->{authz_fail_render}) eq 'CODE'
313             ? $defaults->{authz_fail_render}
314             : sub {
315 0     0     my $c = shift; # $controller
316 0   0       my $resp = shift || $defaults->{authz_fail_render};
317 0           return $resp;
318 0 0         };
319              
320             # Authentication condition + fail render
321             $app->routes->add_condition(basic_authenticated => sub {
322 0     0     my ($r, $c, $captures, $required) = @_;
323 0   0       my $res = (!$required or $c->is_basic_authenticated);
324 0 0         unless ($res) {
325 0           my $render = $defaults->{authn_fail_render_cb}; # Code
326 0           my $fail = $render->($c); # Call render, returns {}
327 0 0         $c->render(%$fail) if $fail;
328             }
329 0           return $res;
330 0           });
331              
332             # Authorization condition + fail render
333             $app->routes->add_condition(basic_authorized => sub {
334 0     0     my ($r, $c, $captures, $required) = @_;
335 0   0       my $res = (!$required or $c->is_basic_authorized);
336 0 0         unless ($res) {
337 0           my $render = $defaults->{authz_fail_render_cb}; # Code
338 0           my $fail = $render->($c); # Call render, returns {}
339 0 0         $c->render(%$fail) if $fail;
340             }
341 0           return $res;
342 0           });
343              
344             # Authentication checker (authn)
345             $app->helper(is_basic_authenticated => sub {
346 0     0     my $c = shift;
347 0   0       my $params = shift // {};
348 0           my %opt = (%$defaults, %$params); # Hashes merging
349              
350             # Define the authn callback
351 0 0         my $authn = ref($opt{authn}) eq 'CODE' ? $opt{authn} : sub { 1 };
  0            
352              
353             # Get authorization string from request headers
354             my $auth_string = $c->req->headers->authorization
355 0   0       || $c->req->env->{'X_HTTP_AUTHORIZATION'} || $c->req->env->{'HTTP_AUTHORIZATION'} || '';
356 0 0         if ($auth_string =~ /Basic\s+(.*)/) {
357 0           $auth_string = $1;
358             }
359 0           my $auth_pair = b64_decode($auth_string);
360              
361             # Verification
362 0 0 0       return 1 if $auth_pair && $authn->($c, $opt{realm}, split(/:/, $auth_pair, 2), $params);
363             # $controller, $realm, $username, $password, $params
364              
365             # Render by fail
366 0 0         if ($opt{render_by_fail}) {
367 0           my $render = $opt{authn_fail_render_cb}; # Code
368 0           my $fail = $render->($c, $opt{realm}, $opt{fail_render}); # Call render, returns {}
369 0 0         $c->render(%$fail) if $fail;
370             }
371              
372             # Not authenticated
373 0           return 0;
374 0           });
375              
376             # Authorization checker (authz)
377             $app->helper(is_basic_authorized => sub {
378 0     0     my $c = shift;
379 0   0       my $params = shift // {};
380 0           my %opt = (%$defaults, %$params); # Hashes merging
381              
382             # Define the authz callback
383 0 0         my $authz = ref($opt{authz}) eq 'CODE' ? $opt{authz} : sub { 1 };
  0            
384              
385             # Verification
386 0 0         return 1 if $authz->($c, $params); # $controller, $params
387              
388             # Render by fail
389 0 0         if ($opt{render_by_fail}) {
390 0           my $render = $opt{authz_fail_render_cb}; # Code
391 0           my $fail = $render->($c, $opt{fail_render}); # Call render, returns {}
392 0 0         $c->render(%$fail) if $fail;
393             }
394              
395             # Not authorized
396 0           return 0;
397 0           });
398             }
399              
400             1;
401              
402             __END__