Flick
Guide Validation Services API Pro Examples GitHub
Docs / Services
Flick Pro Service

Tested and maintained, so you don't have to. $99/year.

Get Flick Pro

Auth

Simple authentication helper for managing login state in Flick forms. You handle user storage and retrieval - Flick just tracks who's logged in.

Why Use This Service?

What you'd build yourself: Session management, secure cookie handling, password hashing, remember-me tokens with HMAC signing, session fixation prevention, and timing-safe verification.

Maintained for you: session security and password hashing stay patched and tested as PHP changes.

What Pro provides:

  • Secure session handling with automatic ID regeneration
  • HMAC-SHA256 signed remember-me cookies
  • Bcrypt password hashing with PASSWORD_DEFAULT
  • Timing-safe token validation
  • Works with any user storage (database, file, API)

Installation

Flick Pro requires a license. See Pro installation for setup instructions.

Configuration

Add auth to your services configuration:

$config = [
    'services' => [
        'auth' => []
    ]
];

$form = new Flick($config);

Remember Me

Enable persistent login with a secure, revocable cookie. Remember-me stores a hashed token server-side, so logging out actually invalidates the cookie and every restore rotates the token. It needs one thing: a signingKey.

$config = [
    'services' => [
        'auth' => [
            'remember' => true,
            'signingKey' => $_ENV['APP_SIGNING_KEY'],
        ],
        'sql' => [ /* your database config */ ]
    ]
];

No key yet? Signing Key below shows how to make one — it takes a minute.

Token storage defaults to the bundled storage over the SQL service — create the remember_tokens table (see Token Storage) and you're done. To keep tokens somewhere else, pass your own storage instance as tokenStorage.

Or customize the settings:

$config = [
    'services' => [
        'auth' => [
            'remember' => [
                'lifetime' => 'days:7',          // Human-readable duration
                'cookieName' => 'flick_remember',
                'secure' => true,      // HTTPS only
                'httpOnly' => true,    // No JavaScript access
                'sameSite' => 'Strict' // CSRF protection
            ],
            'signingKey' => $_ENV['APP_SIGNING_KEY'],
        ],
        'sql' => [ /* your database config */ ]
    ]
];

Supported duration formats: seconds:N, minutes:N, hours:N, days:N, weeks:N, months:N, years:N

Signing Key

The signingKey is like a password for your app itself. Flick uses it to sign remember-me cookies so nobody can fake or tamper with one. You create it once and never change it — changing it breaks every remember-me cookie out there, and everyone has to log in again.

Step 1 — create the key. Open your terminal, paste this in, and hit enter:

php -r "echo bin2hex(random_bytes(32));"

It prints a long random string. That's your key.

Step 2 — store it. Make a config file that lives outside your public folder, so nobody can browse to it, and keep your whole Flick config in it — the key, your database login, all of it in one place and out of git:

// flick-config.php — outside your public folder, not committed to git
return [
    'services' => [
        'auth' => [
            'remember' => true,
            'signingKey' => 'paste-the-string-here',
        ],
        'sql' => [ /* your database config */ ],
    ],
];

Step 3 — hand it to Flick:

$form = new Flick(require __DIR__.'/../flick-config.php');

If your host does environment variables, those work too: 'signingKey' => $_ENV['APP_SIGNING_KEY'].

Why doesn't Flick generate one for you? Because the key has to be the same on every server and every deploy, forever. A key made up on the fly would change — and every change silently logs everyone out.

If signingKey is missing while remember-me is enabled, Auth throws immediately the first time you touch $form->auth.

Token Storage

Tokens are persisted server-side through a storage class. Only a hash of the validator is stored, so a leaked database can't forge a cookie.

