benvanstaveren / Mojolicious-Plugin-Authentication

A plugin to make authentication a bit easier
http://search.cpan.org/dist/Mojolicious-Plugin-Authentication/
Other
20 stars 17 forks source link

Feature: Allow setting current_user #16

Closed preaction closed 7 years ago

preaction commented 8 years ago

I've got a web app that also includes a JSON API that is authenticated with JWT. I'd like to be able to use the same authorization code that relies upon $c->current_user, but using the custom authentication with JWT. Would it be possible to allow setting the current user via $c->current_user( $new_user )? If so, I can produce a patch.

benvanstaveren commented 8 years ago

Hi!

Unsure what JWT stands for, but yes, setting current_user via $c->current_user shouldn't be a problem, if you have a patch for that please also document that it really should be used only if you're absolutely sure one knows what they're doing, since you can in theory bypass the entire authentication with it.

I'd do it up myself but I'm really short on time right now, so pull requests are welcome :)

On 03/08/2016 09:37 PM, Doug Bell wrote:

I've got a web app that also includes a JSON API that is authenticated with JWT. I'd like to be able to use the same authorization code that relies upon |$c->current_user|, but using the custom authentication with JWT. Would it be possible to allow setting the current user via |$c->current_user( $new_user )|? If so, I can produce a patch.

— Reply to this email directly or view it on GitHub https://github.com/benvanstaveren/Mojolicious-Plugin-Authentication/issues/16.

preaction commented 8 years ago

JWT is JSON Web Token. It's basically Mojolicious's signed cookies, built as a standard and using different header mechanisms.

Alright, thanks, I'll put that together.

carragom commented 8 years ago

It would be great to have multiple authentication strategies other than just session/cookies. But setting the current_user manually basically bypasses the entire plugin so you might as well not use it.

Maybe the plugin could be modified to have a hash of strategies, each with it's own set of callbacks for validation and data loading. Where the default strategy would be session based so backwards compatibility is maintained. Then we could do something like this:

WARNING!!! DEMONSTRATION ONLY

$app->plugin('Authentication', {
    # All parameters not inside strategies should be deprecated
    # but permitted and treated as it's today
    strategies => {
        jwt => {
            extract_credentials => sub {
                # Extract and return credentials from request to be
                # passed to validate_credentials, return undef for
                # authentication error
                my ($c) = @_;
                my ($type, $token) = split(/\s+/, $c->req->headers->authorization);

                if ($type eq 'Bearer') {
                    return ($token, 'someextradata');
                } else {
                    return;
                }
            },
            validate_credentials => sub {
                # Renamed from validate_user, should validate the
                # provided credentials and return the data required
                # by load_principal. Or undef for auth error
                my ($c, $token, $extradata) = @_;

                if (valid_jwt($token)) {
                    my $unpacked_token = unpack_token($token);
                    return ($unpacked_token);
                } else {
                    return;
                }
            },
            load_principal => sub {
                # Renamed from load_user,
                # should fetch and return principal data that will end up in current_user
                # In the case of JWT this probably just means
                # the unpacked token
                my ($c, $token) = @_;

                return $token;
            }
        },
        apikey => {
            # Similar deal to JWT with different extract_credentials
        },
        custom => {
            # Custom strategy
        }
    }
});

And then we could just use the plugin in our routes like we do today:

#session based as it works today, deprecated
$app->routes->under('/website/protected')->over(authenticated => 1); 
#session based with the new api
$app->routes->under('/website/protected')->over(authenticated => {strategy => 'session'});
#use multiple strategies in order
$app->routes->under('/api')->over(authenticated => {strategy => ['jwt', 'apikey']});
#use some custom strategy
$app->routes->under('/custom-route')->over(authenticated => {strategy => 'custom'});

We should probably bundle the plugin with at least BASIC and SESSION strategies.

More complex strategies like JWT would probably require some extra dependencies so they should probably be distributed independently. Mojolicious::Plugin::Authentication::JWT and Mojolicious::Plugin::Authentication::APIKey maybe ?

@preaction Would something like this solve your requirement ? @benvanstaveren What do you think about this idea ?

benvanstaveren commented 8 years ago

Sorry for the late reply here, been too busy :(

Funny that you mention this because that's exactly what I've been wanting to do, either have multiple strategies, or do something with additional plugins (like M::P::Authentication::Yourstrategy) that implement the required functionality.

I'll keep the example in mind too, though :)

On 08/15/2016 08:33 PM, Carlos Ramos wrote:

It would be great to have multiple authentication strategies other than just session/cookies. But setting the |current_user| manually basically bypasses the entire plugin so you might as well not use it.

Maybe the plugin could be modified to have a hash of strategies, each with it's own set of callbacks for validation and data loading. Where the default strategy would be session based so backwards compatibility is maintained. Then we could do something like this:

WARNING!!! DEMONSTRATION ONLY

$app->plugin('Authentication', {

All parameters not inside strategies should be deprecated

# but permitted and treated as it's today
strategies => {
    jwt => {
        extract_credentials => sub {
            # Extract and return credentials from request to be
            # passed to validate_credentials, return undef for
            # authentication error
            my ($c) = @_;
            my ($type, $token) = split(/\s+/, $c->req->headers->authorization);
            if ($type eq 'Bearer') {
                return ($token, 'someextradata');
            } else {
                return;
            }
        },
        validate_credentials => sub {
            # Renamed from validate_user, should validate the
            # provided credentials and return the data required
            # by load_principal. Or undef for auth error
            my ($c, $token, $extradata) = @_;

            if (valid_jwt($token)) {
                my $unpacked_token = unpack_token($token);
                return ($unpacked_token);
            } else {
                return;
            }
        },
        load_principal => sub {
            # Renamed from load_user,
            # should fetch and return principal data that will end up in current_user
            # In the case of JWT this probably just means
            # the unpacked token
            my ($c, $token) = @_;

            return $token;
        }
    },
    apikey => {
        # Similar deal to JWT with different extract_credentials
    },
    custom => {
        # Custom strategy
    }
}

});

And then we could just use the plugin in our routes like we do today:

session based as it works today, deprecated

$app->routes->under('/website/protected')->over(authenticated => 1);

session based with the new api

$app->routes->under('/website/protected')->over(authenticated => {strategy => 'session'});

use multiple strategies in order

$app->routes->under('/api')->over(authenticated => {strategy => ['jwt', 'apikey']});

use some custom strategy

$app->routes->under('/custom-route')->over(authenticated => {strategy => 'custom'});

We should probably bundle the plugin with at least BASIC and SESSION strategies.

More complex strategies like JWT would probably require some extra dependencies so they should probably be distributed independently. |Mojolicious::Plugin::Authentication::JWT| and |Mojolicious::Plugin::Authentication::APIKey| maybe ?

@preaction https://github.com/preaction Would something like this solve your requirement ? @benvanstaveren https://github.com/benvanstaveren What do you think about this idea ?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/benvanstaveren/Mojolicious-Plugin-Authentication/issues/16#issuecomment-239887417, or mute the thread https://github.com/notifications/unsubscribe-auth/AAQxlGkkNXVnduPeZJFMnuxssBOvIWPKks5qgLEVgaJpZM4HsILG.