Authentication in Hazaar
Authentication in Hazaar
This example walks through adding authentication to a Hazaar application. We will create a custom auth adapter backed by a database, configure the framework to use it, secure routes so that unauthenticated requests are rejected, and access the logged-in user's data from within a controller.
The Auth Adapter
An auth adapter is responsible for looking up a user given their identity (typically an email address or username) and returning their stored credential so the framework can verify the supplied password.
The simplest approach is to extend Hazaar\Auth\Adapter\DBITable, which handles all of the database querying for you. Create the file src/Auth/UserAdapter.php:
<?php
namespace App\Auth;
use Hazaar\Auth\Adapter\DBITable;
use Hazaar\DBI\Adapter as DBI;
class UserAdapter extends DBITable
{
public function __construct()
{
parent::__construct(new DBI(), [
'table' => 'users',
'fields' => [
'identity' => 'email',
'credential' => 'password',
// Any additional columns you want available in the session:
'data' => ['id', 'name', 'role'],
],
]);
}
// Optional: called after every successful login.
protected function authenticationSuccess(string $identity, array $data): void
{
// e.g. update a last_login timestamp in the database
}
// Optional: called after every failed login attempt.
protected function authenticationFailure(string $identity, array $data): void
{
// e.g. increment a failed_attempts counter
}
}If you need a fully custom lookup (e.g. an external API or a non-standard schema) you can instead implement Hazaar\Auth\Interface\AuthenticationAdapter directly and provide a queryAuth() method that returns the identity, hashed credential, and any extra data you want stored in the session.
Configuration
Tell Hazaar which adapter to use and how to manage sessions by adding an auth section to your app.php config file:
return [
'development' => [
'auth' => [
// The adapter class created above
'adapter' => \App\Auth\UserAdapter::class,
// How long (in seconds) an authenticated session lasts
'expire' => 3600,
// Credential hashing — must match the algorithm used when passwords were stored
'encryption' => [
'hash' => 'sha256',
'salt' => 'my-secret-salt',
],
],
],
];Using JWT Tokens (API / SPA)
For token-based authentication (e.g. a REST API or a single-page application) swap the session backend and transport:
'auth' => [
'adapter' => \App\Auth\UserAdapter::class,
'backend' => \Hazaar\Auth\Session\Backend\JWT::class,
'transport' => \Hazaar\Auth\Session\Transport\Header::class,
// Header transport options
'header' => [
// Do not emit response token headers by default for API-first flows.
// Tokens are typically returned in JSON bodies.
'emit_headers' => false,
// Inbound token extraction defaults
'name' => 'Authorization',
'prefix' => 'Bearer',
'refresh_name' => 'Authorization',
'refresh_prefix' => 'Refresh',
// Outbound advisory header defaults
'advisory_name' => 'X-Auth-Access-Token',
'advisory_prefix' => '',
'advisory_refresh_name' => 'X-Auth-Refresh-Token',
'advisory_refresh_prefix' => '',
],
'jwt' => [
'alg' => 'HS256',
'passphrase' => 'change-me-to-a-strong-secret',
'expire' => 900, // 15 minutes
'refresh' => 86400, // 24 hours
],
],With this setup, the framework reads tokens from Authorization request headers (Bearer for access tokens, Refresh for refresh tokens) rather than cookies. By default it does not emit response token headers unless emit_headers is set to true.
If you need legacy Authorization: Bearer <token> request handling, set:
'header' => [
'name' => 'Authorization',
'prefix' => 'Bearer',
'refresh_name' => 'Authorization',
'refresh_prefix' => 'Refresh',
'emit_headers' => true, // optional: emit advisory response headers
],The Auth Controller
Create a controller to handle login and logout at src/Controller/Auth.php:
<?php
namespace App\Controller;
use Hazaar\Controller\Basic;
use Hazaar\Controller\Response\HTTP\Json as JsonResponse;
class Auth extends Basic
{
// POST /auth/login { "email": "...", "password": "..." }
public function login(): JsonResponse
{
$email = $this->request->get('email');
$password = $this->request->get('password');
$result = $this->session->authenticate($email, $password);
if (!$result->success) {
return new JsonResponse(['error' => $result->message], 401);
}
return new JsonResponse([
'message' => 'Login successful',
'identity' => $result->identity,
]);
}
// POST /auth/logout
public function logout(): JsonResponse
{
$this->session->clear();
return new JsonResponse(['message' => 'Logged out']);
}
}The request is always available as $this->request inside a controller — you do not pass it as a method argument. The $this->session property is a controller helper that is automatically populated when the route has a session or auth middleware attached (see Securing Routes below), giving you direct access to the auth adapter without any manual wiring.
Securing Routes
Use ->auth() on any route or route group to require a valid session. Unauthenticated requests will automatically receive a 401 Unauthorized response.
In your routes.php file:
<?php
use App\Controller\Auth;
use App\Controller\Profile;
use App\Controller\Dashboard;
use Hazaar\Application\Router;
// Login and logout — use ->session() so the adapter is injected and available
// as $this->session inside the controller, without requiring a valid token.
Router::post('/auth/login', [Auth::class, 'login'])->session();
Router::post('/auth/logout', [Auth::class, 'logout'])->session();
// Single protected route — returns 401 if no valid token is present
Router::get('/dashboard', [Dashboard::class, 'index'])->auth();
// Protect an entire group of routes at once
Router::group('/api', function($group) {
$group->get('/profile', [Profile::class, 'show']);
$group->put('/profile', [Profile::class, 'update']);
$group->get('/profile/avatar', [Profile::class, 'avatar']);
})->auth();->auth() requires a valid session and returns 401 Unauthorized when none is present. ->session() loads the session when a token exists but still allows the request through when there is no token — useful for endpoints like login where the user is not yet authenticated, or for public pages that show extra content to logged-in users.
Accessing Session Data
On any route with ->auth() (or ->session() when a token is present), Hazaar injects the adapter into the controller as $this->session. Identities are treated specially and must be retrieved with getIdentity(). All other session data stored at login can be retrieved as an array with toArray():
<?php
namespace App\Controller;
use Hazaar\Controller\Basic;
use Hazaar\Controller\Response\HTTP\Json as JsonResponse;
class Profile extends Basic
{
// GET /api/profile
public function show(): JsonResponse
{
$identity = $this->session->getIdentity();
$data = $this->session->toArray(); // ['id' => ..., 'name' => ..., 'role' => ...]
return new JsonResponse([
'email' => $identity,
'name' => $data['name'],
'role' => $data['role'],
]);
}
// PUT /api/profile
public function update(): JsonResponse
{
$userId = $this->session->get('id');
// ... update the user record in the database ...
return new JsonResponse(['message' => 'Profile updated']);
}
}The data columns declared in the adapter constructor (id, name, role) are automatically loaded into the session at login and remain available for the lifetime of the authenticated session. Identities are kept separate and guarded — always use getIdentity() rather than trying to read them from the data array.
Creating Users
Before a user can log in, their password must be stored using the same hashing algorithm configured above. DBITable exposes a create() helper for this — for example in a registration controller or a seed script:
$auth = new \App\Auth\UserAdapter();
// Hashes the password automatically using the configured algorithm before inserting
$auth->create('[email protected]', 'plain-text-password');To generate a hash manually (e.g. in a migration) use getCredentialHash():
$hash = $auth->getCredentialHash('plain-text-password');
// Store $hash in your databaseNext Steps
- Authentication Overview — in-depth reference for all adapters, backends, and transports
- Routing — more about route groups and middleware
- DBI — configuring the database connection used by
DBITable