Bundled SQL storage (the default). With remember-me on and no tokenStorage configured, Flick stores tokens in a remember_tokens table through the SQL service — nothing to write. Create the table once (Flick doesn't create tables for you):

CREATE TABLE remember_tokens (
    selector CHAR(32) PRIMARY KEY,
    hashed_validator CHAR(64) NOT NULL,
    user_id INT NOT NULL,
    expires_at INTEGER NOT NULL
);

CREATE INDEX idx_remember_tokens_user_id ON remember_tokens(user_id);

'tokenStorage' => 'sql' says the same thing explicitly; any other string throws an InvalidArgumentException naming the bad value.

Custom storage. To keep tokens anywhere else (Redis, an API, a different table layout), implement Flick\Auth\Storage\TokenStorageInterface — the same principle as the rest of Auth, where you own user storage — and pass an instance as tokenStorage:

interface TokenStorageInterface
{
    // Return ['hashed_validator' => string, 'user_id' => int|string, 'expires_at' => int] or null
    public function find(string $selector): ?array;

    public function store(string $selector, string $hashedValidator, int|string $userId, int $expiresAt): void;

    public function destroy(string $selector): void;             // rotation + single-device logout
    public function destroyAllForUser(int|string $userId): void; // "log out everywhere"
}

Basic Usage

Logging In

$user = $form->sql->find('users', ['email' => $email]);

if ($user && $form->auth->verify($password, $user['password'])) {
    $form->auth->login($user['id']);
    $form->redirect('/dashboard');
}

With Remember Me

$remember = isset($_POST['remember']);
$form->auth->login($user['id'], $remember);

Checking Auth Status

if ($form->auth->check()) {
    $form->successMessage("Welcome back, user #{$form->auth->id()}!");
}

if ($form->auth->guest()) {
    $form->infoMessage('Please log in.');
}

Logging Out

$form->auth->logout();

// Use a plain header redirect — Flick's redirect() is a form-flow helper that
// only fires after a successful submission, so it would do nothing here.
header('Location: /login');
exit;

Methods

login()

Log a user in by storing their ID in the session.

$form->auth->login(int|string $id, bool $remember = false): void
Parameter Type Description
$id int|string User ID to store
$remember bool Set remember me cookie (default: false)

Tip

Flick automatically regenerates the session ID on login to prevent session fixation attacks.

logout()

Clear the session, regenerate the session ID, and revoke the current remember token. Pass everywhere: true to revoke every remember token for the user (sign out of all devices).

$form->auth->logout(bool $everywhere = false): void

check()

Check if a user is logged in.

$form->auth->check(): bool

Returns: true if logged in, false otherwise.

guest()

Check if the user is not logged in.

$form->auth->guest(): bool

Returns: true if not logged in, false otherwise.

id()

Get the currently logged-in user's ID.

$form->auth->id(): int|string|null

Returns: User ID or null if not logged in.

Tip

If the session has expired but a valid remember cookie exists, id() will automatically restore the session.

hash()

Hash a password for secure storage.

$form->auth->hash(string $password): string
Parameter Type Description
$password string Plain text password

Returns: Hashed password using PHP's PASSWORD_DEFAULT algorithm (currently bcrypt).

verify()

Verify a password against a stored hash.

$form->auth->verify(string $password, string $hash): bool
Parameter Type Description
$password string Plain text password to verify
$hash string Stored password hash

Returns: true if password matches, false otherwise.

needsRehash()

Check whether a stored hash was made with outdated settings and should be rehashed. Call it after a successful verify(), and if it returns true, re-run hash() on the plain-text password and save the new hash.

$form->auth->needsRehash(string $hash): bool

Returns: true if the hash should be regenerated, false otherwise.

Complete Example

Registration

<?php
require 'vendor/autoload.php';

$form = new Flick\Flick([
    'services' => [
        'auth' => [],
        // this example also reads/writes users, so the sql service is needed too
        'sql' => [
            'driver' => 'mysql',
            'host' => 'localhost',
            'database' => 'myapp',
            'username' => 'root',
            'password' => 'secret'
        ]
    ]
]);

// Create the registration form
$form->create('Name, Email, Password|password, Password Confirmation|password');

// Handle submission
if ($form->submitted()) {
    $request = $form->request('
        Name[required, min:2],
        Email[required, email],
        Password[required, strongPassword, confirmed],
        Password Confirmation[required]
    ');

    if ($form->ok()) {
        // Save user with hashed password
        $userId = $form->sql->save('users', [
            'name' => $request['name'],
            'email' => $request['email'],
            'password' => $form->auth->hash($request['password'])
        ]);

        // Log them in
        $form->auth->login($userId);
        $form->redirect('/dashboard');
    }
}

Tip

confirmed compares password against password_confirmation. Password Confirmation becomes password_confirmation, so the two line up on their own — a two-word label is lowercased and its spaces become underscores in both create() and request().

Login

<?php
require 'vendor/autoload.php';

$form = new Flick\Flick([
    'services' => [
        'auth' => [
            'remember' => true,
            'signingKey' => $_ENV['APP_SIGNING_KEY'],
        ],
        // this example also looks the user up, so the sql service is needed too
        'sql' => [
            'driver' => 'mysql',
            'host' => 'localhost',
            'database' => 'myapp',
            'username' => 'root',
            'password' => 'secret'
        ]
    ]
]);

$form->create('Email, Password|password, Remember Me|checkbox');

if ($form->submitted()) {
    // remember_me has no rules, but it has to be listed to come back in $request
    $request = $form->request('Email[required, email], Password[required], remember_me');

    if ($form->ok()) {
        $user = $form->sql->find('users', ['email' => $request['email']]);

        if ($user && $form->auth->verify($request['password'], $user['password'])) {
            $remember = !empty($request['remember_me']);
            $form->auth->login($user['id'], $remember);
            $form->redirect('/dashboard');
        }

        $form->addError('email', 'Invalid credentials.');
    }
}

Tip

request() only returns the fields you name. An unchecked checkbox posts nothing at all, so remember_me comes back as an empty string rather than being missing — !empty() handles both.

Protected Page

<?php
require 'vendor/autoload.php';

$form = new Flick\Flick([
    'services' => [
        'auth' => [
            'remember' => true,
            'signingKey' => $_ENV['APP_SIGNING_KEY'],
        ],
        // this example also loads the user row, so the sql service is needed too
        'sql' => [
            'driver' => 'mysql',
            'host' => 'localhost',
            'database' => 'myapp',
            'username' => 'root',
            'password' => 'secret'
        ]
    ]
]);

// Require authentication. Use a plain header redirect here — Flick's redirect()
// is a form-flow helper that only fires after a successful submission.
if ($form->auth->guest()) {
    header('Location: /login');
    exit;
}

// Get current user
$user = $form->sql->find('users', $form->auth->id());

$form->successMessage("Welcome, {$user['name']}!");

Security Features

  • Session Fixation Protection: Session ID regenerates on login
  • HMAC-SHA256 Cookies: Remember tokens are cryptographically signed
  • Timing-Safe Verification: Prevents timing attacks on cookie validation
  • Secure Cookie Defaults: HttpOnly, Secure, SameSite=Strict