This recipe ports the BasicAuthAdapter example from the v1 README into
something you would actually deploy: prepared statements, hashed
passwords, and a clean separation between authentication
(who are you?) and authorization (what may you do?).
The adapter class itself is documented in Custom adapters — this page shows the full request lifecycle around it.
A protected admin endpoint that:
- Requires HTTP Basic credentials.
- Looks the user up in a
userstable. - Verifies the password against
password_hash()output. - Exposes the matched row as an auth segment.
- Builds a
Permissionset from the user's role. - Gates the endpoint on
is('admin').
CREATE TABLE users (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(64) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
role ENUM('admin', 'editor', 'viewer') NOT NULL DEFAULT 'viewer'
);Generate password hashes when you create the user:
$pdo->prepare('INSERT INTO users (username, password_hash, role) VALUES (?, ?, ?)')
->execute(['alice', password_hash('s3cret', PASSWORD_DEFAULT), 'admin']);<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use App\Auth\BasicAuthAdapter;
use InitPHP\Auth\Permission;
use InitPHP\Auth\Segment;
$auth = Segment::custom('auth', BasicAuthAdapter::class, [
'dsn' => 'mysql:host=localhost;dbname=app;charset=utf8mb4',
'username' => $_ENV['DB_USER'],
'password' => $_ENV['DB_PASS'],
]);
// At this point either the request authenticated successfully or the
// adapter has already sent a 401 and exit()ed.
$perm = new Permission([$auth->get('role')]);
if (!$perm->is('admin')) {
http_response_code(403);
exit('Admins only.');
}
printf("Welcome, %s.\n", $auth->get('username'));BasicAuthAdapterowns the authentication step. Its constructor reads$_SERVER['PHP_AUTH_USER']/$_SERVER['PHP_AUTH_PW'], looks the user up, and either populates the in-memory row or sends a401and exits.Segment::custom()wires the adapter into the standardAdapterInterfaceso that the rest of your code does not have to know it talks to a database. You can swap the adapter for a Redis one later without touching the call sites.Permissiontranslates the database role into a permission set. Case-insensitivity means the database can store'Admin'and the call site still asks for'admin'.
- Calling
md5()on the password. Usepassword_verify()against the storedpassword_hash()output. The v1 README example usedmd5(...), which is unsuitable for password storage. - Concatenating the username into SQL. Bind it. The
adapter source
shows how — never write
... WHERE username = '" . $username . "'". - Re-authenticating on every request without caching. A real
deployment would put the matched user id in a session or signed
cookie after the first Basic auth, so subsequent requests skip the
database round-trip. Use
Segment::cookie()orSegment::session()for that hop, withBasicAuthAdapteronly as the initial credential check.