Skip to content

Commit

Permalink
ref #2 more docs regarding JWTs and auto revoke issue
Browse files Browse the repository at this point in the history
  • Loading branch information
leejo committed Jun 25, 2015
1 parent 96313ad commit 3d72d89
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 37 deletions.
11 changes: 7 additions & 4 deletions Changes
@@ -1,11 +1,14 @@
Revision history for Mojolicious-Plugin-OAuth2-Server

0.20 2015-06-24
0.20 2015-06-25
- auth codes, access tokens, and refresh tokens returned can now be
JWTs (implemented via Mojo::JWT). this allows validation without
database lookup *should you want to do that*. see the examples,
tests, and perldoc. to get JWTs set the jwt_secret in the plugin
config
database lookup *should you want to do that*

- this allows the "simple" usage of the plugin to be persistent and
multi process compat by supplying a jwt_secret - although you lose
the automatic token revoking capabilities of the module when doing
this. see the examples, tests, and perldoc for more information

0.11 2015-03-19
- update examples/oauth2_client.pl to work with latest version of
Expand Down
37 changes: 23 additions & 14 deletions README.md
Expand Up @@ -79,7 +79,7 @@ Then in your controller:

This plugin implements the OAuth2 "Authorization Code Grant" flow as described
at [http://tools.ietf.org/html/rfc6749#section-4.1](http://tools.ietf.org/html/rfc6749#section-4.1). It is not a complete
implementation of RFC6749, as it is rather large in scope. However the extra
implementation of RFC6749, as that is rather large in scope. However the extra
functionality and flows may be added in the future.

This plugin enables you to easily (?) write an OAuth2 Authorization Server (AS)
Expand Down Expand Up @@ -108,6 +108,14 @@ can also see the tests and examples included with this distribution. OAuth2
seems needlessly complicated at first, hopefully this plugin will clarify the
various steps and simplify the implementation.

If you would still like to use the plugin in an easy way, but also have ACs and
ATs persistent across restarts and shared between multi processes then you can
supply a jwt\_secret. What you lose when doing this is the ability for tokens to
be revoked. You could implement the verify\_auth\_code and verify\_access\_token
methods to handle the revoking in your app. So that would be halfway between
the "simple" and the "realistic" way. ["CLIENT SECRET, TOKEN SECURITY, AND JWT"](#client-secret-token-security-and-jwt)
has more detail about JWTs.

Note that OAuth2 requires https, so you need to have the optional Mojolicious
dependency required to support it. Run the command below to check if
[IO::Socket::SSL](https://metacpan.org/pod/IO::Socket::SSL) is installed.
Expand Down Expand Up @@ -626,24 +634,25 @@ this information to validate an auth code / access token / refresh token without
doing a database lookup. However, it gets somewhat more complicated when you
need to revoke tokens. For more information about JWTs and revoking tokens see
[https://auth0.com/blog/2015/03/10/blacklist-json-web-token-api-keys/](https://auth0.com/blog/2015/03/10/blacklist-json-web-token-api-keys/) and
[https://tools.ietf.org/html/rfc7519](https://tools.ietf.org/html/rfc7519)
[https://tools.ietf.org/html/rfc7519](https://tools.ietf.org/html/rfc7519). Ultimately you're going to have to use
some shared store to revoke tokens, but using the jwt\_secret config setting means
you can simplify parts of the process as the JWT will contain the client, user,
and scope information (JWTs are also easy to debug: [http://jwt.io](http://jwt.io)).

When using JWTs expiry dates will automatically checked ([Mojo::JWT](https://metacpan.org/pod/Mojo::JWT) has this
When using JWTs expiry dates will be automatically checked ([Mojo::JWT](https://metacpan.org/pod/Mojo::JWT) has this
built in to the decoding) and the hash returned from the call to ->oauth will
look something like this:

{
'scopes' => [
'post_images',
'annoy_friends'
],
'iat' => 1435225100,
'type' => 'access', # type: auth, access, or refresh
'exp' => 1435228700,
'client' => 'TrendyNewService',
'user_id' => 'some user id', # as returned from verify_auth_code
'jti' => 'psclb1AcC2OjAKtVJRg1JjRJumkVTkDj',
'aud' => undef # redirect uri in case of type: auth
'iat' => 1435225100, # generation time
'exp' => 1435228700, # expiry time
'aud' => undef # redirect uri in case of type: auth
'jti' => 'psclb1AcC2OjAKtVJRg1JjRJumkVTkDj', # unique

'type' => 'access', # auth, access, or refresh
'scopes' => [ 'list','of','scopes' ], # as requested by client
'client' => 'some client id', # as returned from verify_auth_code
'user_id' => 'some user id', # as returned from verify_auth_code
};

Since a call for an access token requires both the authorization code and the
Expand Down
3 changes: 1 addition & 2 deletions examples/oauth2_server_simple_jwt.pl
Expand Up @@ -5,10 +5,9 @@

use Mojolicious::Lite;
use Mojo::JWT;
use Try::Tiny;

plugin 'OAuth2::Server' => {
jwt_secret => "Is it secret?, Is it safe?";
jwt_secret => "Is it secret?, Is it safe?",
clients => {
TrendyNewService => {
client_secret => 'boo',
Expand Down
45 changes: 30 additions & 15 deletions lib/Mojolicious/Plugin/OAuth2/Server.pm
Expand Up @@ -79,7 +79,7 @@ Then in your controller:
This plugin implements the OAuth2 "Authorization Code Grant" flow as described
at L<http://tools.ietf.org/html/rfc6749#section-4.1>. It is not a complete
implementation of RFC6749, as it is rather large in scope. However the extra
implementation of RFC6749, as that is rather large in scope. However the extra
functionality and flows may be added in the future.
This plugin enables you to easily (?) write an OAuth2 Authorization Server (AS)
Expand Down Expand Up @@ -108,6 +108,14 @@ can also see the tests and examples included with this distribution. OAuth2
seems needlessly complicated at first, hopefully this plugin will clarify the
various steps and simplify the implementation.
If you would still like to use the plugin in an easy way, but also have ACs and
ATs persistent across restarts and shared between multi processes then you can
supply a jwt_secret. What you lose when doing this is the ability for tokens to
be revoked. You could implement the verify_auth_code and verify_access_token
methods to handle the revoking in your app. So that would be halfway between
the "simple" and the "realistic" way. L<CLIENT SECRET, TOKEN SECURITY, AND JWT>
has more detail about JWTs.
Note that OAuth2 requires https, so you need to have the optional Mojolicious
dependency required to support it. Run the command below to check if
L<IO::Socket::SSL> is installed.
Expand Down Expand Up @@ -1126,7 +1134,13 @@ sub _verify_access_token_jwt {
return ( 0,'invalid_grant' );
};

if ( $access_token_payload ) {
if (
$access_token_payload
&& (
$access_token_payload->{type} eq 'access'
|| $is_refresh_token && $access_token_payload->{type} eq 'refresh'
)
) {

if ( $scopes_ref ) {
foreach my $scope ( @{ $scopes_ref // [] } ) {
Expand Down Expand Up @@ -1195,24 +1209,25 @@ this information to validate an auth code / access token / refresh token without
doing a database lookup. However, it gets somewhat more complicated when you
need to revoke tokens. For more information about JWTs and revoking tokens see
L<https://auth0.com/blog/2015/03/10/blacklist-json-web-token-api-keys/> and
L<https://tools.ietf.org/html/rfc7519>
L<https://tools.ietf.org/html/rfc7519>. Ultimately you're going to have to use
some shared store to revoke tokens, but using the jwt_secret config setting means
you can simplify parts of the process as the JWT will contain the client, user,
and scope information (JWTs are also easy to debug: L<http://jwt.io>).
When using JWTs expiry dates will automatically checked (L<Mojo::JWT> has this
When using JWTs expiry dates will be automatically checked (L<Mojo::JWT> has this
built in to the decoding) and the hash returned from the call to ->oauth will
look something like this:
{
'scopes' => [
'post_images',
'annoy_friends'
],
'iat' => 1435225100,
'type' => 'access', # type: auth, access, or refresh
'exp' => 1435228700,
'client' => 'TrendyNewService',
'user_id' => 'some user id', # as returned from verify_auth_code
'jti' => 'psclb1AcC2OjAKtVJRg1JjRJumkVTkDj',
'aud' => undef # redirect uri in case of type: auth
'iat' => 1435225100, # generation time
'exp' => 1435228700, # expiry time
'aud' => undef # redirect uri in case of type: auth
'jti' => 'psclb1AcC2OjAKtVJRg1JjRJumkVTkDj', # unique
'type' => 'access', # auth, access, or refresh
'scopes' => [ 'list','of','scopes' ], # as requested by client
'client' => 'some client id', # as returned from verify_auth_code
'user_id' => 'some user id', # as returned from verify_auth_code
};
Since a call for an access token requires both the authorization code and the
Expand Down
6 changes: 4 additions & 2 deletions t/050_jwt.t
Expand Up @@ -117,8 +117,10 @@ cmp_deeply(
'json_is_deeply'
);

my $decoded_access_token = Mojo::JWT->new( secret => $jwt_secret )->decode( $res->{access_token} );
my $decoded_refresh_token = Mojo::JWT->new( secret => $jwt_secret )->decode( $res->{refresh_token} );
my $decoded_access_token = Mojo::JWT->new( secret => $jwt_secret )
->decode( $res->{access_token} );
my $decoded_refresh_token = Mojo::JWT->new( secret => $jwt_secret )
->decode( $res->{refresh_token} );

cmp_deeply(
$decoded_access_token,
Expand Down
56 changes: 56 additions & 0 deletions t/060_jwt_with_revoke.t
@@ -0,0 +1,56 @@
#!perl

use strict;
use warnings;

use Mojolicious::Lite;
use Test::More;
use FindBin qw/ $Bin /;
use lib $Bin;
use AllTests;

MOJO_APP: {
# plugin configuration
plugin 'OAuth2::Server' => {
jwt_secret => 'prince edward island potatoes',
clients => {
1 => {
client_secret => 'boo',
scopes => {
eat => 1,
drink => 0,
sleep => 1,
},
},
},
};

group {
# /api - must be authorized
under '/api' => sub {
my ( $c ) = @_;
return 1 if $c->oauth;
$c->render( status => 401, text => 'Unauthorized' );
return undef;
};

get '/eat' => sub { shift->render( text => "food"); };
};

# /sleep - must be authorized and have sleep scope
get '/api/sleep' => sub {
my ( $c ) = @_;
$c->oauth( 'sleep' )
|| $c->render( status => 401, text => 'You cannot sleep' );

$c->render( text => "bed" );
};
};

AllTests::run({
skip_revoke_tests => 1,
});

done_testing();

# vim: ts=2:sw=2:et
2 changes: 2 additions & 0 deletions t/AllTests.pm
Expand Up @@ -188,6 +188,8 @@ sub run {
isnt( $t->tx->res->json->{access_token},$access_token,'new access_token' );
isnt( $t->tx->res->json->{refresh_token},$refresh_token,'new refresh_token' );

return if $args->{skip_revoke_tests};

my $new_access_token = $t->tx->res->json->{access_token};
my $new_refresh_token = $t->tx->res->json->{refresh_token};

Expand Down

0 comments on commit 3d72d89

Please sign in to comment.