perlauthenticationhashpasswordsdancer

Perl Dancer2 Authentication Password Management


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?


Solution

  • 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.