Tested and maintained, so you don't have to. $99/year.
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