File Coverage

blib/lib/Plack/Middleware/Auth/JWT.pm
Criterion Covered Total %
statement 75 77 97.4
branch 41 46 89.1
condition 9 11 81.8
subroutine 12 12 100.0
pod 2 3 66.6
total 139 149 93.2


line stmt bran cond sub pod time code
1             package Plack::Middleware::Auth::JWT;
2              
3             # ABSTRACT: Token-based Auth (aka Bearer Token) using JSON Web Tokens (JWT)
4             our $VERSION = '0.907'; # VERSION
5              
6 2     2   4068 use 5.010;
  2         12  
7 2     2   11 use strict;
  2         3  
  2         42  
8 2     2   8 use warnings;
  2         4  
  2         53  
9 2     2   11 use parent qw(Plack::Middleware);
  2         4  
  2         11  
10 2     2   136 use Plack::Util;
  2         4  
  2         86  
11             use Plack::Util::Accessor
12 2     2   13 qw(decode_args decode_callback psgix_claims psgix_token token_required ignore_invalid_token token_header_name token_query_name _env);
  2         4  
  2         19  
13 2     2   1347 use Plack::Request;
  2         124569  
  2         84  
14 2     2   720 use Crypt::JWT 0.020 qw(decode_jwt);
  2         55101  
  2         131  
15 2     2   17 use JSON qw(encode_json);
  2         5  
  2         20  
16              
17             sub prepare_app {
18 10     10 1 38041 my $self = shift;
19              
20             # some defaults
21 10 100       38 $self->psgix_claims('claims') unless $self->psgix_claims;
22 10 100       213 $self->psgix_token('token') unless $self->psgix_token;
23              
24 10 100       93 $self->token_header_name('bearer')
25             unless defined $self->token_header_name;
26 10 100       95 $self->token_header_name(undef) unless $self->token_header_name;
27 10 100       82 $self->token_query_name('token') unless defined $self->token_query_name;
28 10 100       94 $self->token_query_name(undef) unless $self->token_query_name;
29 10 100       55 $self->token_required(0) unless defined $self->token_required;
30 10 100       94 $self->ignore_invalid_token(0) unless defined $self->ignore_invalid_token;
31              
32             # either decode_args or decode_callback is required
33 10 100       96 if ( my $cb = $self->decode_callback ) {
    50          
34 5 50       38 die "decode_callback must be a code reference"
35             unless ref($cb) eq 'CODE';
36             }
37             elsif ( my $args = $self->decode_args ) {
38 5         45 $args->{decode_payload} = 1;
39 5         9 $args->{decode_header} = 0;
40 5 100       18 $args->{verify_exp} = 1 unless exists $args->{verify_exp};
41 5 50       16 $args->{leeway} = 5 unless exists $args->{leeway};
42             }
43             else {
44 0         0 die
45             "Either decode_callback or decode_args has to be defined when loading this Middleware";
46             }
47             }
48              
49             sub call {
50 31     31 1 161316 my ( $self, $env ) = @_;
51 31         122 $self->_env($env);
52              
53 31         442 my $token;
54              
55 31 100 100     75 if ( $self->token_header_name && $env->{HTTP_AUTHORIZATION} ) {
    100          
56 16         135 my $name = $self->token_header_name;
57 16         70 my $auth = $env->{HTTP_AUTHORIZATION};
58 16 50       223 $token = $1 if $auth =~ /^\s*$name\s+(.+)/i;
59             }
60             elsif ( my $name = $self->token_query_name ) {
61 13         1168 my $req = Plack::Request->new($env);
62 13         132 $token = $req->query_parameters->get($name);
63             }
64              
65 31 100       1157 unless ($token) {
66 8 100       23 return $self->unauthorized if $self->token_required;
67              
68             # no token found, but non required, so just call the app
69 2         16 return $self->app->($env);
70             }
71              
72 23         40 my $claims = eval {
73 23 100       61 if ( my $cb = $self->decode_callback ) {
74 8         70 return $cb->( $token, $env );
75             }
76             else {
77 15         72 return decode_jwt( token => $token, %{ $self->decode_args } );
  15         31  
78             }
79             };
80 23 100 66     3499 if ($@ || !$claims) {
81 10         21 my $e = $@;
82 10 100       31 if ($self->ignore_invalid_token) {
83 1         7 return $self->app->($env);
84             }
85             else {
86 9         85 $e =~ s/ at .*$//; # don't leak implementation detail on error!
87 9         36 return $self->unauthorized( 'Invalid token: ' . $e );
88             }
89             }
90             else {
91 13         1232 $env->{ 'psgix.' . $self->psgix_token } = $token;
92 13         102 $env->{ 'psgix.' . $self->psgix_claims } = $claims;
93 13         107 return $self->app->($env);
94             }
95              
96             # should never be reached, but just to make sure...
97 0         0 return $self->unauthorized;
98             }
99              
100             sub unauthorized {
101 15     15 0 57 my $self = shift;
102 15   100     47 my $body = shift || 'Authorization required';
103              
104 15         35 my $env = $self->_env;
105 15 100 66     89 if ($env->{HTTP_ACCEPT} && $env->{HTTP_ACCEPT} =~ m{application/json}i ) {
106 1 50       5 my $ident = $body =~/exp claim check failed/ ? 'token_expired' : 'token_invalid';
107 1         17 my $data = encode_json({
108             ident=>$ident,
109             message=>$body,
110             });
111             return [
112 1         9 401,
113             [ 'Content-Type' => 'application/json',
114             'Content-Length' => length $data
115             ],
116             [$data]
117             ];
118             }
119             else {
120             return [
121 14         117 401,
122             [ 'Content-Type' => 'text/plain',
123             'Content-Length' => length $body
124             ],
125             [$body]
126             ];
127             }
128             }
129              
130             1;
131              
132             __END__