Search code examples
perlpostgresqldancer

A Simple Login/Authorization system using Dancer and Postgres


As a newbie to Perl I'm struggling to find a simple way to do this. I've created a very simple table in my database:

CREATE TABLE users (
id SERIAL NOT NULL PRIMARY KEY, 
username TEXT NOT NULL, 
password TEXT NOT NULL);

So far I used a simple login system that has a hard coded username and password that I found online:

package Example;
use Dancer ':syntax';

our $VERSION = '0.1';
set session => "Simple";

get '/' => sub {
    # template 'index',{},{layout => 0};
    template 'index';
};

before sub {
    if (! session('user') && request->path_info !~ m{^/login}) {
        var requested_path => request->path_info;
        request->path_info('/login');
    }
};

get '/login' => sub {
    # Display a login page; the original URL they requested is available as
    # vars->{requested_path}, so could be put in a hidden field in the form
    template 'login', { path => vars->{requested_path} }, {layout => 0};
};

post '/login' => sub {
    # Validate the username and password they supplied
    if (params->{user} eq 'user' && params->{pass} eq 'letmein') {
        session user => params->{user};
        redirect params->{path} || '/';
    } else {
        redirect printf 'login failed';
    }
};

get '/logout' => sub {
  session->destroy;
  redirect '/';
};

How do I get started with linking the database and then matching what the user inputs with what's in the database? And also when do I implement the hashing of the passwords? Any tutorials will be greatly appreciated - I've been using metacpan but it's not providing as much detail as I need!


Solution

  • Dancer::Plugin::Auth::Extensible takes care of a lot of boilerplate code for you. You can get a simple login system up and running without having to write any of your own /login routes as follows.

    Configure Dancer::Plugin::Auth::Extensible

    Install Dancer::Plugin::Database and Dancer::Plugin::Auth::Extensible::Provider::Database and add this to config.yml:

    session: "YAML"
    
    plugins:
      Auth::Extensible:
        realms:
          users:
            provider: 'Database'
            disable_roles: 1
    

    Configure database connection

    Configure your database connection in environments/development.yml so that you can have different configurations for dev and production. This is what the configuration looks like for MySQL, with the connection credentials (database name, host, username, and password) stored in a separate options file database.cfg:

    plugins:
      Database:
        dsn: 'dbi:mysql:;mysql_read_default_file=/path/to/database.cfg'
        dbi_params:
          RaiseError: 1
          AutoCommit: 1
    

    For Postgres, you should use a .pgpass file to store your connection credentials. Make sure the file is not world readable. See this Stack Overflow post for an example. Test that your credentials file works on the command line and that your webserver can read it.

    Your existing table appears to conform to the suggested schema in the docs, but even if it doesn't, you can adjust the table and column names in the configuration.

    Lock down your routes

    Add the require_login keyword to a route you want to protect. A /login route will be generated automatically with a basic login form, although you can create your own if you like.

    lib/MyApp.pm

    package MyApp;
    use Dancer ':syntax';
    
    use Dancer::Plugin::Auth::Extensible;
    
    our $VERSION = '0.1';
    
    get '/' => require_login sub {
        template 'index';
    };
    
    true;
    

    (Yes, that really is all the code you have to write. I told you it takes care of a lot of boilerplate.)

    Crypt::SaltedHash is used to hash passwords automatically. Note that you should never store plaintext passwords in your database; when you add a user to your database, you should generate a hash of the password and store the hash.

    Note that roles are disabled in this example. If you enable roles, you can do other nifty things like only allow users with the admin role to view admin pages.