Skip to content

Repository files navigation

Auth

A lightweight authentication service: verifies a login/password pair against a PDO-backed table and tracks the authenticated user id for the rest of the request.

Example

use orange\auth\Auth;
use orange\auth\AuthError;

$auth = Auth::getInstance($config, $pdo); // config merges over auth/src/config/auth.php

if ($auth->login($_POST['email'], $_POST['password'])) {
    $userId = $auth->userId();
} else {
    echo $auth->error(); // e.g. "Login Error.", "Your user is not active."

    // branch on the code, not the (configurable) message
    if ($auth->errorCode() === AuthError::NotActivated) {
        // offer to resend the activation email, say
    }
}

$auth->logout(); // clears the error/code and resets userId() to 0

AuthError is the stable machine-readable contract: None, EmptyFields, UnknownUser, BadPassword, NotActivated. Present UnknownUser and BadPassword identically to the end user — the distinct cases exist for server-side logging/metrics; rendering them differently would reveal which logins exist.

Configure table, username column, password column, is active column, and (optionally) is deleted column to match your schema — each is validated as a plain SQL identifier at construction, so a typo'd config value throws immediately. Passwords must be stored with PHP's password_hash()login() verifies them with password_verify(), and only succeeds when the matching row's active column equals 1.

A cookbook of worked examples lives in example.md.

Configuration

src/config/auth.php (merged under anything you pass to Auth::getInstance()):

Key Default Purpose
table orange_users credential table (matches orange/acl; DDL in support/)
username column email login identifier column
password column password password_hash() column
is active column is_active must equal 1 to log in
is deleted column is_deleted soft-deleted rows read as unknown; null disables
normalize login true trim + lowercase the login before lookup
empty fields error Missing Required Field. message for AuthError::EmptyFields
general error Login Error. message for AuthError::UnknownUser
incorrect password error Login Error. message for AuthError::BadPassword
not activated error Your user is not active. message for AuthError::NotActivated

The defaults target the same orange_users table orange/acl ships — the two packages are designed to pair (auth answers "who is this", acl answers "what may they do"). On a successful login pass userId() to the acl package's User::change(), which regenerates the session id itself (fixation defense):

if ($auth->login($email, $password)) {
    $currentUser->change($auth->userId());
}

Behavior worth knowing

  • Logins are normalized before lookup: always trimmed, and lowercased by default ('normalize login' => true) so matching doesn't depend on the database collation — store identifiers lowercase. Set the flag false for case-sensitive username schemes.
  • Stale hashes upgrade themselves. After a successful verify, password_needs_rehash() is checked and the stored hash transparently rewritten with current defaults — login is the only moment the plaintext is available, so imported or legacy hashes strengthen over time without any migration.
  • The PDO handle is switched to ERRMODE_EXCEPTION at construction — this class's SQL reports failures by throwing, never by returning false mid-flight.

Security behavior

  • An unknown login and a soft-deleted account (is deleted column, set it to null for tables without one) both report the generic general error — indistinguishable from a wrong password by message.
  • The unknown-login path burns a password_verify() against a dummy hash so it costs the same as a wrong password — response timing can't be used to enumerate accounts.
  • Passwords longer than 1024 bytes are rejected before hashing (bcrypt reads at most 72 anyway) — a megabyte "password" is a CPU-burn attempt, not a credential.
  • Deliberate non-goals: rate limiting and account lockout need state shared across requests (a table or cache) and belong to the application layer — this class does not provide them.

Testing

cd unittest && sh runUnitTests.sh    # runs against in-memory sqlite

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages