Crypt-JWT
view release on metacpan or search on metacpan
lib/Crypt/JWT.pm view on Meta::CPAN
croak "$what: JWK has unknown ECDSA alg '$key->{alg}'";
}
}
}
sub _check_accepted {
my ($what, $value, $check) = @_;
return unless defined $check;
my $r = ref $check;
if ($r eq 'Regexp') { croak "JWT: $what '$value' does not match accepted_$what" if $value !~ $check }
elsif ($r eq 'ARRAY') { my %ok = map { $_ => 1 } @$check;
croak "JWT: $what '$value' not in accepted_$what" unless $ok{$value} }
elsif (!$r) { croak "JWT: $what '$value' not accepted_$what" if $value ne $check }
else { croak "JWT: accepted_$what must be Regexp, ARRAY ref, or Scalar (got $r)" }
}
sub _verify_header {
my ($header, %args) = @_;
# currently we only check "typ" header parameter
my $check = $args{verify_typ};
return if !defined $check;
if (exists $header->{typ}) {
if (ref $check eq 'Regexp') {
my $value = $header->{typ};
$value = "" if !defined $value;
croak "JWT: typ header re check failed" unless $value =~ $check;
}
elsif (ref $check eq 'CODE') {
croak "JWT: typ header code check failed" unless $check->($header->{typ});
}
elsif (!ref $check) {
my $value = $header->{typ};
croak "JWT: typ header scalar check failed" unless defined $value && $value eq $check;
}
else {
croak "JWT: verify_typ must be Regexp, Scalar or CODE";
}
}
else {
croak "JWT: typ header required but missing"
}
}
sub _check_numeric_date {
my ($payload, $claim) = @_;
my $value = $payload->{$claim};
croak "JWT: $claim claim must be a NumericDate"
if !defined($value) || ref($value) || "$value" !~ /\A(?:0|[1-9][0-9]*)(?:\.[0-9]+)?\z/;
}
sub _verify_claims {
my ($payload, %args) = @_;
return if $args{ignore_claims};
if (ref($payload) ne 'HASH') {
# https://github.com/DCIT/perl-Crypt-JWT/issues/31
# payload needs to be decoded into a HASH for checking any verify_XXXX
for my $claim (qw(exp nbf iat iss sub aud jti)) {
if (defined $args{"verify_$claim"} && $args{"verify_$claim"} != 0) {
croak "JWT: cannot check verify_$claim (payload not decoded JSON/HASH)";
}
}
return; # nothing to check
}
my $leeway = $args{leeway} || 0;
my $now = time;
### exp
if(defined $payload->{exp}) {
if (!defined $args{verify_exp} || $args{verify_exp}==1) {
_check_numeric_date($payload, 'exp');
croak "JWT: exp claim check failed ($payload->{exp}/$leeway vs. $now)" if $payload->{exp} + $leeway <= $now;
}
}
elsif ($args{verify_exp} && $args{verify_exp}==1) {
croak "JWT: exp claim required but missing"
}
### nbf
if(defined $payload->{nbf}) {
if (!defined $args{verify_nbf} || $args{verify_nbf}==1) {
_check_numeric_date($payload, 'nbf');
croak "JWT: nbf claim check failed ($payload->{nbf}/$leeway vs. $now)" if $payload->{nbf} - $leeway > $now;
}
}
elsif ($args{verify_nbf} && $args{verify_nbf}==1) {
croak "JWT: nbf claim required but missing"
}
### iat
if (exists $args{verify_iat}) { #default (non existing verify_iat) == no iat check
if(defined $payload->{iat}) {
if (!defined $args{verify_iat} || $args{verify_iat}==1) {
_check_numeric_date($payload, 'iat');
croak "JWT: iat claim check failed ($payload->{iat}/$leeway vs. $now)" if $payload->{iat} - $leeway > $now;
}
}
elsif ($args{verify_iat} && $args{verify_iat}==1) {
croak "JWT: iat claim required but missing"
}
}
### aud
if (defined $args{verify_aud}) {
my $check = $args{verify_aud};
if (exists $payload->{aud}) {
my $match = 0;
# aud claim is a bit special as it can be either a string or an array of strings
my @aud_list = ref $payload->{aud} eq 'ARRAY' ? @{$payload->{aud}} : ( $payload->{aud} );
for my $value (@aud_list) {
if (ref $check eq 'Regexp') {
$value = "" if !defined $value;
$match = 1 if $value =~ $check;
}
elsif (ref $check eq 'CODE') {
$match = 1 if $check->($value);
}
elsif (!ref $check) {
$match = 1 if defined $value && $value eq $check;
lib/Crypt/JWT.pm view on Meta::CPAN
}
1;
#### URLs
# https://metacpan.org/pod/JSON::WebToken
# https://metacpan.org/pod/Mojo::JWT
# https://bitbucket.org/b_c/jose4j/wiki/JWE%20Examples
# https://bitbucket.org/b_c/jose4j/wiki/JWS%20Examples
# https://github.com/dvsekhvalnov/jose-jwt/tree/master/JWT/jwe
# https://github.com/progrium/ruby-jwt
# https://github.com/jpadilla/pyjwt/
=pod
=head1 NAME
Crypt::JWT - JSON Web Token (JWT, JWS, JWE) as defined by RFC7519, RFC7515, RFC7516
=head1 SYNOPSIS
# encoding
use Crypt::JWT qw(encode_jwt);
my $jws_token = encode_jwt(payload=>$data, alg=>'HS256', key=>'secret');
my $jwe_token = encode_jwt(payload=>$data, alg=>'PBES2-HS256+A128KW', enc=>'A128GCM', key=>'secret');
# decoding
use Crypt::JWT qw(decode_jwt);
my $data1 = decode_jwt(token=>$jws_token, key=>'secret');
my $data2 = decode_jwt(token=>$jwe_token, key=>'secret');
=head1 DESCRIPTION
Implements B<JSON Web Token (JWT)> - L<https://tools.ietf.org/html/rfc7519>.
The implementation covers not only B<JSON Web Signature (JWS)> - L<https://tools.ietf.org/html/rfc7515>,
but also B<JSON Web Encryption (JWE)> - L<https://tools.ietf.org/html/rfc7516>.
The module implements all algorithms defined in L<https://tools.ietf.org/html/rfc7518> - B<JSON Web Algorithms (JWA)>.
This module supports B<Compact JWS/JWE> and B<Flattened JWS/JWE JSON> serialization. General (multi-recipient) JSON serialization is not supported.
=head1 EXPORT
Nothing is exported by default.
You can export selected functions:
use Crypt::JWT qw(decode_jwt encode_jwt);
Or all of them at once:
use Crypt::JWT ':all';
=head1 FUNCTIONS
=head2 decode_jwt
my $data = decode_jwt(%named_args);
my ($header, $data) = decode_jwt(%named_args, decode_header=>1);
Returns the decoded payload (in scalar context) or the decoded header
followed by the decoded payload (when C<decode_header =E<gt> 1>). Croaks
on any verification, decryption, or claim-check failure.
Named arguments:
=over
=item token
Mandatory. The serialized JWS or JWE token as a string. Both compact
(C<.>-separated, 3 segments for JWS / 5 for JWE) and flattened JSON
serialization are accepted.
### JWS compact (3 segments)
$t = "eyJhbGciOiJIUzI1NiJ9.dGVzdA.ujBihtLSr66CEWqN74SpLUkv28lra_CeHnxLmLNp4Jo";
my $data = decode_jwt(token=>$t, key=>$k);
### JWE compact (5 segments)
$t = "eyJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiQTEyOEtXIn0.UusxEbzhGkORxTRq0xkFKhvzPrXb9smw.VGfOuq0Fxt6TsdqLZUpnxw.JajIQQ.pkKZ7MHS0XjyGmRsqgom6w";
my $data = decode_jwt(token=>$t, key=>$k);
=item key
A key used for token decryption (JWE) or token signature validation (JWS).
The value depends on the C<alg> token header value.
B<Since: 0.038> B<SECURITY:> how the C<key> argument is shaped matters.
=over
=item *
A bare scalar (e.g. C<'secret'>) is always interpreted as a raw octet
string (HMAC secret, AES key, etc.).
=item *
PEM, DER, and JWK-JSON key material B<must> be passed as a SCALAR ref
(C<\$pem>) or as an appropriate key object - never as a bare string.
=item *
If a public-key string is mistakenly passed as a bare scalar and
C<accepted_alg> is not set, an attacker who flips the token's C<alg> to
C<HS*> can forge a signature using the public-key bytes as the HMAC
secret (the so-called "alg confusion" attack).
=item *
For defense in depth, B<always> pin the algorithm with C<accepted_alg>.
=back
Overview of supported keys:
JWS alg header key value
------------------ ----------------------------------
none no key required
HS256 string (raw octets) of any length (or perl HASH ref with JWK, kty=>'oct')
HS384 same as HS256
HS512 same as HS256
lib/Crypt/JWT.pm view on Meta::CPAN
ARRAY ref - list of accepted C<alg> names
=item *
C<Regexp> - the C<alg> value must match this regexp
=back
Example:
my $payload = decode_jwt(token=>$t, key=>$k, accepted_alg=>'HS256');
my $payload = decode_jwt(token=>$t, key=>$k, accepted_alg=>['HS256','HS384']);
my $payload = decode_jwt(token=>$t, key=>$k, accepted_alg=>qr/^HS(256|384|512)$/);
B<INCOMPATIBLE CHANGE Since: 0.038> Any other argument type (HASH ref,
CODE ref, GLOB ref, etc.) now croaks at decode time; previously such typos
silently became no-ops on the JWE side.
=item accepted_enc
JWE only. Restricts which content-encryption algorithms are accepted.
Accepted value types (same shape as L</accepted_alg>):
=over
=item *
C<undef> (default) - accept all C<enc> algorithms
=item *
Scalar string - the single accepted C<enc> name
=item *
ARRAY ref - list of accepted C<enc> names
=item *
C<Regexp> - the C<enc> value must match this regexp
=back
Example:
my $payload = decode_jwt(token=>$t, key=>$k, accepted_enc=>'A192GCM');
my $payload = decode_jwt(token=>$t, key=>$k, accepted_enc=>['A192GCM','A256GCM']);
my $payload = decode_jwt(token=>$t, key=>$k, accepted_enc=>qr/^A(128|192|256)GCM$/);
=item decode_payload
C<0> - do not decode payload, return it as a raw string (octets).
C<1> - decode payload from JSON string, return it as perl hash ref (or array ref) - decode_json failure means fatal error (croak).
C<undef> (default) - if possible decode payload from JSON string, if decode_json fails return payload as a raw string (octets).
=item decode_header
C<0> (default) - C<decode_jwt> returns just the decoded payload (scalar
context).
C<1> - C<decode_jwt> returns C<($header, $payload)>; useful when you need
to inspect the JWT header (e.g. C<alg>, C<kid>, C<typ>).
my $payload = decode_jwt(token=>$t, key=>$k);
my ($header, $payload) = decode_jwt(token=>$t, key=>$k, decode_header=>1);
=item verify_iss
B<INCOMPATIBLE CHANGE Since: 0.024> If C<verify_iss> is specified and the
C<iss> (Issuer) claim is completely missing, verification fails.
C<CODE ref> - subroutine (with 'iss' claim value passed as argument) should return C<true> otherwise verification fails
C<Regexp ref> - 'iss' claim value has to match given regexp otherwise verification fails
C<Scalar> - 'iss' claim value has to be equal to given string. B<Since: 0.029>
C<undef> (default) - do not verify 'iss' claim
=item verify_aud
B<INCOMPATIBLE CHANGE Since: 0.024> If C<verify_aud> is specified and the
C<aud> (Audience) claim is completely missing, verification fails.
C<CODE ref> - subroutine (with 'aud' claim value passed as argument) should return C<true> otherwise verification fails
C<Regexp ref> - 'aud' claim value has to match given regexp otherwise verification fails
C<Scalar> - 'aud' claim value has to be equal to given string. B<Since: 0.029>
C<undef> (default) - do not verify 'aud' claim
B<Since: 0.036> The C<aud> claim may also be an array of strings. The
check succeeds if at least one array element matches; the configured check
(CODE, Regexp, Scalar) is applied individually to each element.
=item verify_sub
B<INCOMPATIBLE CHANGE Since: 0.024> If C<verify_sub> is specified and the
C<sub> (Subject) claim is completely missing, verification fails.
C<CODE ref> - subroutine (with 'sub' claim value passed as argument) should return C<true> otherwise verification fails
C<Regexp ref> - 'sub' claim value has to match given regexp otherwise verification fails
C<Scalar> - 'sub' claim value has to be equal to given string. B<Since: 0.029>
C<undef> (default) - do not verify 'sub' claim
=item verify_jti
B<INCOMPATIBLE CHANGE Since: 0.024> If C<verify_jti> is specified and the
C<jti> (JWT ID) claim is completely missing, verification fails.
C<CODE ref> - subroutine (with 'jti' claim value passed as argument) should return C<true> otherwise verification fails
C<Regexp ref> - 'jti' claim value has to match given regexp otherwise verification fails
( run in 0.949 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )