Add google:JWT
[scpubgit/stemmaweb.git] / lib / Google / JWT.pm
1 package Google::JWT;
2
3 use Crypt::OpenSSL::X509;
4 use JSON::WebToken;
5 use IO::All;
6 use JSON::MaybeXS;
7 use MIME::Base64;
8 use LWP::Simple qw(get);
9 use Date::Parse qw(str2time);
10
11 use warnings;
12 use strict;
13 use strictures 1;
14
15 =head1 NAME
16
17 Google::JWT - JSON Web Token handler for Google tokens.
18
19 =head1 SYNOPSIS
20
21 my $tok = Google->JWT->decode($token);
22
23 =head1 DESCRIPTION
24
25 Retrieves Google's public certificates, and then retrieves the key from the
26 cert using L<Crypt::OpenSSL::X509>. Finally, uses the pubkey to decrypt a
27 Google token using L<JSON::WebToken>.
28
29 =cut
30
31 =head1 METHODS
32
33 =head2 retrieve_certs
34
35 Retrieves a pair of JSON-encoded certificates from the given URL (defaults to
36 Google's public cert url), and returns the decoded JSON object.
37
38 =head3 ARGUMENTS
39
40 =over
41
42 =item url
43
44 Optional. Location where certificates are located.
45 Defaults to https://www.googleapis.com/oauth2/v1/certs.
46
47 =back
48
49 =head3 RETURNS
50
51 Decoded JSON object containing certificates.
52
53 =cut
54
55 sub retrieve_certs {
56     my ($self, $url) = @_;
57
58     $url ||= 'https://www.googleapis.com/oauth2/v1/certs';
59     return decode_json(get($url));
60 }
61
62 =head2 get_key_from_cert
63
64 Given a pair of certificates $certs (defaults to L</retrieve_certs>),
65 this function returns the public key of the cert identified by $kid.
66
67 =head3 ARGUMENTS
68
69 =over
70
71 =item $kid
72
73 Required. Index of the certificate hash $hash where the cert we want is
74 located.
75
76 =item $certs
77
78 Optional. A (hashref) pair of certificates.
79 It's retrieved using L</retrieve_certs> if not given,
80 or if the pair is expired.
81
82 =back
83
84 =head3 RETURNS
85
86 Public key of certificate.
87
88 =cut
89
90 sub get_key_from_cert {
91     my ($self, $kid, $certs) = @_;
92
93     $certs ||= $self->retrieve_certs;
94     my $cert = $certs->{$kid};
95     my $x509 = Crypt::OpenSSL::X509->new_from_string($cert);
96
97     if ($self->is_cert_expired($x509)) {
98         # If we ended up here, we were given
99         # an old $certs string from the user.
100         # Let's force getting another.
101         return $self->get_key_from_cert($kid);
102     }
103
104     return $x509->pubkey;
105 }
106
107 =head2 is_cert_expired
108
109 Returns if a given L<Crypt::OpenSSL::X509> certificate is expired.
110
111 =cut
112
113 sub is_cert_expired {
114     my ($self, $x509) = @_;
115
116     my $expiry = str2time($x509->notAfter);
117
118     return time > $expiry;
119 }
120
121 =head2 decode
122
123 Returns the decoded information contained in a user's token.
124
125 =head3 ARGUMENTS
126
127 =over
128
129 =item $token
130
131 Required. The user's token from Google+.
132
133 =item $pubkey
134
135 Optional. A public key string with which to decode the token.
136 If not given, the public key will be retrieved from $certs.
137
138 =item $certs
139
140 Optional. A pair of public key certs retrieved from Google.
141 If not given, or if the certificates have expired, a new
142 pair of certificates is retrieved.
143
144 =back
145
146 =head2 RETURNS
147
148 Decoded JSON object from the decrypted token.
149
150 =cut
151
152 sub decode {
153     my ($self, $token, $certs, $pubkey) = @_;
154
155     if (!$pubkey) {
156         my $details = decode_json(
157             MIME::Base64::decode_base64(
158                 substr( $token, 0, CORE::index($token, '.') )
159             )
160         );
161
162         my $kid = $details->{kid};
163         $pubkey = $self->get_key_from_cert($kid, $certs);
164     }
165
166     return JSON::WebToken->decode($token, $pubkey);
167 }
168
169 =head1 AUTHOR
170
171 Errietta Kostala <e.kostala@shadowcat.co.uk>
172
173 =head1 LICENSE
174
175 This library is free software. You can redistribute it and/or modify
176 it under the same terms as Perl itself.
177
178 =cut
179
180 1;