File Coverage

blib/lib/Net/OAuth2/AuthorizationServer/AuthorizationCodeGrant.pm
Criterion Covered Total %
statement 60 61 98.3
branch 14 14 100.0
condition 30 43 69.7
subroutine 17 18 94.4
pod 0 3 0.0
total 121 139 87.0


line stmt bran cond sub pod time code
1             package Net::OAuth2::AuthorizationServer::AuthorizationCodeGrant;
2              
3             =head1 NAME
4              
5             Net::OAuth2::AuthorizationServer::AuthorizationCodeGrant - OAuth2 Authorization Code Grant
6              
7             =head1 SYNOPSIS
8              
9             my $Grant = Net::OAuth2::AuthorizationServer::AuthorizationCodeGrant->new(
10             clients => {
11             TrendyNewService => {
12             client_secret => 'TopSecretClientSecret',
13             scopes => {
14             post_images => 1,
15             annoy_friends => 1,
16             },
17             },
18             }
19             );
20              
21             # verify a client against known clients
22             my ( $is_valid,$error ) = $Grant->verify_client(
23             client_id => $client_id,
24             scopes => [ qw/ list of scopes / ],
25             );
26              
27             if ( ! $Grant->login_resource_owner ) {
28             # resource owner needs to login
29             ...
30             }
31              
32             # have resource owner confirm (and perhaps modify) scopes
33             my ( $confirmed,$error,$scopes_ref ) = $Grant->confirm_by_resource_owner(
34             client_id => $client_id,
35             scopes => [ qw/ list of scopes / ],
36             );
37              
38             # generate a token
39             my $token = $Grant->token(
40             client_id => $client_id,
41             scopes => $scopes_ref,
42             type => 'auth', # one of: auth, access, refresh
43             redirect_uri => $redirect_uri,
44             user_id => $user_id, # optional
45             jwt_claims_cb => sub { ... }, # optional, see jwt_claims_cb in Manual
46             );
47              
48             # store the auth code
49             $Grant->store_auth_code(
50             auth_code => $auth_code,
51             client_id => $client_id,
52             redirect_uri => $uri,
53             scopes => $scopes_ref,
54             );
55              
56             # verify an auth code
57             my ( $client,$error,$scope,$user_id ) = $Grant->verify_auth_code(
58             client_id => $client_id,
59             client_secret => $client_secret,
60             auth_code => $auth_code,
61             redirect_uri => $uri,
62             );
63              
64             # store access token
65             $Grant->store_access_token(
66             client_id => $client,
67             auth_code => $auth_code,
68             access_token => $access_token,
69             refresh_token => $refresh_token,
70             scopes => $scopes_ref,
71             old_refresh_token => $old_refresh_token,
72             );
73              
74             # verify an access token
75             my ( $is_valid,$error ) = $Grant->verify_access_token(
76             access_token => $access_token,
77             scopes => [ qw/ list of scopes / ],
78             is_refresh_token => 0,
79             );
80              
81             # or:
82             my ( $client,$error,$scope,$user_id ) = $Grant->verify_token_and_scope(
83             refresh_token => $refresh_token,
84             auth_header => $http_authorization_header,
85             );
86              
87             =head1 DESCRIPTION
88              
89             This module implements the OAuth2 "Authorization Code Grant" flow as described
90             at L.
91              
92             =head1 CONSTRUCTOR ARGUMENTS
93              
94             Along with those detailed at L
95             the following are supported by this grant type:
96              
97             =head2 auth_code_ttl
98              
99             The validity period of the generated authorization code in seconds. Defaults to
100             600 seconds (10 minutes)
101              
102             =head1 CALLBACK FUNCTIONS
103              
104             The following callbacks are supported by this grant type:
105              
106             verify_client_cb
107             login_resource_owner_cb
108             confirm_by_resource_owner_cb
109             store_auth_code_cb
110             verify_auth_code_cb
111             store_access_token_cb
112             verify_access_token_cb
113              
114             Please see L for
115             documentation on each callback function.
116              
117             =cut
118              
119 3     3   1695 use strict;
  3         8  
  3         97  
120 3     3   17 use warnings;
  3         7  
  3         87  
121              
122 3     3   1166 use Moo;
  3         23901  
  3         21  
123             with 'Net::OAuth2::AuthorizationServer::Defaults';
124              
125 3     3   4755 use Types::Standard qw/ :all /;
  3         158625  
  3         55  
126 3     3   148616 use Carp qw/ croak /;
  3         13  
  3         272  
127 3     3   2198 use MIME::Base64 qw/ decode_base64 /;
  3         2512  
  3         204  
128 3     3   1297 use Crypt::JWT qw/ decode_jwt /;
  3         112179  
  3         223  
129 3     3   1759 use Try::Tiny;
  3         4525  
  3         2903  
