Giter Site home page Giter Site logo

mojolicious-plugin-oauth2's Introduction

NAME

Mojolicious::Plugin::OAuth2 - Auth against OAuth2 APIs including OpenID Connect

SYNOPSIS

Example application

use Mojolicious::Lite;

plugin OAuth2 => {
  providers => {
    facebook => {
      key    => 'some-public-app-id',
      secret => $ENV{OAUTH2_FACEBOOK_SECRET},
    },
  },
};

get '/connect' => sub {
  my $c         = shift;
  my %get_token = (redirect_uri => $c->url_for('connect')->userinfo(undef)->to_abs);

  return $c->oauth2->get_token_p(facebook => \%get_token)->then(sub {
    # Redirected to Facebook
    return unless my $provider_res = shift;

    # Token received
    $c->session(token => $provider_res->{access_token});
    $c->redirect_to('profile');
  })->catch(sub {
    $c->render('connect', error => shift);
  });
};

See "register" for more details about the configuration this plugin takes.

Testing

Code using this plugin can perform offline testing, using the "mocked" provider:

$app->plugin(OAuth2 => {mocked => {key => 42}});
$app->routes->get('/profile' => sub {
  my $c = shift;

  state $mocked = $ENV{TEST_MOCKED} && 'mocked';
  return $c->oauth2->get_token_p($mocked || 'facebook')->then(sub {
    ...
  });
});

See Mojolicious::Plugin::OAuth2::Mock for more details.

Connect button

You can add a "connect link" to your template using the "oauth2.auth_url" helper. Example template:

Click here to log in:
<%= link_to 'Connect!', $c->oauth2->auth_url('facebook', scope => 'user_about_me email') %>

DESCRIPTION

This Mojolicious plugin allows you to easily authenticate against a OAuth2 or OpenID Connect provider. It includes configurations for a few popular providers, but you can add your own as well.

See "register" for a full list of bundled providers.

To support "OpenID Connect", the following optional modules must be installed manually: Crypt::OpenSSL::Bignum, Crypt::OpenSSL::RSA and Mojo::JWT. The modules can be installed with App::cpanminus:

$ cpanm Crypt::OpenSSL::Bignum Crypt::OpenSSL::RSA Mojo::JWT

HELPERS

oauth2.auth_url

$url = $c->oauth2->auth_url($provider_name => \%args);

Returns a Mojo::URL object which contain the authorize URL. This is useful if you want to add the authorize URL as a link to your webpage instead of doing a redirect like "oauth2.get_token" does. %args is optional, but can contain:

  • host

    Useful if your provider uses different hosts for accessing different accounts. The default is specified in the provider configuration.

      $url->host($host);
    
  • authorize_query

    Either a hash-ref or an array-ref which can be used to give extra query params to the URL.

      $url->query($authorize_url);
    
  • redirect_uri

    Useful if you want to go back to a different page than what you came from. The default is:

      $c->url_for->to_abs->to_string
    
  • scope

    Scope to ask for credentials to. Should be a space separated list.

  • state

    A string that will be sent to the identity provider. When the user returns from the identity provider, this exact same string will be carried with the user, as a GET parameter called state in the URL that the user will return to.

oauth2.get_refresh_token_p

$promise = $c->oauth2->get_refresh_token_p($provider_name => \%args);

When Mojolicious::Plugin::OAuth2 is being used in OpenID Connect mode this helper allows for a token to be refreshed by specifying a refresh_token in %args. Usage is similar to "oauth2.get_token_p".

oauth2.get_token_p

$promise = $c->oauth2->get_token_p($provider_name => \%args)
             ->then(sub { my $provider_res = shift })
             ->catch(sub { my $err = shift; });

"oauth2.get_token_p" is used to either fetch an access token from an OAuth2 provider, handle errors or redirect to OAuth2 provider. $err in the rejection handler holds a error description if something went wrong. $provider_res is a hash-ref containing the access token from the OAauth2 provider or undef if this plugin performed a 302 redirect to the provider's connect website.

In more detail, this method will do one of two things:

  1. When called from an action on your site, it will redirect you to the provider's authorize_url. This site will probably have some sort of "Connect" and "Reject" button, allowing the visitor to either connect your site with his/her profile on the OAuth2 provider's page or not.
  2. The OAuth2 provider will redirect the user back to your site after clicking the "Connect" or "Reject" button. $provider_res will then contain a key "access_token" on "Connect" and a false value on "Reject".

The method takes these arguments: $provider_name need to match on of the provider names under "Configuration" or a custom provider defined when registering the plugin.

