NAME

PAGI::Middleware::Session - Session management middleware with pluggable State/Store

SYNOPSIS

use PAGI::Middleware::Builder;

# Default (cookie-based, in-memory store)
my $app = builder {
    enable 'Session', secret => 'your-secret-key';
    $my_app;
};

# Explicit state and store
use PAGI::Middleware::Session::State::Header;
use PAGI::Middleware::Session::Store::Memory;

my $app = builder {
    enable 'Session',
        secret => 'your-secret-key',
        state  => PAGI::Middleware::Session::State::Header->new(
            header_name => 'X-Session-ID',
        ),
        store  => PAGI::Middleware::Session::Store::Memory->new;
    $my_app;
};

# In your app:
async sub app {
    my ($scope, $receive, $send) = @_;

    # Raw hashref access
    my $session = $scope->{'pagi.session'};
    $session->{user_id} = 123;

    # Or use the PAGI::Session helper
    use PAGI::Session;
    my $s = PAGI::Session->new($scope->{'pagi.session'});
    $s->set('user_id', 123);
    my $uid = $s->get('user_id');  # dies if key missing
}

DESCRIPTION

PAGI::Middleware::Session provides server-side session management with a pluggable architecture for session ID transport (State) and session data storage (Store).

The State layer controls how the session ID travels between client and server (cookies, headers, bearer tokens, or custom logic). The Store layer controls where session data is persisted (memory, Redis, database).

By default, sessions use cookie-based IDs and in-memory storage.

Warning: The default in-memory store is suitable for development and single-process deployments only. Sessions are not shared between workers and are lost on restart. For production multi-worker deployments, provide a store object backed by Redis, a database, or another shared storage.

CONFIGURATION

STATE CLASSES

State classes control how the session ID is extracted from requests and injected into responses. All implement the PAGI::Middleware::Session::State interface.

PAGI::Middleware::Session::State::Cookie

Default. Reads the session ID from a request cookie and sets it via Set-Cookie on the response. Suitable for browser-based web applications.

PAGI::Middleware::Session::State::Header

Reads the session ID from a custom HTTP header. Requires header_name; accepts an optional pattern regex with a capture group. Injection is a no-op (the client manages header-based transport).

PAGI::Middleware::Session::State::Header->new(
    header_name => 'X-Session-ID',
);
PAGI::Middleware::Session::State::Bearer

Convenience subclass of State::Header that reads an opaque bearer token from the Authorization: Bearer <token> header. Intended for opaque session tokens, not JWTs. For JWT authentication, use PAGI::Middleware::Auth::Bearer instead.

PAGI::Middleware::Session::State::Bearer->new();
PAGI::Middleware::Session::State::Callback

Custom session ID transport using coderefs. Requires an extract coderef; accepts an optional inject coderef (defaults to no-op).

PAGI::Middleware::Session::State::Callback->new(
    extract => sub { my ($scope) = @_; ... },
    inject  => sub { my ($headers, $id, $options) = @_; ... },
);

STORE CLASSES

Store classes control where session data is persisted. All implement the PAGI::Middleware::Session::Store interface; methods return Future objects for async compatibility.

PAGI::Middleware::Session::Store::Memory

Default. In-memory hash storage. Not shared across workers or restarts. Suitable for development and testing only.

External stores

Redis, database, and other shared stores are available as separate CPAN distributions. Any object implementing get($id), set($id, $data), and delete($id) (returning Futures) can be used.

PAGI::Session HELPER

PAGI::Session is a standalone helper object that wraps the raw session data hashref with a clean accessor interface.

use PAGI::Session;
my $session = PAGI::Session->new($scope->{'pagi.session'});

Key methods:

  • get($key) - Dies if the key does not exist (catches typos).

  • get($key, $default) - Returns $default for missing keys.

  • set($key, $value) - Sets a session value.

  • exists($key) - Checks key existence.

  • delete($key) - Removes a key.

  • keys - Lists user keys (excludes internal _-prefixed keys).

  • id - Returns the session ID.

  • regenerate - Requests session ID regeneration on next response.

  • destroy - Marks the session for deletion.

The helper stores a reference to the underlying hash, so mutations are visible to the middleware.

IDEMPOTENCY

The middleware skips processing if $scope->{'pagi.session'} already exists. This prevents double-initialization when the middleware appears more than once in a stack.

For mixed auth patterns (e.g. web cookies for browsers, bearer tokens for APIs), use PAGI::Middleware::Session::State::Callback with fallback logic instead of stacking multiple Session middleware instances:

use PAGI::Middleware::Session::State::Callback;
use PAGI::Middleware::Session::State::Cookie;
use PAGI::Middleware::Session::State::Bearer;

my $cookie_state = PAGI::Middleware::Session::State::Cookie->new(
    cookie_name => 'pagi_session',
    expire      => 3600,
);
my $bearer_state = PAGI::Middleware::Session::State::Bearer->new();

enable 'Session',
    secret => $ENV{SESSION_SECRET},
    state  => PAGI::Middleware::Session::State::Callback->new(
        extract => sub {
            my ($scope) = @_;
            return $bearer_state->extract($scope)
                // $cookie_state->extract($scope);
        },
        inject => sub {
            my ($headers, $id, $options) = @_;
            $cookie_state->inject($headers, $id, $options);
        },
    );

SCOPE EXTENSIONS

This middleware adds the following to $scope:

  • pagi.session

    Hashref of session data. Modify this directly to update the session, or wrap it with PAGI::Session for strict accessor methods. Keys starting with _ are reserved for internal use.

  • pagi.session_id

    The session ID string.

SESSION DATA

Keys starting with _ are reserved for internal use by the session middleware. Do not use underscore-prefixed keys for application data.

Reserved keys (read-only metadata)

  • _id - Session ID

  • _created - Unix timestamp when session was created

  • _last_access - Unix timestamp of last access (updated each request)

Reserved keys (middleware-consumed flags)

These flags are set by the application and consumed by the middleware during response handling. They are deleted after processing.

  • _regenerated - Set to 1 to regenerate the session ID on this response. The old session is deleted from the store and a new ID is issued. Always do this after authentication to prevent session fixation attacks.

  • _destroyed - Set to 1 to destroy the session entirely. The session data is deleted from the store and the client-side state (cookie) is cleared. Use this for logout.

SEE ALSO

PAGI::Session - Standalone session helper object

PAGI::Middleware::Session::State - Base class for session ID transport

PAGI::Middleware::Session::Store - Base class for session storage

PAGI::Middleware - Base class for middleware

PAGI::Middleware::Cookie - Cookie parsing