Perl Dancer2 Authentication Password Management

370 views Asked by At

So any one who has used perl dancer knows that to authenticate a user on login you can call authenticate_user

authenticate_user(
    params->{username}, params->{password}
);

This is part of the Auth::Extensible plugin.

To me it looks like it encourages the use of storing passwords in plain text! Sure you can hash the password first then make sure the stored password is the same hash but this seems to be more of a work around and i found isn't guaranteed to work. I have only got this to work using sha1 which shouldn't be used. I want to use Bcrypt but the passphrase simply wont match. Possibly odd characters not matching i'm not sure.

The thing is using the dancer Passphrase plugin i can already validate the username and password without even needing to rely on authenticate_user to verify them. But for the dancer framework to consider the user logged in you still have to call authenticate_user which must be passed the password.

I'm completely stuck. I'm curious how other people have managed to use proper password management in dancer2?

2

There are 2 answers

0
David Precious On BEST ANSWER

Firstly, I'll echo the "you almost certainly don't need to be using authenticate_user()" comments. The plugin can handle all that for you.

However, "it doesn't hash it" is wrong; here's how it works. The authenticate_user keyword loops through all auth realms configured, and for each one, asks that provider's authenticate_user() method to see if it accepts the username and password. The Database provider (and the others) fetch the record from the DB, and use $self->match_password() (which comes from the Provider role) to validate it; that code checks if the stored password from the database starts with {scheme} and if so, uses Crypt::SaltedHash->validate to validate that the user-supplied password (in plain text, as it's just come in over the wire) matches the stored, hashed passsword ($correct in the code below is the stored password):

if ( $correct =~ /^{.+}/ ) {

    # Looks like a crypted password starting with the scheme, so try to
    # validate it with Crypt::SaltedHash:
    return Crypt::SaltedHash->validate( $correct, $given );
}

So, yes, if your stored password in the database is hashed, then it will match it if the password supplied matches that hash.

For an example of what a stored hashed password should look like, here's the output of the bundled generate-crypted-password utility:

[davidp@supernova:~]$ generate-crypted-password 
Enter plain-text password ?> hunter2
Result: {SSHA}z9llSLkkAXENw8FerEchzRxABeuJ6OPs

See the Crypt::SaltedHash doco for details on which algorhythms are supported by it, and the format it uses (which "comes from RFC-3112 and is extended by the use of different digital algorithms").

Do bear in mind that the code behind authenticate_user is exactly what's used under the hood for you.

For an example of just letting the plugin do the work for you, consider:

get '/secret' => require_login sub {
    my $user = logged_in_user();
    return "Hi, $user->{username}, let me tell you a secret";
};

... that's it. The require_login means that the plugin will check if the user is logged in, and if not, redirect them to the login page to log in. You don't need to call authenticate_user yourself, you don't need to set any session variables or anything. logged_in_user() will return a hashref of information about the logged in user (and because the route code has require_login, there's guaranteed to be one at this point, so you don't need to check).

If you need to check they have a suitable role, instead of just that they are logged in, then look at require_role in the documentation instead.

4
Dave Cross On

In the documentation for Dancer2::Plugin::Auth::Extensible, the description for authenticate_user() says:

Usually you'll want to let the built-in login handling code deal with authenticating users, but in case you need to do it yourself, this keyword accepts a username and password ...

Which strongly implies to me that you shouldn't be calling this function at all unless you're doing something particularly clever.

I haven't used this module myself, but it seems to me that all the hashing and encryption stuff should be handled by one of the authentication providers and if there's not one that covers the case you use, then you can write one yourself.

Whenever I need to store secure passwords for a Dancer app, I reach for Dancer2::Plugin::Passphrase. I wonder if I should consider writing an Auth::Extensible style authentication provider for it.