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.
- Database-backed auth cookie + CSRF pairing with automatic rotation helpers.
- Built-in SQLite (default), MariaDB/MySQL, and PostgreSQL backends with automailes untic table creation; SQL fder
migrations/are only written when you supplytable_config. - Optional lock/unlock flow with a dedicated lock cookie and callbacks.
- Customizable helper/stash prefixes, cookie lifetimes, and cookie attributes.
- Session manager that supports
max-agewhile reusing a shared sessions object per app.
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.
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:
- Ensure the auth table exists on startup. A migration SQL file under
diris only written when you pass customtable_config(otherwise tables are created directly in the database). - Attach a shared
Mojo::Hakkefuin::Sessionsinstance so session cookies getmax-agederived froms.time. - Register helpers using your chosen
helper.prefix.
All options are optional; defaults are shown in parentheses.
helper.prefix(mhf): prefix for helpers (<prefix>_signin,<prefix>_csrf, etc.).stash.prefix(mhf): prefix for stash keys set by*_has_auth(mhf.backend-id,mhf.identify,mhf.lock_state).via(sqlite): backend driver:sqlite,mariadb, orpg.dsnis required formariadb/pg.dsn(none): DB connection string, e.g.mariadb://user:pass@db:3306/mhforpostgresql://user:pass@localhost/mhf.dir(migrations): directory for migration SQL and SQLite DB file.csrf.name(mhf_csrf_token): session/header key for the CSRF token.c.time(1w): auth cookie lifetime; also used to set DB expiration.s.time(1w): session cookie lifetime; also setsmax-agewhen supported by the browser.cl.time(60m): lock cookie lifetime whenlockis enabled.lock(1): enable lock/unlock helpers and cookies.cookies({name => 'clg', path => '/', httponly => 1, secure => 0}): override auth cookie attributes;expires/max_ageare computed fromc.time.cookies_lock({name => 'clglc', path => '/', httponly => 1, secure => 0}): override lock cookie attributes;expires/max_agecome fromcl.time.session({cookie_name => '_mhf', cookie_path => '/', secure => 0}): base options passed toMojo::Hakkefuin::Sessions;default_expirationis derived froms.time.callback({lock => sub {}, unlock => sub {}}): optional coderefs called after lock/unlock operations.
<prefix>_signin($identify): create auth cookie + CSRF, store in DB with expiration derived fromc.time.<prefix>_signout($identify): clear session and cookies, remove DB entry.<prefix>_has_auth: check cookie + CSRF against the backend. Returns{result => 1}on success,{result => 2}when locked,{result => 3}when the CSRF token mismatches, or{result => 0}when missing/expired. Stashes backend id, identify, and lock state usingstash.prefix.<prefix>_auth_update($identify): rotate cookie and CSRF token for an active session.<prefix>_lock/<prefix>_unlock: issue or clear the lock cookie whenlockis enabled.<prefix>_csrf: ensure a CSRF token exists in the session/response headers.<prefix>_csrf_get,<prefix>_csrf_val,<prefix>_csrf_regen: read, validate, or regenerate the CSRF token.<prefix>_backend: access the underlying backend object (e.g. for inspecting connection status).
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.
- SQLite stores data in
dir/mhf_sqlite.db; adir/mhf_sqlite.sqlfile is written only whentable_configis provided. Indexes are created in the database at startup. - MariaDB/MySQL and PostgreSQL create the tables (and indexes) directly in the database at startup; migration SQL is not dumped to
dirunless you providetable_config. - The plugin calls
check_migrationon startup, so the table is created automatically when credentials are valid.