line
stmt
bran
cond
sub
pod
time
code
1
package Mojo::JWT;
2
3
1
1
237037
use Mojo::Base -base;
1
2
1
8
4
5
our $VERSION = '0.09';
6
$VERSION = eval $VERSION;
7
8
1
1
216
use Scalar::Util qw/blessed/;
1
2
1
40
9
1
1
6
use List::Util qw/first/;
1
1
1
69
10
1
1
6
use Mojo::JSON qw/encode_json decode_json/;
1
2
1
37
11
1
1
5
use MIME::Base64 qw/encode_base64url decode_base64url/;
1
2
1
52
12
13
1
1
6
use Carp;
1
1
1
1750
14
15
my $isa = sub { blessed $_[0] && $_[0]->isa($_[1]) };
16
17
has header => sub { {} };
18
has algorithm => 'HS256';
19
has [qw/allow_none set_iat/] => 0;
20
has claims => sub { {} };
21
has jwks => sub { [] };
22
has [qw/expires not_before/];
23
has [qw/public secret/] => '';
24
25
my $re_hs = qr/^HS(\d+)$/;
26
my $re_rs = qr/^RS(\d+)$/;
27
28
sub add_jwkset {
29
2
2
1
1840
my ($self, $jwkset) = @_;
30
2
50
7
if (ref $jwkset eq 'HASH') {
31
2
2
push @{ $self->jwks }, @{ $jwkset->{keys} };
2
6
2
5
32
}
33
2
50
6
if (ref $jwkset eq 'ARRAY') {
34
0
0
push @{ $self->jwks }, @{ $jwkset };
0
0
0
0
35
}
36
2
5
return $self;
37
}
38
39
sub decode {
40
16
16
1
3644
my ($self, $token, $peek) = @_;
41
16
30
$self->{token} = $token;
42
43
# reset
44
16
37
$self->algorithm(undef);
45
16
108
delete $self->{$_} for qw/claims expires not_before header/;
46
47
16
66
my ($hstring, $cstring, $signature) = split /\./, $token;
48
16
40
my $header = decode_json decode_base64url($hstring);
49
16
2112
my $claims = decode_json decode_base64url($cstring);
50
16
1604
$signature = decode_base64url $signature;
51
52
# typ header is only recommended and is ignored
53
# https://tools.ietf.org/html/rfc7519#section-5.1
54
16
139
delete $header->{typ};
55
croak 'Required header field "alg" not specified'
56
16
50
44
unless my $algo = $self->algorithm(delete $header->{alg})->algorithm;
57
16
145
$self->header($header);
58
59
16
89
$self->_try_jwks($algo, $header);
60
61
16
50
55
$self->$peek($claims) if $peek;
62
63
# check signature
64
16
44
my $payload = "$hstring.$cstring";
65
16
100
149
if ($algo eq 'none') {
100
50
66
1
50
4
croak 'Algorithm "none" is prohibited'
67
unless $self->allow_none;
68
} elsif ($algo =~ $re_rs) {
69
7
100
19
croak 'Failed RS validation'
70
unless $self->verify_rsa($1, $payload, $signature);
71
} elsif ($algo =~ $re_hs) {
72
8
100
21
croak 'Failed HS validation'
73
unless $signature eq $self->sign_hmac($1, $payload);
74
} else {
75
0
0
croak 'Unsupported signing algorithm';
76
}
77
78
# check timing
79
11
35
my $now = $self->now;
80
11
50
43
if (defined(my $exp = $claims->{exp})) {
81
0
0
0
croak 'JWT has expired' if $now > $exp;
82
0
0
$self->expires($exp);
83
}
84
11
50
23
if (defined(my $nbf = $claims->{nbf})) {
85
0
0
0
croak 'JWT is not yet valid' if $now < $nbf;
86
0
0
$self->not_before($nbf);
87
}
88
89
11
23
return $self->claims($claims)->claims;
90
}
91
92
sub _try_jwks {
93
16
16
26
my ($self, $algo, $header) = @_;
94
16
100
100
19
return unless @{$self->jwks} && $header->{kid};
16
29
95
96
# Check we have the JWK for this JWT
97
4
50
4
35
my $jwk = first { exists $header->{kid} && $_->{kid} eq $header->{kid} } @{$self->jwks};
4
36
4
6
98
4
100
17
return unless $jwk;
99
100
3
50
14
if ($algo =~ /^RS/) {
0
101
3
13
require Crypt::OpenSSL::Bignum;
102
3
7
my $n = Crypt::OpenSSL::Bignum->new_from_bin(decode_base64url $jwk->{n});
103
3
81
my $e = Crypt::OpenSSL::Bignum->new_from_bin(decode_base64url $jwk->{e});
104
105
3
27
require Crypt::OpenSSL::RSA;
106
3
59
my $pubkey = Crypt::OpenSSL::RSA->new_key_from_parameters($n, $e);
107
3
460
$self->public($pubkey);
108
} elsif ($algo =~ /^HS/) {
109
$self->secret( decode_base64url $jwk->{k} )
110
0
0
}
111
}
112
113
sub encode {
114
11
11
1
58592
my $self = shift;
115
11
27
delete $self->{token};
116
117
11
33
my $claims = $self->claims;
118
11
100
61
if ($self->set_iat) { $claims->{iat} = $self->now }
1
8
119
11
50
69
if (defined(my $exp = $self->expires)) { $claims->{exp} = $exp }
0
0
120
11
50
62
if (defined(my $nbf = $self->not_before)) { $claims->{nbf} = $nbf }
0
0
121
122
11
41
my $header = { %{ $self->header }, typ => 'JWT', alg => $self->algorithm };
11
23
123
11
82
my $hstring = encode_base64url encode_json($header);
124
11
1034
my $cstring = encode_base64url encode_json($claims);
125
11
474
my $payload = "$hstring.$cstring";
126
11
13
my $signature;
127
11
23
my $algo = $self->algorithm;
128
11
100
126
if ($algo eq 'none') {
100
50
129
1
2
$signature = '';
130
} elsif ($algo =~ $re_rs) {
131
2
25
$signature = $self->sign_rsa($1, $payload);
132
} elsif ($algo =~ $re_hs) {
133
8
23
$signature = $self->sign_hmac($1, $payload);
134
} else {
135
0
0
croak 'Unknown algorithm';
136
}
137
138
10
32
return $self->{token} = "$payload." . encode_base64url $signature;
139
}
140
141
10
10
17
sub now { time }
142
143
sub sign_hmac {
144
16
16
1
45
my ($self, $size, $payload) = @_;
145
16
100
36
croak 'symmetric secret not specified' unless my $secret = $self->secret;
146
15
116
require Digest::SHA;
147
15
66
211
my $f = Digest::SHA->can("hmac_sha$size") || croak 'Unsupported HS signing algorithm';
148
14
308
return $f->($payload, $secret);
149
}
150
151
sub sign_rsa {
152
2
2
1
15
my ($self, $size, $payload) = @_;
153
2
21
require Crypt::OpenSSL::RSA;
154
2
33
8
my $crypt = Crypt::OpenSSL::RSA->new_private_key($self->secret || croak 'private key (secret) not specified');
155
2
33
103
my $method = $crypt->can("use_sha${size}_hash") || croak 'Unsupported RS signing algorithm';
156
2
27
$crypt->$method;
157
2
1038
return $crypt->sign($payload);
158
}
159
160
0
0
1
0
sub token { shift->{token} }
161
162
sub verify_rsa {
163
7
7
1
22
my ($self, $size, $payload, $signature) = @_;
164
7
31
require Crypt::OpenSSL::RSA;
165
7
100
17
croak 'public key not specified' unless my $public = $self->public;
166
5
100
32
my $crypt = $public->$isa('Crypt::OpenSSL::RSA') ? $public : Crypt::OpenSSL::RSA->new_public_key($public);
167
5
33
781
my $method = $crypt->can("use_sha${size}_hash") || croak 'Unsupported RS verification algorithm';
168
5
16
$crypt->$method;
169
5
624
return $crypt->verify($payload, $signature);
170
}
171
172
1;
173
174
=head1 NAME
175
176
Mojo::JWT - JSON Web Token the Mojo way
177
178
=head1 SYNOPSIS
179
180
my $jwt = Mojo::JWT->new(claims => {...}, secret => 's3cr3t')->encode;
181
my $claims = Mojo::JWT->new(secret => 's3cr3t')->decode($jwt);
182
183
=head1 DESCRIPTION
184
185
JSON Web Token is described in L.
186
L implements that standard with an API that should feel familiar to L users (though of course it is useful elsewhere).
187
Indeed, JWT is much like L except that the result is a url-safe text string rather than a cookie.
188
189
In JWT, the primary payload is called the C, and a few claims are reserved, as seen in the IETF document.
190
The header and the claims are signed when stringified to guard against tampering.
191
Note that while signed, the data is not encrypted, so don't use it to send secrets over clear channels.
192
193
=head1 ATTRIBUTES
194
195
L inherits all of the attributes from L and implements the following new ones.
196
197
=head2 algorithm
198
199
The algorithm to be used to sign a JWT during encoding or else the algorithm that was used for the most recent decoding.
200
Defaults to C until a decode is performed.
201
202
C is an acceptable encoding algorithm, however for it to be used to decode, L must be set.
203
204
=head2 allow_none
205
206
To prevent spoofing attacks, C must be explicitly set to a true value otherwise decoding a JWT which specifies the C algorithm will result in an exception.
207
The default is of course false.
208
209
=head2 claims
210
211
The payload to be encoded or else the claims from the most recent decoding.
212
This must be a hash reference, array references are not allowed as the top-level JWT claims.
213
214
=head2 expires
215
216
The epoch time value after which the JWT value should not be considered valid.
217
This value (if set and not undefined) will be used as the C key in the claims or was extracted from the claims during the most recent decoding.
218
219
=head2 header
220
221
You may set your own headers when encoding the JWT bypassing a hash reference to the L attribute. Please note that there are two default headers set. B is set to the value of L or 'HS256' and B is set to 'JWT'. These cannot be overridden.
222
223
=head2 not_before
224
225
The epoch time value before which the JWT value should not be considered valid.
226
This value (if set and not undefined) will be used as the C key in the claims or was extracted from the claims during the most recent decoding.
227
228
=head2 public
229
230
The public key to be used in decoding an asymmetrically signed JWT (eg. RSA).
231
232
=head2 secret
233
234
The symmetric secret (eg. HMAC) or else the private key used in encoding an asymmetrically signed JWT (eg. RSA).
235
236
=head2 set_iat
237
238
If true (false by default), then the C claim will be set to the value of L during L.
239
240
=head2 jwks
241
242
An arrayref of JWK objects used by C to verify the input token when matching with the JWTs C field.
243
244
my $jwks = Mojo::UserAgent->new->get('https://example.com/oidc/jwks.json')->result->json('/keys');
245
my $jwt = Mojo::JWT->new(jwks => $jwks);
246
$jwk->decode($token);
247
248
=head1 METHODS
249
250
L inherits all of the methods from L and implements the following new ones.
251
252
=head2 add_jwkset
253
254
my $jwkset = Mojo::UserAgent->new->get('https://example.com/oidc/jwks.json')->result->json;
255
my $jwt = Mojo::JWT->new->add_jwkset($jwksset);
256
$jwk->decode($token);
257
258
Helper for appending a jwkset to the L.
259
Accepts a hashref with a C field that is an arrayref of jwks and also an arrayref directly.
260
Appends the JWKs to L.
261
Returns the instance.
262
263
=head2 decode
264
265
my $claims = $jwt->decode($token);
266
267
my $peek = sub { my ($jwt, $claims) = @_; ... };
268
my $claims = $jwt->decode($token, $peek);
269
270
Decode and parse a JSON Web Token string and return the claims hashref.
271
Calling this function immediately sets the L to the passed in token.
272
It also sets L to C and unsets L, L and L.
273
These values are then set as part of the parsing process.
274
275
Parsing occurs as follows
276
277
=over
278
279
=item *
280
281
The L is extracted from the header and set, if not present or permissible an exception is thrown
282
283
=item *
284
285
Any JWKs in C are checked against the headers and if one is found then it is set in L or L as appropriate to the L
286
287
=item *
288
289
If a C<$peek> callback is provided, it is called with the instance and claims as arguments
290
291
=item *
292
293
The signature is verified or an exception is thrown
294
295
=item *
296
297
The timing claims (L and L), if present, are evaluated, failures result in exceptions. On success the values are set in the relevant attributes
298
299
=item *
300
301
The L attribute is set and the claims are returned.
302
303
=back
304
305
Note that when the C<$peek> callback is invoked, the claims have not yet been verified.
306
This callback is most likely to be used to inspect the C or issuer claim to determine a secret or key for decoding.
307
The return value is ignored, changes should be made to the instances attributes directly.
308
Since the L has already been parsed, it is available via the instance attribute as well.
309
310
=head2 encode
311
312
my $token = $jwt->encode;
313
314
Encode the data expressed in the instance attributes: L, L, L, L.
315
Note that if the timing attributes are given, they override existing keys in the L.
316
Calling C immediately clears the L and upon completion sets it to the result as well as returning it.
317
318
Note also that due to Perl's hash randomization, repeated encoding is not guaranteed to result in the same encoded string.
319
However any encoded string will survive an encode/decode roundtrip.
320
321
=head2 header
322
323
my $header = $jwt->header;
324
325
Returns a hash reference representing the JWT header, constructed from instance attributes (see L).
326
327
=head2 now
328
329
my $time = $jwt->now;
330
331
Returns the current time, currently implemented as the core C function.
332
333
=head2 sign_hmac
334
335
my $signature = $jwt->sign_hmac($size, $payload);
336
337
Returns the HMAC SHA signature for the given size and payload.
338
The L attribute is used as the symmetric key.
339
The result is not yet base64 encoded.
340
This method is provided mostly for the purposes of subclassing.
341
342
=head2 sign_rsa
343
344
my $signature = $jwt->sign_rsa($size, $payload);
345
346
Returns the RSA signature for the given size and payload.
347
The L attribute is used as the private key.
348
The result is not yet base64 encoded.
349
This method is provided mostly for the purposes of subclassing.
350
351
=head2 token
352
353
The most recently encoded or decoded token.
354
Note that any attribute modifications are not taken into account until L is called again.
355
356
=head2 verify_rsa
357
358
my $bool = $jwt->verify_rsa($size, $payload, $signature);
359
360
Returns true if the given RSA size algorithm validates the given payload and signature.
361
The L attribute is used as the public key.
362
This method is provided mostly for the purposes of subclassing.
363
364
=head1 SEE ALSO
365
366
=over
367
368
=item L
369
370
=item L
371
372
=item L
373
374
=back
375
376
=head1 SOURCE REPOSITORY
377
378
L
379
380
=head1 AUTHOR
381
382
Joel Berger, Ejoel.a.berger@gmail.comE
383
384
=head1 CONTRIBUTORS
385
386
Christopher Raa (mishanti1)
387
388
Cameron Daniel (ccakes)
389
390
=head1 COPYRIGHT AND LICENSE
391
392
Copyright (C) 2015 by L and L.
393
394
This library is free software; you can redistribute it and/or modify
395
it under the same terms as Perl itself.
396