130              
131             has 'auth_code_ttl' => (
132             is => 'ro',
133             isa => Int,
134             required => 0,
135             default => sub { 600 },
136             );
137              
138             has 'auth_codes' => (
139             is => 'ro',
140             isa => Maybe [HashRef],
141             required => 0,
142             default => sub { {} },
143             );
144              
145             has [
146             qw/
147             store_auth_code_cb
148             verify_auth_code_cb
149             /
150             ] => (
151             is => 'ro',
152             isa => Maybe [CodeRef],
153             required => 0,
154             );
155              
156 16     16   71 sub _uses_auth_codes { 1 };
157 0     0   0 sub _uses_user_passwords { 0 };
158              
159             sub BUILD {
160 6     6 0 393 my ( $self, $args ) = @_;
161              
162 6 100 66     31 if (
163             # if we don't have a list of clients
164             !$self->_has_clients
165              
166             # we must know how to verify clients and tokens
167             and ( !$args->{ verify_client_cb }
168             and !$args->{ store_auth_code_cb }
169             and !$args->{ verify_auth_code_cb }
170             and !$args->{ store_access_token_cb }
171             and !$args->{ verify_access_token_cb } )
172             )
173             {
174 1         23 croak __PACKAGE__ . " requires either clients or overrides";
175             }
176             }
177              
178             sub store_auth_code {
179 4     4 0 23 _delegate_to_cb_or_private( 'store_auth_code', @_ );
180             }
181              
182             sub verify_auth_code {
183 22     22 0 25930 _delegate_to_cb_or_private( 'verify_auth_code', @_ );
184             }
185              
186             sub _store_auth_code {
187 4     4   27 my ( $self, %args ) = @_;
188              
189             my ( $auth_code, $client_id, $expires_in, $uri, $scopes_ref ) =
190 4         22 @args{ qw/ auth_code client_id expires_in redirect_uri scopes / };
191              
192 4 100       33 return 1 if $self->jwt_secret;
193              
194 2   33     16 $expires_in //= $self->auth_code_ttl;
195              
196 2         15 $self->auth_codes->{ $auth_code } = {
197             client_id => $client_id,
198             expires => time + $expires_in,
199             redirect_uri => $uri,
200             scope => $scopes_ref,
201             };
202              
203 2         15 return 1;
204             }
205              
206             sub _verify_auth_code {
207 22     22   119 my ( $self, %args ) = @_;
208              
209             my ( $client_id, $client_secret, $auth_code, $uri ) =
210 22         63 @args{ qw/ client_id client_secret auth_code redirect_uri / };
211              
212 22   100     119 my $client = $self->clients->{ $client_id }
213             || return ( 0, 'unauthorized_client' );
214              
215 18 100       71 return $self->_verify_auth_code_jwt( %args ) if $self->jwt_secret;
216              
217 10         63 my ( $sec, $usec, $rand ) = split( '-', decode_base64( $auth_code ) );
218              
219 10 100 66     138 if ( !exists( $self->auth_codes->{ $auth_code } )
      100        
      100        
      66        
      100        
      66        
220             or !exists( $self->clients->{ $client_id } )
221             or ( $client_secret ne $self->clients->{ $client_id }{ client_secret } )
222             or $self->auth_codes->{ $auth_code }{ access_token }
223             or ( $uri && $self->auth_codes->{ $auth_code }{ redirect_uri } ne $uri )
224             or ( $self->auth_codes->{ $auth_code }{ expires } <= time ) )
225             {
226              
227 8 100       32 if ( my $access_token = $self->auth_codes->{ $auth_code }{ access_token } ) {
228              
229             # this auth code has already been used to generate an access token
230             # so we need to revoke the access token that was previously generated
231 2         19 $self->_revoke_access_token( $access_token );
232             }
233              
234 8         51 return ( 0, 'invalid_grant' );
235             }
236             else {
237 2         5 return ( 1, undef, @{ $self->auth_codes->{ $auth_code } }{ qw/ scope user_id / } );
  2         28  
238             }
239              
240             }
241              
242             sub _verify_auth_code_jwt {
243 8     8   31 my ( $self, %args ) = @_;
244              
245             my ( $client_id, $client_secret, $auth_code, $uri ) =
246 8         22 @args{ qw/ client_id client_secret auth_code redirect_uri / };
247              
248 8   50     26 my $client = $self->clients->{ $client_id }
249             || return ( 0, 'unauthorized_client' );
250              
251             return ( 0, 'invalid_grant' )
252 8 100       38 if ( $client_secret ne $client->{ client_secret } );
253              
254 6         13 my ( $auth_code_payload,$invalid_jwt );
255              
256             try {
257 6     6   485 $auth_code_payload =
258             decode_jwt(
259             alg => $self->jwt_algorithm,
260             key => $self->jwt_secret,
261             token => $auth_code,
262             );
263             }
264             catch {
265 2     2   911 $invalid_jwt = 1;
266 6         51 };
267              
268 6 100 66     901 if ( !$auth_code_payload
      66        
      33        
      66        
      66        
269             or $invalid_jwt
270             or $auth_code_payload->{ type } ne 'auth'
271             or $auth_code_payload->{ client } ne $client_id
272             or ( $uri && $auth_code_payload->{ aud } ne $uri ) )
273             {
274 4         39 return ( 0, 'invalid_grant' );
275             }
276              
277 2         17 my $scope = $auth_code_payload->{ scopes };
278 2         5 my $user_id = $auth_code_payload->{ user_id };
279              
280 2         23 return ( $client_id, undef, $scope, $user_id );
281             }
282              
283             =head1 AUTHOR
284              
285             Lee Johnson - C
286              
287             =head1 LICENSE
288              
289             This library is free software; you can redistribute it and/or modify it under
290             the same terms as Perl itself. If you would like to contribute documentation
291             or file a bug report then please raise an issue / pull request:
292              
293             https://github.com/Humanstate/net-oauth2-authorizationserver
294              
295             =cut
296              
297             __PACKAGE__->meta->make_immutable;