Mojolicious app using google authentication, plugin OAuth2 - perl

I am using Mojolicious::Plugin::OAuth2 to build a simple app where you can log in using your google+ credentials, and I am having trouble with the syntax.
My code is very close to the example they give:
use Mojolicious::Lite;
use Mojolicious::Plugin::OAuth2;
plugin 'OAuth2' => {
google => {
key => 'xxxxxx.apps.googleusercontent.com',
secret => 'xxxxxxxx',
},
};
get "/auth" => sub {
my $self = shift;
$self->delay(
sub {
my $delay = shift;
$self->get_token(google => $delay->begin, scope->profile)
},
sub {
my($delay, $token, $tx) = #_;
return $self->render(text => $tx->res->error) unless $token;
$self->session(token => $token);
$self->render(text => $token);
},
);
};
app->start;
The problem area being the scope->profile. Without specifying the scope, I get an error from google saying "400: Error: invalid_request Missing required parameter: scope" but I can't quite get it right and now receive syntax errors.
The documentation says to do it like this $token = $c->get_token($provider_name => \%args); So how do I write that args hash so that it makes sense?

This syntax is what worked for me
$self->get_token('google', scope => 'profile', $delay->begin);
I had to put $delay->begin at the end and google in quotes.

Related

How do I get refresh tokens with Mojolicious::Plugin::OAuth2

