The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Mojolicious::Plugin::Web::Auth::OAuth2;

use Mojo::Base -base;
use Mojo::JSON;
use Mojo::Parameters;
use Mojo::UserAgent;

has 'key';
has 'secret';
has 'scope';
has 'user_info';
has 'authorize_url';
has 'access_token_url';
has 'user_info_url';
has moniker => sub { die 'override me' };

sub _ua {
    my $self = shift;
    $self->{_ua} = Mojo::UserAgent->new( name => "Mojolicious::Plugin::Web::Auth/$Mojolicious::Plugin::Web::Auth::VERSION")
        unless ($self->{_ua});
    return $self->{_ua};
}

sub auth_uri {
    my ($self, $c, $callback_uri) = @_;
    $callback_uri or die "Missing mandatory parameter: callback_uri";

    my $url = Mojo::URL->new( $self->authorize_url );
    $url->query->param( client_id    => $self->key );
    $url->query->param( redirect_uri => $callback_uri );
    $url->query->param( scope        => $self->scope ) if ( defined $self->scope );

    return $url->to_string;
}

sub callback {
    my ($self, $c, $callback) = @_;
    if (my $error_description = $c->req->param('error_description')) {
        return $callback->{on_error}->($error_description);
    }
    my $code = $c->param('code') or die "Cannot get a 'code' parameter";

    my $params = +{
        code          => $code,
        client_id     => $self->key,
        client_secret => $self->secret,
        redirect_uri  => $c->url_for->path( $c->req->url->path )->to_abs->to_string,
        grant_type    => 'authorization_code',
    };

    my $tx = $self->_ua->post_form( $self->access_token_url => $params );
    (my $res = $tx->success ) or do {
        return $callback->{on_error}->( $tx->res->body );
    };

    my $dat = $self->_response_to_hash($res);
    if ( my $err = $dat->{error} ) {
        return $callback->{on_error}->($err);
    }
    my $access_token = $dat->{access_token} or die "Cannot get a access_token";
    my @args = ($access_token);

    if ( $self->user_info ) {
        my $url = Mojo::URL->new( $self->user_info_url );
        $url->query->param( access_token => $access_token );
        my $tx = $self->_ua->get( $url->to_abs );
        ( my $res = $tx->success )
            or return $callback->{on_error}->( sprintf( '%d %s', $tx->res->code, $tx->res->default_message ) );
        push @args, $res->json;
    }

    return $callback->{on_finished}->(@args);
}

sub _response_to_hash {
    my ( $self, $res ) = @_;
    return ( $res->headers->content_type eq 'application/json' )
        ? $res->json
        : Mojo::Parameters->new( $res->body )->to_hash;
}

1;

__END__