Authentication
Credentials, JWT access & refresh tokens, device tracking, password resets and spam protection.
The Auth class is the entry point: it signs a credential in, issues
the JWT tokens, and answers the questions your code and routes ask about the current request.
Signing in
Resolve a credential by email, confirm it may sign in and that the password
matches, then call Auth::login() — which issues the tokens and starts the session.
Auth::logout() ends it:
use Framework\Auth\Auth;
$credential = Auth::getLoginCredential($email);
if (Auth::canLogin($credential) && Auth::isPasswordCorrect($password)) {
Auth::login($credential);
}
Once signed in, everything about the current request is available statically — you never thread the user through your code:
| Method | Returns |
|---|---|
getCredential() | The current credential entity. |
getID() / getAdminID() | The signed-in id (and the admin id while impersonating). |
getAccessName() | The current access level. |
getLanguage() | The user's language. |
isLoggedIn() / isAdmin() | Session checks. |
Access & refresh tokens
Sessions are stateless. Signing in issues a pair of tokens — a short-lived access token that
authorizes each request and a long-lived refresh token that renews it — and every request carries
them in its xAccessToken / xRefreshToken values.
Framework::execute() validates them and attaches fresh ones to
the response, so the client simply stores whatever it last received:
use Framework\Auth\Auth;
Auth::getAccessToken(); // the current JWT access token
Auth::getRefreshToken(); // the refresh token
The access token (JWT)
The access token is a JWT
(through firebase/php-jwt), signed HS256 with your AUTH_KEY. It carries the
standard iat (issued-at), nbf (not-before) and exp (expiry) claims plus a
data payload with the credential id, and expires AUTH_HOURS hours after it is issued.
Because it is signed, it is validated by decoding it with the key — no database lookup — which is what
keeps the request path stateless. AuthToken creates and verifies it.
The refresh token
The refresh token is the opposite: an opaque token stored in the database (one row per device),
valid for AUTH_DAYS days. Once the access token has expired, the client sends its refresh token and
Framework::execute() looks it up to resolve the credential and mint a new access token — so
the user stays signed in without re-entering a password. Being stored means they can be revoked: signing out
removes the row, and RefreshToken::removeOld() prunes expired ones on a
schedule.
Configuring the tokens
Three environment keys tune them:
| Key | Sets |
|---|---|
AUTH_KEY | The secret the JWTs are signed with — keep it private; changing it invalidates every issued token. |
AUTH_HOURS | Access-token lifetime, in hours (shorter is safer, but refreshes more often). |
AUTH_DAYS | Refresh-token lifetime, in days — how long a session survives before a full sign-in. |
API access
For server-to-server calls, set a static AUTH_API_TOKEN and send it as the token field
or an Authorization header. Framework::execute() validates it with
Auth::validateAPI() and marks the request as an API call — those may send a JSON body instead of form
fields.
Access roles & the Access class
Access levels are declared with AccessRole::register() and grouped in a
config file. The build turns them into a typed
Access enum — one case per role, ordered by the level you registered them at:
use Framework\System\Access;
Access::Admin; // an enum case
Access::getLevel(Access::Admin); // its numeric level
Access::getName(Access::Admin); // its localized name
For each role the class generates checks against the current user, so authorization reads
naturally — and because levels are ordered, you get OrHigher / OrLower variants:
Access::isAdmin(); // exactly Admin
Access::isAdminOrHigher(); // Admin or above
Access::isGeneralOrHigher();
The second argument to register() is a group — a named set of roles,
separate from the level ordering. Register several roles under the same group name to bundle them (a role always
belongs to exactly one group):
AccessRole::register("Owner", "Staff");
AccessRole::register("Manager", "Staff");
AccessRole::register("Client", "Customer");For every group the Access class generates membership and listing helpers, named after the group.
For the Staff group above:
Access::inStaffs(); // is the current user Owner or Manager?
Access::isValidStaff(Access::Owner); // is a given role part of the group?
Access::getStaffs(); // [Access::Owner, Access::Manager]
Access::getStaffSelect(); // a Select list of the group's roles
Use a group when a permission spans several roles that a single OrHigher check can't express —
membership is by set, not by level. The same enum is what every route
declares in its #[Route("/path", Access::Admin)] attribute, and
Framework::execute() enforces it before your method runs.
Password resets
Reset drives the forgot-password flow: create() issues a one-time code to email the
user, codeExists() validates the code they return, and getCredentialID() resolves it back
to an account so a new password can be set. deleteOld() prunes expired codes:
use Framework\Auth\Reset;
$code = Reset::create($credentialID); // email this code/link to the user
// …when they follow the link:
if (Reset::codeExists($code)) {
$credentialID = Reset::getCredentialID($code);
// set the new password, then:
Reset::delete($code);
}
Spam protection
To blunt brute-force and abuse, Spam::protect() rate-limits repeated attempts from the same source
and returns whether the action should be blocked; Spam::reset() clears the counter after a success.
Framework::execute() also runs a spam check on incoming requests:
use Framework\Auth\Spam;
if (Spam::protect()) {
return; // too many attempts — reject
}
// …on success:
Spam::reset();
Signing in as a user
An admin can act as another user — for support, say — without their password. loginAs() switches
the session to the target credential while remembering the admin (getAdminID() and
isLoggedAsUser() report it), and logoutAs() switches back. canLoginAs()
guards it: an admin may only impersonate users at or below their own level.
use Framework\Auth\Auth;
Auth::loginAs($userCredentialID); // act as the user
Auth::logoutAs(); // return to the admin
Configuration
Authentication is toggled and tuned through environment values:
| Key | Controls |
|---|---|
AUTH_ACTIVE | Enables the auth module. |
AUTH_KEY | Secret used to sign the JWT tokens. |
AUTH_HOURS | Access-token lifetime, in hours. |
AUTH_DAYS | Refresh-token lifetime, in days. |
AUTH_API_TOKEN | Static token for server-to-server API access. |
AUTH_FIELDS | Extra credential fields to expose. |