I am a happy user of Mojolicious::Plugin::OAuth2, but there is a but: I can get an access token without a problem, but I have no idea on how to get a refresh one. The documentation is a bit terse and I could not find examples in the wild.
Currently I do this:
plugin OAuth2 => {
providers => {
google => {
key => 'somekey',
secret => 'somesecret',
redirect => 'http://localhost:3000/login/google',
access_type => 'offline',
scope => join ' ', qw|some scopes|,
}
}
};
get '/' => sub {
my $c = shift;
$c->render(template => 'login');
};
get '/done' => sub {
my $c = shift;
$c->render(text => 'done: ' . $c->session('token'));
};
get '/login/google' => sub {
my $c = shift;
my $otx = $c->render_later->tx;
my $args = { redirect_uri => 'http://localhost:3000/login/google' };
$c->oauth2->get_token_p(google => $args)
->then(sub {
my $otx = $otx;
return unless my $res = shift;
$c->session(token => $res->{access_token});
1;
})
->then(sub {
my $tx = shift;
my $ua = $c->app->ua;
my $url = 'https://www.googleapis.com/userinfo/v2/me';
my $tx = $ua->build_tx(GET => $url);
$tx->req->headers->authorization('Bearer ' . $c->session('token'));
return $ua->start_p($tx);
})
->then(sub {
my $tx = shift;
my $otx = $otx;
my $data = $tx->res->json;
$c->app->log->info($tx->res->body);
$c->app->log->info(dumper $tx->res->json);
$c->redirect_to('/done');
})
->catch(sub {
my $err = shift;
$c->log->info($err);
$c->render(text => $err);
});
};
(sorry for the dump) which is pretty much the standard flow for Mojolicious::Plugin::OAuth2.
The response from Google however does not contain any refresh token as far as I can see, nor can I figure out how to ask for one - inserting $c->oauth2->get_refresh_token_p($provider_name => \%args); somewhere in the middle gives me a bad request response.
So, how should I do this so it works ok?
If you set access_type => 'offline' when creating the OAuth2 plugin instance (as you did in your example), get_access_token_p() will return the refresh token (in addition to the access_token) as explained here. You should store the refresh token at a safe place. Then you can use it at a later time to refresh the access token (for example, if an api call returns an access token expired error). In that case you can call get_refresh_token_p() with the refresh token that you have already stored as a parameter.
Here, is an example:
use feature qw(say);
use strict;
use warnings;
use experimental qw(declared_refs refaliasing signatures);
use JSON;
use Mojolicious::Lite -signatures;
{
my #scopes = ('https://www.googleapis.com/auth/userinfo.email');
my $cred = read_credentials('credentials.json');
plugin OAuth2 => {
providers => {
google => {
# "offline": instructs the Google authorization server to also return a
# refresh token the first time the application exchanges an
# authorization code for tokens
access_type => 'offline',
key => $cred->{client_id},
secret => $cred->{client_secret},
}
}
};
# Note that this /login/google end point callback is called in two different cases:
# - the first case is when the user accesses the login page,
# - the second case is when the google authorization server redirects back
# to the redirect_uri parameter. In this case the "code" query parameter is
# set.
# The OAuth2 plugin can differentiate between these two cases, such that
# get_token_p() will behave correctly for each case..
#
get '/login/google' => sub {
my $c = shift;
my $app = $c->app;
my $args = {
redirect => 1, # tell get_token_p() to redirect to the current route
scope => (join ' ', #scopes),
};
$c->oauth2->get_token_p(google => $args)->then(
# This callback is for the second response from google aut. server,
# (the first response is handled by get_token_p() internally)
sub {
my $res = shift; # The response from the authorization server
$c->session(token => $res->{access_token});
$c->session(refresh_token => $res->{refresh_token});
#------------------------------------------
# This should log the refresh token
#------------------------------------------
$c->log->info('Refresh token: ' . $res->{refresh_token});
#------------------------------------------
my $ua = $app->ua;
my $url = 'https://www.googleapis.com/userinfo/v2/me';
my $tx = $ua->build_tx(GET => $url);
$tx->req->headers->authorization('Bearer ' . $c->session('token'));
return $ua->start_p($tx);
}
)->then(
sub {
my $tx = shift;
my $data = $tx->res->json;
#$app->log->info($app->dumper($data));
$c->session(user_email => $data->{email});
$c->redirect_to('/done');
}
)->catch(
sub {
my $err = shift;
$c->log->info($err);
$c->render(text => $err);
}
);
};
get '/google/refreshtoken' => sub {
my $c = shift;
my $app = $c->app;
my $refresh_token = $c->session('refresh_token');
if ($refresh_token) {
my $args = {
refresh_token => $refresh_token,
};
$c->oauth2->get_refresh_token_p(google => $args)->then(
sub {
my $res = shift;
# update stored access token
$c->session(token => $res->{access_token});
$c->render(template => 'refreshed');
}
);
}
else {
$c->render(text => "No refresh token stored. Please login first");
}
};
get '/' => sub {
my $c = shift;
$c->render(template => 'index');
};
get '/done' => sub {
my $c = shift;
$c->render(template => 'done');
};
app->start;
}
sub read_credentials( $fn ) {
open ( my $fh, '<', $fn ) or die "Could not open file '$fn': $!";
my $str = do { local $/; <$fh> };
close $fh;
my $cred = decode_json( $str );
return $cred->{installed};
}
__DATA__
## index.html.ep
<!DOCTYPE html>
<html>
<head><title>Testing mojolicious oauth2 refresh token...</title></head>
<body>
<h1>Please access route /login/google to start...</h1>
</body>
</html>
## done.html.ep
<!DOCTYPE html>
<html>
<head><title>Done testing mojolicious oauth2</title></head>
<body>
<h1>Done testing. User email: <%= $c->session('user_email') %></h1>
</body>
</html>
## refreshed.html.ep
<!DOCTYPE html>
<html>
<head><title>Refreshed token</title></head>
<body>
<h1>Refreshed token</h1>
</body>
</html>

What is the difference between two routes in Mojolicious?

I just create two routes:
$r->any ( '/api/v:api', [ api => qr/\d+/ ], { api => 1 } )->partial( 1 );
$r->under( '/api/v:api', [ api => qr/\d+/ ], { api => 1 } );
It seems both work same.
What is the difference under hood between them?
UPD
I have noticed that for first case (->any) link to static files are moved to /api/v1. This is noticeable when exception happen. mojo/debug template tries to load static files from /api/v1/... path and not /.... Why?
The under is called before anything else. You can place authentication code inside it.
use strict;
use warnings;
use Mojolicious::Lite;
under sub {
my $self = shift;
my $api_key = $self->req->url->to_abs->username;
my $api_secret = $self->req->url->to_abs->password;
if($api_key ne "Admin" && $api_secret ne "Password123") {
$self->res->headers->www_authenticate('Basic');
$self->render(text => 'Error: API-Credentials are wrong', status => 401);
# No routes will be executed
return undef;
}
# Authentication with success, execute other routes
return 1;
}
# Only executed if the url is http://Admin:Password123#127.0.0.1:3000
any '/' => sub {
my $self = shift;
my $date = Mojo::Date->new;
$self->render(text => $date, status => 200);
};
It's very important that you have to place under above the routines you want to protect.

What's the best practice for error handling with Catalyst::Controller::REST

I'm having a hard time trying to figure out a way to handle unexpected errors in my Catalyst::Controller::REST based API.
BEGIN { extends 'Catalyst::Controller::REST' }
__PACKAGE__->config(
json_options => { relaxed => 1, allow_nonref => 1 },
default => 'application/json',
map => { 'application/json' => [qw(View JSON)] },
);
sub default : Path : ActionClass('REST') { }
sub default_GET {
my ( $self, $c, $mid ) = #_;
### something happens here and it dies
}
If default_GET dies unexpectedly, the application standard status 500 error page is shown. I'd expect the REST library behind the controller to take control over it and show a JSON error (or whatever serialization response the REST request accepts).
Adding error control (with ie Try::Tiny) action by action is not an option. I'm looking to centralize all error handling. I've tried using an sub end action but that did not work.
sub error :Private {
my ( $self, $c, $code, $reason ) = #_;
$reason ||= 'Unknown Error';
$code ||= 500;
$c->res->status($code);
$c->stash->{data} = { error => $reason };
}
This is not a best practice. It's just how I would do it.
You can use Try::Tiny to catch the errors in your controller, and the helpers that Catalyst::Action::REST brings to send the appropriate response codes. It will take care of converting the response to the right format (i.e. JSON) for you.
But that still requires you to do it for each type of error. Basically it boils down to this:
use Try::Tiny;
BEGIN { extends 'Catalyst::Controller::REST' }
__PACKAGE__->config(
json_options => { relaxed => 1, allow_nonref => 1 },
default => 'application/json',
map => { 'application/json' => [qw(View JSON)] },
);
sub default : Path : ActionClass('REST') { }
sub default_GET {
my ( $self, $c, $mid ) = #_;
try {
# ... (there might be a $c->detach in here)
} catch {
# this is thrown by $c->detach(), so don't 400 in this case
return if $_->$_isa('Catalyst::Exception::Detach');
$self->status_bad_request( $c, message => q{Boom!} );
}
}
The methods for those kinds of responses are listed in Catalyst::Controller::REST under STATUS HELPERS. They are:
status_ok
status_created
status_accepted
status_no_content
status_multiple_choices
status_found
status_bad_request
status_forbidden
status_not_found
gone
status_see_other
status_moved
You can implement your own for the missing status1 by subclassing Catalyst::Controller::REST or by adding into its namespace. Refer to one of them for how they are constructed. Here's an example.
*Catalyst::Controller::REST::status_teapot = sub {
my $self = shift;
my $c = shift;
my %p = Params::Validate::validate( #_, { message => { type => SCALAR }, }, );
$c->response->status(418);
$c->log->debug( "Status I'm A Teapot: " . $p{'message'} ) if $c->debug;
$self->_set_entity( $c, { error => $p{'message'} } );
return 1;
}
If that is too tedious because you have a lot of actions, I suggest you make use of the end action like you intended. More to how that works a bit further below.
In that case, don't add the Try::Tiny construct to your actions. Instead, make sure that all of your models or other modules you are using throw good exceptions. Create exception classes for each of the cases, and hand the control of what is supposed to happen in which case to them.
A good way to do all this is to use Catalyst::ControllerRole::CatchErrors. It lets you define a catch_error method that will handle errors for you. In that method, you build a dispatch table that knows what exception should cause which kind of response. Also look at the documentation of $c->error as it has some valuable information here.
package MyApp::Controller::Root;
use Moose;
use Safe::Isa;
BEGIN { extends 'Catalyst::Controller::REST' }
with 'Catalyst::ControllerRole::CatchErrors';
__PACKAGE__->config(
json_options => { relaxed => 1, allow_nonref => 1 },
default => 'application/json',
map => { 'application/json' => [qw(View JSON)] },
);
sub default : Path : ActionClass('REST') { }
sub default_GET {
my ( $self, $c, $mid ) = #_;
$c->model('Foo')->frobnicate;
}
sub catch_errors : Private {
my ($self, $c, #errors) = #_;
# Build a callback for each of the exceptions.
# This might go as an attribute on $c in MyApp::Catalyst as well.
my %dispatch = (
'MyApp::Exception::BadRequest' => sub {
$c->status_bad_request(message => $_[0]->message);
},
'MyApp::Exception::Teapot' => sub {
$c->status_teapot;
},
);
# #errors is like $c->error
my $e = shift #errors;
# this might be a bit more elaborate
if (ref $e =~ /^MyAPP::Exception/) {
$dispatch{ref $e}->($e) if exists $dispatch{ref $e};
$c->detach;
}
# if not, rethrow or re-die (simplified)
die $e;
}
The above is a crude, untested example. It might not work like this exactly, but it's a good start. It would make sense to move the dispatching into an attribute of your main Catalyst application object (the context, $c). Place it in MyApp::Catalyst to do that.
package MyApp::Catalyst;
# ...
has error_dispatch_table => (
is => 'ro',
isa => 'HashRef',
traits => 'Hash',
handles => {
can_dispatch_error => 'exists',
dispatch_error => 'get',
},
builder => '_build_error_dispatch_table',
);
sub _build_error_dispatch_table {
return {
'MyApp::Exception::BadRequest' => sub {
$c->status_bad_request(message => $_[0]->message);
},
'MyApp::Exception::Teapot' => sub {
$c->status_teapot;
},
};
}
And then do the dispatching like this:
$c->dispatch_error(ref $e)->($e) if $c->can_dispatch_error(ref $e);
Now all you need is good exceptions. There are different ways to do those. I like Exception::Class or Throwable::Factory.
package MyApp::Model::Foo;
use Moose;
BEGIN { extends 'Catalyst::Model' };
# this would go in its own file for reusability
use Exception::Class (
'MyApp::Exception::Base',
'MyApp::Exception::BadRequest' => {
isa => 'MyApp::Exception::Base',
description => 'This is a 400',
fields => [ 'message' ],
},
'MyApp::Exception::Teapot' => {
isa => 'MyApp::Exception::Base',
description => 'I do not like coffee',
},
);
sub frobnicate {
my ($self) = #_;
MyApp::Exception::Teapot->throw;
}
Again, it would make sense to move the exceptions into their own module so you can reuse them everywhere.
This can be nicely extended I believe.
Also keep in mind that coupling the business logic or models too strongly to the fact that it's a web app is be bad design. I chose very speaking exception names because it's easy to explain that way. You might want to just more generic or rather less web centric names, and your dispatch thingy should take of actually mapping them. Otherwise it's too much tied to the web layer.
1) Yes, this is the plural. See here.

Rendering errors in proper format inside under on Mojolicious::Lite

I have an under statement that may generate and error (for instance, authentication error). I use content negotiation all over, and I'd like to return the error inside the under in a proper format. An example code:
under sub {
my $self = shift;
# Authenticated
my $token = $self->param('token') || '';
return 1 if $token eq '123456';
# Not authenticated
$self->respond_to(
json => {
json => { error => 'Invalid authentication token.' },
status => 401
},
text => {
text => 'Unauthorized.',
status => 401
}
);
return undef;
}
I can use render inside under, but respond_to won't work. Probably under work for that. But in that case, what should I do?
In Galileo I have an auth_fail helper which does something like this. While I work out an example, see that code (and the if_author and if_admin unders).
Ok here is an example, the trick turned out to be (at least for this mechanism) before you can redirect to your failure handler, you need to flash the format, which make it available to the next handler.
#!/usr/bin/env perl
use Mojolicious::Lite;
any '/fail' => sub {
my $self = shift;
$self->respond_to(
json => {
json => { error => 'Invalid authentication token.' },
status => 401
},
text => {
text => 'Unauthorized.',
status => 401
}
);
};
under sub {
my $self = shift;
# Authenticated
my $token = $self->param('token') || '';
return 1 if $token eq '123456';
# Not authenticated
$self->flash( format => $self->param('format') );
$self->redirect_to('fail');
return undef;
};
any '/private' => sub {
my $self = shift;
$self->respond_to(
json => {
json => { launch_codes => '9999999' },
},
text => {
text => 'Launch Code: 9999999',
}
);
};
app->start;

Is it possible to overwrite the response data in a Mojo::Message::Response?

In a Mojolicious app I have a route in my Controller code like the following:
/account/:id/users
The /account/:id part of the route has the following data in it when I get to the
users part of the chain:
$VAR1 = {
'signup_ip' => '172.17.5.146',
'z_id' => '382C58D8-529E-11E1-BDFB-A44585CCC763',
'signup_date' => '2012-03-12T12:11:10Z',
'name' => 'Some Cool Account Name',
'users' => [
{
'user_id' => '382C67EC-529E-11E1-BDFB-A44585CCC763'
}
],
'account_id' => '382C67EC-529E-11E1-BDFB-A44585CCC763',
};
In the users part of the chain I'm getting the above hash using
$self->tx->res->content->get_body_chunk(0)
sub users {
my $self = shift;
my $user_list = from_json( $self->tx->res->content->get_body_chunk(0) );
$self->respond_to( json => $user_list->{users} );
}
The problem I'm having is that I want to overwrite the response with only
the users arrayref. The code above in sub users(){} doesn't do that. That is,
when I dump the result in the test, I still getting the entire hash.
The $user_list is the arrayref I'm looking for in users() but I'm unable to overwrite it.
Anyone have an idea how to do that?
Hrm I think I put my previous answer in the wrong place. So here it is:
In the application I added the following routes:
my $base = $r->bridge('/account/:id')->to('account#read');
$base->route('/')->via('get')->to('account#index');
$base->route('/users')->via('get')->to('account#users');
In Acount.pm
sub read {
my $self = shift;
# do stuff
$self->stash->{account} = $data; # set the stash
return 1; #return 1. Don't render.
}
sub index {
my $self = shift;
my $data = $self->stash('account'); #get the stash
$self->render_json( $data );
}
sub users {
my $self = shift;
# do stuff
my $data = $self->stash('account');
$self->render_json( $data );
}
Doing this sets the result of /account/:id into the stash in the read sub.
Setting a route to $base->route('/')->via('get')->to('account#index');
causes calls to /account/:id to be rendered from the index sub.
The route $base->route('/users')->via('get')->to('account#users') causes
the calls to /account/:id/users to be rendered from the users sub.
I think you have to provide different parameters to respond_to method. I would expect this to work:
$self->respond_to(json => { json => $user_list->{users} });
Or just call render_json:
$self->render_json($user_list->{users});
Edit: I made simple testing script that works for me (using latter option above):
use Mojolicious::Lite;
get '/account/users' => sub {
my $self = shift;
my $user_list = {
'signup_ip' => '172.17.5.146',
'z_id' => '382C58D8-529E-11E1-BDFB-A44585CCC763',
'signup_date' => '2012-03-12T12:11:10Z',
'name' => 'Some Cool Account Name',
'users' => [{'user_id' => '382C67EC-529E-11E1-BDFB-A44585CCC763'}],
'account_id' => '382C67EC-529E-11E1-BDFB-A44585CCC763',
};
$self->render_json($user_list->{users});
};
app->start;
the request to http://localhost:3000/account/users returned this:
[{"user_id":"382C67EC-529E-11E1-BDFB-A44585CCC763"}]