%args can have:

  • host

    Useful if your provider uses different hosts for accessing different accounts. The default is specified in the provider configuration.

  • redirect

    Set redirect to 0 to disable automatic redirect.

  • scope

    Scope to ask for credentials to. Should be a space separated list.

oauth2.jwt_decode

$claims = $c->oauth2->jwt_decode($provider, sub { my $jwt = shift; ... });
$claims = $c->oauth2->jwt_decode($provider);

When Mojolicious::Plugin::OAuth2 is being used in OpenID Connect mode this helper allows you to decode the response data encoded with the JWKS discovered from well_known_url configuration.

oauth2.logout_url

$url = $c->oauth2->logout_url($provider_name => \%args);

When Mojolicious::Plugin::OAuth2 is being used in OpenID Connect mode this helper creates the url to redirect to end the session. The OpenID Connect Provider will redirect to the post_logout_redirect_uri provided in %args. Additional keys for %args are id_token_hint and state.

oauth2.providers

$hash_ref = $c->oauth2->providers;

This helper allow you to access the raw providers mapping, which looks something like this:

{
  facebook => {
    authorize_url => "https://graph.facebook.com/oauth/authorize",
    token_url     => "https://graph.facebook.com/oauth/access_token",
    key           => ...,
    secret        => ...,
  },
  ...
}

ATTRIBUTES

providers

$hash_ref = $oauth2->providers;

Holds a hash of provider information. See "oauth2.providers".

METHODS

register

$app->plugin(OAuth2 => \%provider_config);
$app->plugin(OAuth2 => {providers => \%provider_config, proxy => 1, ua => Mojo::UserAgent->new});

Will register this plugin in your application with a given %provider_config. The keys in %provider_config are provider names and the values are configuration for each provider. Note that the value will be merged with the predefined providers below.

Instead of just passing in %provider_config, it is possible to pass in a more complex config, with these keys:

  • providers

    The %provider_config must be present under this key.

  • proxy

    Setting this to a true value will automatically detect proxy settings using "detect" in Mojo::UserAgent::Proxy.

  • ua

    A custom Mojo::UserAgent, in case you want to change proxy settings, timeouts or other attributes.

Instead of just passing in %provider_config, it is possible to pass in a hash-ref "providers" (%provider_config) and "ua" (a custom Mojo::UserAgent object).

Here is an example to add adddition information like "key" and "secret":

$app->plugin(OAuth2 => {
  providers => {
    custom_provider => {
      key           => 'APP_ID',
      secret        => 'SECRET_KEY',
      authorize_url => 'https://provider.example.com/auth',
      token_url     => 'https://provider.example.com/token',
    },
    github => {
      key    => 'APP_ID',
      secret => 'SECRET_KEY',
    },
  },
});

For OpenID Connect, authorize_url and token_url are configured from the well_known_url so these are replaced by the well_known_url key.

$app->plugin(OAuth2 => {
  providers => {
    azure_ad => {
      key            => 'APP_ID',
      secret         => 'SECRET_KEY',
      well_known_url => 'https://login.microsoftonline.com/tenant-id/v2.0/.well-known/openid-configuration',
    },
  },
});

To make it a bit easier the are already some predefined providers bundled with this plugin:

dailymotion

Authentication for https://www.dailymotion.com/ video site.

debian_salsa

Authentication for https://salsa.debian.org/.

eventbrite

Authentication for https://www.eventbrite.com event site.

See also http://developer.eventbrite.com/docs/auth/.

facebook

OAuth2 for Facebook's graph API, http://graph.facebook.com/. You can find key (App ID) and secret (App Secret) from the app dashboard here: https://developers.facebook.com/apps.

See also https://developers.facebook.com/docs/reference/dialogs/oauth/.

instagram

OAuth2 for Instagram API. You can find key (Client ID) and secret (Client Secret) from the app dashboard here: https://www.instagram.com/developer/clients/manage/.

See also https://www.instagram.com/developer/authentication/.

github

Authentication with Github.

See also https://developer.github.com/v3/oauth/.

google

OAuth2 for Google. You can find the key (CLIENT ID) and secret (CLIENT SECRET) from the app console here under "APIs & Auth" and "Credentials" in the menu at https://console.developers.google.com/project.

See also https://developers.google.com/+/quickstart/.

vkontakte

OAuth2 for Vkontakte. You can find key (App ID) and secret (Secure key) from the app dashboard here: https://vk.com/apps?act=manage.

See also https://vk.com/dev/authcode_flow_user.

AUTHOR

Marcus Ramberg - [email protected]

Jan Henning Thorsen - [email protected]

LICENSE

