Skip to content

Commit

Permalink
ref #2 make plugin use JWTs in basic usage jwt_secret set
Browse files Browse the repository at this point in the history
makes the tokens persistent and also multi proc safe, but loses
the ability to revoke tokens (...trade offs)
  • Loading branch information
leejo committed Jun 25, 2015
1 parent 23c0642 commit 96313ad
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 101 deletions.
99 changes: 9 additions & 90 deletions examples/oauth2_server_simple_jwt.pl
Original file line number Diff line number Diff line change
Expand Up @@ -7,98 +7,17 @@
use Mojo::JWT;
use Try::Tiny;

my $jwt_secret = "Is it secret?, Is it safe?";
my $oauth2_clients = {
TrendyNewService => {
client_secret => 'boo',
scopes => {
"post_images" => 1,
"annoy_friends" => 1,
plugin 'OAuth2::Server' => {
jwt_secret => "Is it secret?, Is it safe?";
clients => {
TrendyNewService => {
client_secret => 'boo',
scopes => {
"post_images" => 1,
"annoy_friends" => 1,
},
},
},
};

my $verify_auth_code_sub = sub {
my ( $c,$client_id,$client_secret,$auth_code,$uri ) = @_;

my $client = $oauth2_clients->{$client_id}
|| return ( 0,'unauthorized_client' );

return ( 0,'invalid_grant' )
if ( $client_secret ne $client->{client_secret} );

my $auth_code_payload;

try {
$auth_code_payload = Mojo::JWT->new( secret => $jwt_secret )
->decode( $auth_code );
} catch {
chomp;
$c->app->log->error( 'OAuth2::Server Auth code: ' . $_ );
return ( 0,'invalid_grant' );
};

if (
! $auth_code_payload
or $auth_code_payload->{type} ne 'auth'
or $auth_code_payload->{client} ne $client_id
or ( $uri && $auth_code_payload->{aud} ne $uri )
) {
return ( 0,'invalid_grant' );
}

# scopes are those that were requested in the authorization request, not
# those stored in the client (i.e. what the auth request restriced scopes
# to and not everything the client is capable of)
my $scope = $auth_code_payload->{scopes};

# some user id - $c->session( 'user_id' ) or whatever your equivalent is
return ( $client_id,undef,$scope,'some user id' );
};

my $verify_access_token_sub = sub {
my ( $c,$access_token,$scopes_ref,$is_refresh_token ) = @_;

my $access_token_payload;

try {
$access_token_payload = Mojo::JWT->new( secret => $jwt_secret )
->decode( $access_token );
} catch {
chomp;
$c->app->log->error( 'OAuth2::Server Access token: ' . $_ );
return ( 0,'invalid_grant' );
};

if ( $access_token_payload ) {

if ( $scopes_ref ) {
foreach my $scope ( @{ $scopes_ref // [] } ) {
if ( ! grep { $_ eq $scope } @{ $access_token_payload->{scopes} } ) {
$c->app->log->debug(
"OAuth2::Server: Access token does not have scope ($scope)"
);
return ( 0,'invalid_grant' );
}
}
}

return $access_token_payload;
}

$c->app->log->debug( "OAuth2::Server: Access token does not exist" );
return 0;
};

plugin 'OAuth2::Server' => {
jwt_secret => $jwt_secret,
# because we are using JWTs in this example we only need
# to verify codes/tokens and don't need to store them
verify_auth_code => $verify_auth_code_sub,
verify_access_token => $verify_access_token_sub,
store_auth_code => sub {},
store_access_token => sub {},
clients => $oauth2_clients,
};

group {
Expand Down
84 changes: 81 additions & 3 deletions lib/Mojolicious/Plugin/OAuth2/Server.pm
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ use Time::HiRes qw/ gettimeofday /;
use MIME::Base64 qw/ encode_base64 decode_base64 /;
use Carp qw/ croak /;
use Crypt::PRNG qw/ random_string /;
use Try::Tiny;

our $VERSION = '0.20';

Expand Down Expand Up @@ -396,7 +397,8 @@ sub _access_token_request {
);
$old_refresh_token = $refresh_token;
} else {
my $verify_auth_code_sub = $config->{verify_auth_code} // \&_verify_auth_code;
my $verify_auth_code_sub = $config->{verify_auth_code}
// \&_verify_auth_code;
( $client,$error,$scope,$user_id ) = $verify_auth_code_sub->(
$self,$client_id,$client_secret,$auth_code,$url
);
Expand Down Expand Up @@ -481,8 +483,8 @@ sub _token {
sub _verify_access_token_and_scope {
my ( $app,$config,$refresh_token,$c,@scopes ) = @_;

my $verify_access_token_sub
= $config->{verify_access_token} // \&_verify_access_token;
my $verify_access_token_sub = $config->{verify_access_token}
// \&_verify_access_token;

my $access_token;

Expand Down Expand Up @@ -689,6 +691,8 @@ the verify_auth_code callback for verification:
=cut

sub _store_auth_code {
return if $JWT_SECRET;

my ( $c,$auth_code,$client_id,$expires_in,$uri,@scopes ) = @_;

$AUTH_CODES{$auth_code} = {
Expand Down Expand Up @@ -765,6 +769,8 @@ be a user identifier:
=cut

sub _verify_auth_code {
return _verify_auth_code_jwt( @_ ) if $JWT_SECRET;

my ( $c,$client_id,$client_secret,$auth_code,$uri ) = @_;

my ( $sec,$usec,$rand ) = split( '-',decode_base64( $auth_code ) );
Expand Down Expand Up @@ -813,6 +819,40 @@ sub _verify_auth_code {

}

sub _verify_auth_code_jwt {
my ( $c,$client_id,$client_secret,$auth_code,$uri ) = @_;

my $client = $CLIENTS{$client_id}
|| return ( 0,'unauthorized_client' );

return ( 0,'invalid_grant' )
if ( $client_secret ne $client->{client_secret} );

my $auth_code_payload;

try {
$auth_code_payload = Mojo::JWT->new( secret => $JWT_SECRET )
->decode( $auth_code );
} catch {
chomp;
$c->app->log->error( 'OAuth2::Server Auth code: ' . $_ );
return ( 0,'invalid_grant' );
};

if (
! $auth_code_payload
or $auth_code_payload->{type} ne 'auth'
or $auth_code_payload->{client} ne $client_id
or ( $uri && $auth_code_payload->{aud} ne $uri )
) {
return ( 0,'invalid_grant' );
}

my $scope = $auth_code_payload->{scopes};

return ( $client_id,undef,$scope,undef );
}

=head2 store_access_token
A callback to allow you to store the generated access and refresh tokens. The
Expand Down Expand Up @@ -902,6 +942,8 @@ the verify_access_token callback for verification:
=cut

sub _store_access_token {
return if $JWT_SECRET;

my (
$c,$c_id,$auth_code,$access_token,$refresh_token,
$expires_in,$scope,$old_refresh_token
Expand Down Expand Up @@ -1019,6 +1061,8 @@ message (almost certainly 'invalid_grant' in this case)
=cut

sub _verify_access_token {
return _verify_access_token_jwt( @_ ) if $JWT_SECRET;

my ( $c,$access_token,$scopes_ref,$is_refresh_token ) = @_;

if (
Expand Down Expand Up @@ -1068,6 +1112,40 @@ sub _verify_access_token {
return ( 0,'invalid_grant' )
}

sub _verify_access_token_jwt {
my ( $c,$access_token,$scopes_ref,$is_refresh_token ) = @_;

my $access_token_payload;

try {
$access_token_payload = Mojo::JWT->new( secret => $JWT_SECRET )
->decode( $access_token );
} catch {
chomp;
$c->app->log->error( 'OAuth2::Server Access token: ' . $_ );
return ( 0,'invalid_grant' );
};

if ( $access_token_payload ) {

if ( $scopes_ref ) {
foreach my $scope ( @{ $scopes_ref // [] } ) {
if ( ! grep { $_ eq $scope } @{ $access_token_payload->{scopes} } ) {
$c->app->log->debug(
"OAuth2::Server: Access token does not have scope ($scope)"
);
return ( 0,'invalid_grant' );
}
}
}

return $access_token_payload;
}

$c->app->log->debug( "OAuth2::Server: Access token does not exist" );
return 0;
}

1;

=head1 PUTTING IT ALL TOGETHER
Expand Down
16 changes: 8 additions & 8 deletions t/050_jwt.t
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,10 @@ cmp_deeply(
'exp' => re( '^\d{10}$' ),
'iat' => re( '^\d{10}$' ),
'jti' => re( '^.{32}$' ),
'scopes' => {
eat => 1,
sleep => 1,
}
'scopes' => [
'eat',
'sleep',
]
},
'decoded JWT (access token)',
);
Expand All @@ -147,10 +147,10 @@ cmp_deeply(
'user_id' => undef,
'iat' => re( '^\d{10}$' ),
'jti' => re( '^.{32}$' ),
'scopes' => {
eat => 1,
sleep => 1,
}
'scopes' => [
'eat',
'sleep',
]
},
'decoded JWT (refresh token)',
);
Expand Down

0 comments on commit 96313ad

Please sign in to comment.