Mojolicious::Plugin::Hakkefuin

Mojolicious plugin for minimalistic authentication. It pairs an HMAC cookie with a server-side CSRF token stored in the database, keeps expirations enforceable from the backend, and ships an optional lock/unlock flow for "screen lock" style behavior.

Features

Installation

curl -L https://cpanmin.us | perl - -M -n https://github.com/CellBIS/mojo-hakkefuin.git
# or from a clone
cpanm .

Using Perlbrew or another local perl is recommended.

Quick start

Mojolicious Lite:

use Mojolicious::Lite;

plugin 'Hakkefuin' => {
  'helper.prefix' => 'fuin',
  'stash.prefix'  => 'fuin',
  via             => 'sqlite',                 # or mariadb / pg
  dir             => 'migrations',             # where migration/sqlite db lives
  dsn             => 'postgresql://user:pass@localhost/mhf', # required for pg/mariadb
  'c.time'        => '1w',                     # auth cookie TTL
  's.time'        => '1w',                     # session TTL
  'cl.time'       => '60m',                    # lock cookie TTL
  'lock'          => 1,                        # enable lock/unlock helpers
};

post '/login' => sub {
  my $c   = shift;
  my $id  = $c->param('user');
  my $res = $c->fuin_signin($id);           # stores cookie+csrf in DB
  return $c->render(status => $res->{code}, json => $res);
};

under sub {
  my $c    = shift;
  my $auth = $c->fuin_has_auth;             # checks cookie+csrf and stashes ids
  return $c->render(status => 423, json => $auth) if $auth->{result} == 2;
  return $c->render(status => 401, text => 'Unauthorized') unless $auth->{result} == 1;
  $c->fuin_csrf;                            # ensure token is in session/header
  return 1;
};

get '/me' => sub {
  my $c = shift;
  return $c->render(json => { user => $c->stash('fuin.identify') });
};

# Per-request override of cookie/session TTLs
post '/login-custom' => sub {
  my $c    = shift;
  my $opts = {
    c_time => $c->param('c_time') // '2h',  # auth cookie TTL
    s_time => $c->param('s_time') // '30m', # session TTL
  };
  my $res = $c->fuin_signin($c->param('user'), $opts);
  return $c->render(status => $res->{code}, json => $res);
};

get '/auth-update-custom' => sub {
  my $c        = shift;
  my $backend  = $c->stash('fuin.backend-id');
  my $res      = $c->fuin_auth_update($backend, {c_time => '45m', s_time => '20m'});
  my $httpcode = $res->{code} // 500;
  return $c->render(status => $httpcode, json => $res);
};

app->start;

Mojolicious (non-Lite) looks the same inside startup, e.g. $self->plugin('Hakkefuin' => { ... });.

On startup the plugin will:

Configuration

All options are optional; defaults are shown in parentheses.

Helper reference

Lock/unlock flow

Call <prefix>_lock after *_has_auth passes to mark the session locked; a lock cookie is issued and the backend row is marked. Use <prefix>_unlock to clear the lock. When locked, *_has_auth returns {result => 2, code => 423, lock_cookie => 0|1} so you can respond with HTTP 423 or show a lock screen.

Backend notes