This software is licensed under the same terms as Perl itself.

SEE ALSO

mojolicious-plugin-oauth2's People

Contributors

akarelas avatar daleif avatar diegok avatar dsteinbrunner avatar elcamlost avatar gregoa avatar jberger avatar judofyr avatar kiwiroy avatar kraih avatar manwar avatar marcusramberg avatar oalders avatar pstray avatar ratcristian avatar renovate-bot avatar sailingyyc avatar tekki avatar valchonedelchev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mojolicious-plugin-oauth2's Issues

OIDC vs response_type (suggestion for better documentation)

Shouldn't it be more obvious from the documentation that the $args for oauth2.auth_url and oauth2.get_token_p are actually the same?

This is very important when one uses OpenID Connect and response_type=code is not automatically added to the authorize_url. Then the provider may give an error because response_type is missing.

It is not obvious from the documentation that one can do

$args = {
		authorize_query => {
		    response_type => 'code',
		},
    ...
};

and then use

$promise = $c->oauth2->get_token_p($provider_name => $args)
             ->then(sub { my $provider_res = shift })
             ->catch(sub { my $err = shift; });

took me quite a while to figure that one out.

Alternatively, since get_token_p expects the code parameter in the response, perhaps response_type => 'code' ought to be hard coded into _auth_url.

Getting a refresh token from Google does not seem to work

I struggled for a while to try and get a refresh token from Google. See this question. I ended up messing around with the source and (I think) I figured that the access_type arg doesn't get passed on - which seems to be fixed by adding a line like this:

$authorize_url->query->append(access_type => $provider_args->{access_type}) if defined $provider_args->{access_type};  

in _auth_url around line 95 or so.

It could be that I haven't figured out the right way to do this (but then could you please take a look at the docs, which are slightly terse IMHO). Or maybe it is an issue - and I hope it's ok to raise it here. Thanks for all the work on Mojolicious BTW. It's been a joy and a lifesaver so often for me.

fix_get_token configuration item is required but nowhere explained

After upgrading to v1.52 I got the error $c->oauth2->get_token(...) has changed api! Please set 'fix_get_token' in the config arguments to move forward....
I tried to understand what I actually need to do, but I did not find explanation in the documentation.
The only place I found it used is https://github.com/marcusramberg/Mojolicious-Plugin-OAuth2/blob/master/lib/Mojolicious/Plugin/OAuth2.pm#L227
Still not clear what is expected exactly.
Can you please explain?
Thanks!

Does non blocking example need a render_later?

I'll admit to being inexperienced with the use of promises, and the Mojo implementation of them.

I tried to use the example documented in "Example non-blocking application", but I had consistent failures with an "Transaction already destroyed" exception inside the "then" block (causing the 'catch' block to be executed).

This would always happen after returning from the oauth provider dialog (ie, when $provider_res is populated).

The solution for me was to add a call to render_later at the top of the action.

Versions:

Mojolicious: 8.35
Mojolicious::Plugin::OAuth2: 1.58

I'm using a custom provider, mixer.com.

Default authorize_url and token_url for Google OpenID isn't fresh

Now it's:

google => {
      authorize_url => "https://accounts.google.com/o/oauth2/auth?response_type=code",
      token_url     => "https://accounts.google.com/o/oauth2/token",
    },

Actual are:

google => {
      authorize_url => "https://accounts.google.com/o/oauth2/v2/auth?response_type=code",
      token_url     => "https://www.googleapis.com/oauth2/v4/token",
    },

I think it's better to get this urls from Discovery document and hardcode only one link: https://accounts.google.com/.well-known/openid-configuration

Outdated documentation snippet

I believe the following snippet from the POD is outdated, refers to an older version of the module, and doesn't fit-in with the previous snippet:

Lines 566-580:

Will redirect to the provider to allow for authorization, then fetch the
token. The token gets provided as a parameter to the callback function.
Usually you want to store the token in a session or similar to use for
API requests. Supported arguments:
=over 4
=item * host
Useful if your provider uses different hosts for accessing different accounts.
The default is specified in the provider configuration.
=item * scope
Scope to ask for credentials to. Should be a space separated list.

Shouldn't it get deleted, or maybe modified if not deleted?

Manual unclear about oauth2.jwt_decode

About oauth2.jwt_decode the manual says

$claims = $c->oauth2->jwt_decode($provider, sub { my $jwt = shift; ... });
$claims = $c->oauth2->jwt_decode($provider);

When Mojolicious::Plugin::OAuth2 is being used in OpenID Connect mode
this helper allows you to decode the response data encoded with the JWKS discovered from well_known_url configuration.

In the sources we have

sub _jwt_decode {
  my $peek = ref $_[-1] eq 'CODE' && pop;
  my ($self, $c, $args) = @_;
  croak 'Provider does not have "jwt" defined.' unless my $jwt = $self->providers->{$args->{provider}}{jwt};
  return $jwt->decode($args->{data}, $peek);
}

Thus it is looking at data from the input. But there is no data being set anywhere.

Aren't we suppose to use it something like this

$claims = $c->oauth2->jwt_decode($provider, data => $provider_res->{id_token});

At least then I got the claims I was expecting.

File::ShareDir::Install not specified in configure_requires

Automatic install fails if File::ShareDir::Install is not installed:

Output from '/bbbike/perl-5.18.4t/bin/perl Makefile.PL':

Can't locate File/ShareDir/Install.pm in @INC (you may need to install the File::ShareDir::Install module) (@INC contains: /var/tmp/cpansmoker-1023/2018092404/CPAN-Reporter-lib-dKMW /opt/perl-5.18.4t/lib/site_perl/5.18.4/x86_64-linux-thread-multi /opt/perl-5.18.4t/lib/site_perl/5.18.4 /opt/perl-5.18.4t/lib/5.18.4/x86_64-linux-thread-multi /opt/perl-5.18.4t/lib/5.18.4 .) at Makefile.PL line 3.
BEGIN failed--compilation aborted at Makefile.PL line 3.

oauth2 is camelized to Oauth2

Mojolicious::Plugin::OAuth2 is translated from o_auth2 instead of oauth2 by Mojo::Util::camelize.

The examples in doc are not working till I change the name passed to the plugin helper method. I think the only solution is to rename to Mojolicious::Plugin::Oauth2

Getting the state parameter back

We can specify the state variable for example in oauth2.auth_url. But it is not obvious to be how we get it back.

Looking into the code behind oauth2.get_token_p we can see that the state variable is picked up when there is a incoming code request. And it is passed on to the POST call to the token_url. But the state is not included in the POST response (at least not in the tests I've made so far).

How exactly are we suppose to get the specific state value back again?

`get_token_p` seems to always return `undef` on callback when not called from controller

I'm implementing OAuth in a pretty large code-base, i've passed the OAuth2 object down from the controller into a service layer object, which ultimately should be making the call too get_token_p however, the parameter passed to the ->then(...) callback seems to ALWAYS be undef, and at the speed of the error I'm almost 100% sure it doesn't make a call at all. I haven't had this issue with seemingly identical code that lives in the Mojolicious::Controller,

Custom service level code

sub execute {
   my $self = shift;
   my $args = {
      scope        => $self->scope, # 'user email'
      redirect_uri => $self->redirect_uri # 'http://localhost:3000/login/github'
   };

   return $self->oauth->get_token_p( github => $args )->then(
      sub {
         my $pr = shift;

         use DDP;

         p $pr;
         p $self->oauth->providers;

         return $pr->{access_token};
      });
}

First print:

undef

Second print (redacted of course):

    github         {
        authorize_url   "https://github.com/login/oauth/authorize",
        key             "XXX" (dualvar: 6),
        secret          "XXX",
        token_url       "https://github.com/login/oauth/access_token"
    },

Controller code

sub github {
    my $self = shift;

    Project::Service::OAuth2::Github->new(oauth => $self->oauth2)->execute->then(sub { ... });
}

I need the "refresh token"

"refresh tokens" are extremely useful (for example, when retrieving the friends list of one of your users, 6 months after they "logged-in with google" and now use a cookie to enter your site without passing from Google again)

But as the "on_success" subref only takes one parameter (just the token), I do not have access to the refresh token given by Google.

Improve handling of the case where they reject authorization

If the user gets redirected to the OAuth2 provider, and there they refuse to authorize my webapp, the user will be sent to the redirect_uri, from where your module will re-send them back to the OAuth2 provider for authorization... again!

This is not the correct way to handle refusal. The webapp creator should be able to define what happens if the user refuses to authorize.

Create blocking mode for M::P::OAuth2

I am going to try to create a pull-request for this module, which will allow it to work in blocking mode as well. I'm creating this issue so we can discuss the various difficulties and problems I encounter.

More issues with oauth2.jwt_decode

As far as I can see the possible callback (last arg) for jwt_decode will never be executed. That is in a call like

c->oauth2->jwt_decode($provider, data => $provider_res->{id_token}, sub { ....} );

the peek method at the end will never be correctly passed on to $jwt->decode($args->{data}, $peek); because of how _call collects arguments before passing it to _jwt_decode.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.