iviphp / auth
Authentication guards, user providers and password hashing for the IviPHP ecosystem.
Requires
- php: >=8.2
- iviphp/contracts: ^0.1
- iviphp/support: ^0.1
This package is not auto-updated.
Last update: 2026-07-21 21:57:55 UTC
README
Authentication guards, user providers and secure password hashing for the IviPHP ecosystem.
Overview
iviphp/auth provides a small, framework-independent authentication layer for PHP applications.
It includes:
- authentication contracts;
- authenticatable identity support;
- user-provider abstraction;
- in-memory user providers;
- native PHP session authentication;
- named authentication guards;
- named user providers;
- lazy guard and provider resolution;
- configurable default guards;
- secure password hashing;
- password verification;
- password rehash detection;
- session fixation protection;
- disabled-account handling;
- safe authentication exceptions;
- no global static authentication state.
The initial release focuses on stateful web authentication using native PHP sessions.
It does not include authorization, roles, permissions, OAuth, JWT, password-reset workflows or database models.
Installation
composer require iviphp/auth
Requirements
- PHP 8.2 or later
- Composer
- native PHP session support
iviphp/contractsiviphp/support
Argon2 support depends on the PHP build and operating system.
Available password algorithms can be inspected through PHP:
php -r 'print_r(password_algos());'
Package structure
src/
├── Auth.php
├── AuthManager.php
├── PasswordHasher.php
├── Contracts/
│ ├── AuthenticatableInterface.php
│ ├── GuardInterface.php
│ └── UserProviderInterface.php
├── Exceptions/
│ └── AuthException.php
├── Guards/
│ └── SessionGuard.php
└── Providers/
└── ArrayUserProvider.php
Core concepts
The package is built around four main concepts:
-
Authenticatable identity An application user or account that can be authenticated.
-
User provider Retrieves identities and validates supplied credentials.
-
Guard Maintains authentication state for the current application context.
-
Auth manager Registers and resolves named guards and providers.
Creating an authenticatable user
Application identities must implement:
Ivi\Auth\Contracts\AuthenticatableInterface
Example:
<?php declare(strict_types=1); namespace App\Auth; use Ivi\Auth\Contracts\AuthenticatableInterface; final readonly class User implements AuthenticatableInterface { public function __construct( private int $id, private string $name, private string $passwordHash, private bool $active = true ) {} public function authIdentifier(): int|string { return $this->id; } public function authPasswordHash(): string { return $this->passwordHash; } public function authDisplayName(): string { return $this->name; } public function canAuthenticate(): bool { return $this->active; } }
Authentication identifier
The authentication identifier must be stable across requests.
Supported types:
int|string
Examples include:
- database primary keys;
- UUIDs;
- immutable account identifiers;
- external identity identifiers.
The identifier should not contain passwords, access tokens or other secrets.
Password hashes
authPasswordHash() must return a password hash.
public function authPasswordHash(): string { return $this->passwordHash; }
Never return a plain-text password.
Hashes should be created through PasswordHasher or PHP's native password_hash() function.
Account restrictions
canAuthenticate() controls whether an identity may log in.
public function canAuthenticate(): bool { return $this->active && !$this->suspended && !$this->deleted; }
The method can reject:
- suspended accounts;
- disabled accounts;
- deleted accounts;
- unverified accounts;
- accounts blocked by application policy.
Password hashing
Create a password hasher:
use Ivi\Auth\PasswordHasher; $hasher = new PasswordHasher();
Hash a password:
$hash = $hasher->hash( 'correct horse battery staple' );
The default PHP password algorithm is used.
Verifying passwords
$valid = $hasher->verify( 'correct horse battery staple', $hash );
The method returns:
true
when the password matches, otherwise:
false
Checking whether a hash needs rehashing
if ($hasher->needsRehash($hash)) { $newHash = $hasher->hash($password); }
This is useful after:
- changing the hashing algorithm;
- changing bcrypt cost;
- changing Argon2 parameters;
- upgrading PHP's default algorithm.
Verify and rehash
$newHash = $hasher->verifyAndRehash( $password, $existingHash );
The result behaves as follows:
null: the password is invalid;- existing hash: the password is valid and no rehash is required;
- new hash: the password is valid and should be persisted again.
Example:
$newHash = $hasher->verifyAndRehash( $password, $user->authPasswordHash() ); if ($newHash === null) { throw new RuntimeException( 'Invalid credentials.' ); } if ($newHash !== $user->authPasswordHash()) { updateStoredPasswordHash( $user->authIdentifier(), $newHash ); }
Bcrypt configuration
$hasher = new PasswordHasher( PASSWORD_BCRYPT, [ 'cost' => 12, ] );
The bcrypt cost must be between 4 and 31.
Argon2id configuration
When supported by PHP:
$hasher = new PasswordHasher( PASSWORD_ARGON2ID, [ 'memory_cost' => 65536, 'time_cost' => 4, 'threads' => 2, ] );
Choose hashing parameters based on the deployment environment.
Very high values may make login requests too slow or consume excessive memory.
Password hash information
$information = $hasher->information($hash);
Example result:
[
'algorithm' => '2y',
'algorithm_name' => 'bcrypt',
'options' => [
'cost' => 12,
],
]
The original hash is not included in the result.
Checking a hash
if (!$hasher->isValidHash($hash)) { throw new RuntimeException( 'Unsupported password hash.' ); }
Array user provider
The initial package includes an in-memory user provider:
Ivi\Auth\Providers\ArrayUserProvider
It is suitable for:
- automated tests;
- development tools;
- small internal applications;
- predefined administrative identities;
- applications that already have identity objects in memory.
Creating an array provider
use App\Auth\User; use Ivi\Auth\PasswordHasher; use Ivi\Auth\Providers\ArrayUserProvider; $hasher = new PasswordHasher(); $user = new User( id: 1, name: 'Gaspard', passwordHash: $hasher->hash('secret-password') ); $provider = new ArrayUserProvider([ [ 'user' => $user, 'credentials' => [ 'email' => 'gaspard@example.com', 'username' => 'gaspard', ], ], ]);
Adding users
$provider->add( $user, [ 'email' => 'gaspard@example.com', 'username' => 'gaspard', ] );
Built-in searchable attributes are added automatically:
id
identifier
display_name
Replacing users
$provider->replace( $updatedUser, [ 'email' => 'new@example.com', 'username' => 'gaspard', ] );
Checking whether a user exists
$provider->has(1);
Integer and string identifiers remain distinct:
$provider->has(42); $provider->has('42');
These can represent different identities.
Retrieving a user by identifier
$user = $provider->retrieveById(1);
When the identity is missing:
null
is returned.
Retrieving a user by credentials
$user = $provider->retrieveByCredentials([ 'email' => 'gaspard@example.com', 'password' => 'secret-password', ]);
The password is not used to locate the identity.
Only non-sensitive identifying fields such as email or username are searched.
Credential validation happens separately.
Validating credentials
$valid = $provider->validateCredentials( $user, [ 'email' => 'gaspard@example.com', 'password' => 'secret-password', ] );
Custom password field
$provider = new ArrayUserProvider( users: [], passwordField: 'passphrase' );
Credentials then use:
[
'email' => 'gaspard@example.com',
'passphrase' => 'secret-password',
]
Sensitive credential fields
Sensitive fields cannot be added to the searchable index.
Examples include:
password
password_hash
passwd
passphrase
secret
token
access_token
refresh_token
api_key
private_key
credential
This prevents secrets from being used as identity lookup fields.
Provider inspection
Return all identities:
$users = $provider->all();
Count identities:
$count = $provider->count();
Return searchable attributes:
$attributes = $provider->attributes(1);
Example:
[
'id' => 1,
'identifier' => 1,
'display_name' => 'Gaspard',
'email' => 'gaspard@example.com',
'username' => 'gaspard',
]
Remove an identity:
$provider->remove(1);
Remove all identities:
$provider->clear();
Session guard
The initial authentication guard uses native PHP sessions:
Ivi\Auth\Guards\SessionGuard
Create a guard:
use Ivi\Auth\Guards\SessionGuard; $guard = new SessionGuard( provider: $provider, name: 'web' );
The guard stores only the user's stable authentication identifier in the session.
The complete identity is restored through the user provider.
Session key
By default, the session key is generated from the guard name.
For a guard named:
web
the key is:
_ivi_auth_web
Use a custom key:
$guard = new SessionGuard( provider: $provider, name: 'web', sessionKey: '_application_auth' );
Automatic session start
Sessions are started automatically by default.
$guard = new SessionGuard( provider: $provider, autoStart: true );
Disable automatic startup:
$guard = new SessionGuard( provider: $provider, autoStart: false );
When automatic startup is disabled, the application must call:
session_start();
before using the guard.
Authentication attempt
$authenticated = $guard->attempt([ 'email' => 'gaspard@example.com', 'password' => 'secret-password', ]);
The result is true only when:
- a matching identity is found;
- the password is valid;
- the identity permits authentication;
- the session state is written successfully.
Invalid credentials return false.
The guard does not reveal whether the email or password was incorrect.
Checking authentication
if ($guard->check()) { $user = $guard->user(); }
Check whether the current request is unauthenticated:
if ($guard->guest()) { redirectToLogin(); }
Current user
$user = $guard->user();
Possible result:
AuthenticatableInterface|null
The identity is restored lazily from the session.
Current identifier
$id = $guard->id();
Possible result:
int|string|null
Direct login
$guard->login($user);
Direct login still checks:
$user->canAuthenticate()
The user's identifier is stored in the session.
Logout
$guard->logout();
Only the authentication key owned by the guard is removed.
Other application session data remains available.
Session identifier regeneration
The session ID is regenerated after login and logout by default.
This helps reduce session fixation risks.
$guard = new SessionGuard( provider: $provider, regenerateOnLogin: true, regenerateOnLogout: true );
The behavior can be disabled when an application has another session-management strategy:
$guard = new SessionGuard( provider: $provider, regenerateOnLogin: false, regenerateOnLogout: false );
Disabling regeneration should be done carefully.
Validating credentials without login
SessionGuard provides an additional method:
$user = $guard->validate([ 'email' => 'gaspard@example.com', 'password' => 'secret-password', ]);
The method returns:
AuthenticatableInterface|null
It does not create an authenticated session.
This can be useful for:
- confirming a password;
- sensitive account actions;
- reauthentication;
- API token creation;
- administrative confirmation.
Forgetting the cached identity
$guard->forgetUser();
The session identifier remains unchanged.
The next call to user() reloads the identity through the provider.
Replacing the cached identity
$guard->setUser($updatedUser);
The supplied identity must have the same identifier as the current authenticated session.
Session state
$active = $guard->sessionActive();
The method returns true when:
session_status() === PHP_SESSION_ACTIVE
Authentication manager
Create a manager:
use Ivi\Auth\AuthManager; $manager = new AuthManager( guards: [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], ], providers: [ 'users' => [ 'driver' => 'array', 'users' => [ [ 'user' => $user, 'credentials' => [ 'email' => 'gaspard@example.com', ], ], ], ], ], defaultGuard: 'web' );
Configuration-based manager
$manager = AuthManager::fromConfig([ 'default' => 'web', 'providers' => [ 'users' => [ 'driver' => 'array', 'users' => [ [ 'user' => $user, 'credentials' => [ 'email' => 'gaspard@example.com', 'username' => 'gaspard', ], ], ], ], ], 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], ], ]);
Provider configuration
Array-provider configuration:
'providers' => [ 'users' => [ 'driver' => 'array', 'users' => [ [ 'user' => $user, 'credentials' => [ 'email' => 'gaspard@example.com', ], ], ], 'password_field' => 'password', 'hashing' => [ 'algorithm' => PASSWORD_BCRYPT, 'options' => [ 'cost' => 12, ], ], ], ],
Guard configuration
Session-guard configuration:
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', 'session_key' => '_application_auth', 'auto_start' => true, 'regenerate_on_login' => true, 'regenerate_on_logout' => true, ], ],
Resolving guards
Resolve the default guard:
$guard = $manager->guard();
Resolve a named guard:
$guard = $manager->guard('web');
get() is an alias:
$guard = $manager->get('web');
Guards are resolved lazily and reused after their first creation.
Resolving providers
$provider = $manager->provider('users');
Providers are also resolved lazily and reused.
Registering a guard
$manager->registerGuard( 'admin', [ 'driver' => 'session', 'provider' => 'administrators', 'session_key' => '_admin_auth', ] );
Registering a guard instance
$manager->guardInstance( 'web', $guard );
Guard factories
use Ivi\Auth\AuthManager; use Ivi\Auth\Contracts\GuardInterface; use Ivi\Auth\Guards\SessionGuard; $manager->registerGuard( 'custom', function ( AuthManager $manager, string $name ): GuardInterface { return new SessionGuard( provider: $manager->provider('users'), name: $name ); } );
The factory must return an implementation of GuardInterface.
Replacing a guard
$manager->replaceGuard( 'web', [ 'driver' => 'session', 'provider' => 'users', 'session_key' => '_new_auth_key', ] );
The previously resolved guard is discarded.
The replacement is resolved lazily.
Removing a guard
$manager->forgetGuard('admin');
When the removed guard was the default, the first remaining guard becomes the default.
Registering a provider
$manager->registerProvider( 'administrators', [ 'driver' => 'array', 'users' => $administrators, ] );
Registering a provider instance
$manager->providerInstance( 'users', $provider );
Provider factories
use Ivi\Auth\AuthManager; use Ivi\Auth\Contracts\UserProviderInterface; use Ivi\Auth\Providers\ArrayUserProvider; $manager->registerProvider( 'custom', function ( AuthManager $manager, string $name ): UserProviderInterface { return new ArrayUserProvider(); } );
Replacing a provider
$manager->replaceProvider( 'users', [ 'driver' => 'array', 'users' => $newUsers, ] );
Resolved guards using that configured provider are also discarded so they can be recreated with the replacement provider.
Removing a provider
$manager->forgetProvider('users');
Guards referencing the removed provider fail when resolved again.
Default guard
Return the default guard name:
$name = $manager->defaultName();
Change it:
$manager->setDefault('admin');
The guard must already be registered.
Manager inspection
Check whether a guard exists:
$manager->hasGuard('web');
Check whether it has been resolved:
$manager->isGuardResolved('web');
Return guard names:
$names = $manager->guardNames();
Check whether a provider exists:
$manager->hasProvider('users');
Check whether it has been resolved:
$manager->isProviderResolved('users');
Return provider names:
$names = $manager->providerNames();
Forgetting resolved services
Forget resolved guards while preserving definitions:
$manager->forgetResolvedGuards();
Forget resolved providers and dependent guards:
$manager->forgetResolvedProviders();
Remove all definitions and resolved instances:
$manager->flush();
flush() does not automatically log authenticated users out of existing sessions.
Main Auth entry point
Create the main authentication service:
use Ivi\Auth\Auth; $auth = new Auth($manager);
Create it from configuration:
$auth = Auth::fromConfig([ 'default' => 'web', 'providers' => [ 'users' => [ 'driver' => 'array', 'users' => $users, ], ], 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], ], ]);
Authentication through Auth
Attempt login:
if ($auth->attempt([ 'email' => 'gaspard@example.com', 'password' => 'secret-password', ])) { redirectToDashboard(); }
Check login state:
$auth->check(); $auth->guest();
Retrieve the user:
$user = $auth->user();
Retrieve the identifier:
$id = $auth->id();
Direct login:
$auth->login($user);
Logout:
$auth->logout();
Multiple guards
$auth = Auth::fromConfig([ 'default' => 'web', 'providers' => [ 'users' => [ 'driver' => 'array', 'users' => $users, ], 'administrators' => [ 'driver' => 'array', 'users' => $administrators, ], ], 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'admin' => [ 'driver' => 'session', 'provider' => 'administrators', 'session_key' => '_ivi_admin_auth', ], ], ]);
Use the default guard:
$auth->attempt([ 'email' => 'user@example.com', 'password' => 'password', ]);
Use the administrator guard:
$adminAuth = $auth->using('admin'); $adminAuth->attempt([ 'email' => 'admin@example.com', 'password' => 'password', ]);
The original Auth instance remains unchanged.
$auth->guardName(); // web $adminAuth->guardName(); // admin
Return to the default guard:
$defaultAuth = $adminAuth->usingDefault();
Auth inspection
Return the manager:
$manager = $auth->manager();
Return the current guard:
$guard = $auth->guard();
Return its provider:
$provider = $auth->provider();
Return the selected guard name:
$name = $auth->guardName();
Return the manager default:
$name = $auth->defaultGuardName();
Change the default:
$auth->setDefaultGuard('admin');
List guards:
$guards = $auth->guardNames();
List providers:
$providers = $auth->providerNames();
Custom user provider
Applications can implement:
Ivi\Auth\Contracts\UserProviderInterface
Example database-oriented provider skeleton:
<?php declare(strict_types=1); namespace App\Auth; use Ivi\Auth\Contracts\AuthenticatableInterface; use Ivi\Auth\Contracts\UserProviderInterface; use Ivi\Auth\PasswordHasher; final readonly class DatabaseUserProvider implements UserProviderInterface { public function __construct( private PasswordHasher $hasher ) {} public function retrieveById( int|string $identifier ): ?AuthenticatableInterface { return findUserById($identifier); } public function retrieveByCredentials( array $credentials ): ?AuthenticatableInterface { $email = $credentials['email'] ?? null; if (!is_string($email) || $email === '') { return null; } return findUserByEmail($email); } public function validateCredentials( AuthenticatableInterface $user, array $credentials ): bool { $password = $credentials['password'] ?? null; if (!is_string($password)) { return false; } return $this->hasher->verify( $password, $user->authPasswordHash() ); } }
Register the provider:
$manager->providerInstance( 'users', new DatabaseUserProvider( new PasswordHasher() ) );
Custom guard
Applications can implement:
Ivi\Auth\Contracts\GuardInterface
A custom guard could support:
- bearer tokens;
- API keys;
- signed cookies;
- request attributes;
- external identity providers;
- short-lived access tokens.
The initial package does not provide those implementations.
Authentication exceptions
Authentication failures may throw:
Ivi\Auth\Exceptions\AuthException
Example:
use Ivi\Auth\Exceptions\AuthException; try { $authenticated = $auth->attempt([ 'email' => $email, 'password' => $password, ]); } catch (AuthException $exception) { error_log( json_encode([ 'message' => $exception->getMessage(), 'context' => $exception->context(), ]) ); }
Invalid usernames or passwords normally return false.
Exceptions are reserved for configuration, storage, session and security failures.
Safe exception context
Passwords, hashes, tokens and credential values are not included in exception context.
$context = $exception->context();
Example:
[
'guard' => 'web',
'credential_fields' => [
'email',
'password',
],
]
Only credential field names are included.
Exception factories
use Ivi\Auth\Exceptions\AuthException; AuthException::invalidIdentifier( $identifier, $reason ); AuthException::invalidCredentials( $fields, $reason ); AuthException::authenticationFailed( $fields ); AuthException::userCannotAuthenticate( $identifier ); AuthException::userNotFound( $identifier ); AuthException::invalidPasswordHash( $reason ); AuthException::passwordHashFailed( $algorithm, $previous ); AuthException::passwordVerificationFailed( $previous ); AuthException::guardNotFound( $guard ); AuthException::invalidGuardConfiguration( $guard, $reason ); AuthException::providerNotFound( $provider ); AuthException::invalidProviderConfiguration( $provider, $reason ); AuthException::sessionUnavailable( $reason ); AuthException::sessionReadFailed( $guard, $previous ); AuthException::sessionWriteFailed( $guard, $previous ); AuthException::logoutFailed( $guard, $previous );
Complete example
<?php declare(strict_types=1); use App\Auth\User; use Ivi\Auth\Auth; use Ivi\Auth\Exceptions\AuthException; use Ivi\Auth\PasswordHasher; require __DIR__ . '/vendor/autoload.php'; $hasher = new PasswordHasher(); $user = new User( id: 1, name: 'Gaspard', passwordHash: $hasher->hash( 'secure-password' ), active: true ); $auth = Auth::fromConfig([ 'default' => 'web', 'providers' => [ 'users' => [ 'driver' => 'array', 'users' => [ [ 'user' => $user, 'credentials' => [ 'email' => 'gaspard@example.com', 'username' => 'gaspard', ], ], ], 'password_field' => 'password', ], ], 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', 'session_key' => '_ivi_auth_web', 'auto_start' => true, 'regenerate_on_login' => true, 'regenerate_on_logout' => true, ], ], ]); try { if ($_SERVER['REQUEST_METHOD'] === 'POST') { $authenticated = $auth->attempt([ 'email' => $_POST['email'] ?? null, 'password' => $_POST['password'] ?? null, ]); if (!$authenticated) { http_response_code(401); echo 'Invalid credentials.'; exit; } header('Location: /dashboard'); exit; } if ($auth->guest()) { http_response_code(401); echo 'Authentication required.'; exit; } $currentUser = $auth->user(); echo 'Welcome, ' . htmlspecialchars( $currentUser?->authDisplayName() ?? 'User', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ); } catch (AuthException $exception) { error_log( json_encode([ 'message' => $exception->getMessage(), 'context' => $exception->context(), ]) ); http_response_code(500); echo 'Authentication is temporarily unavailable.'; }
Available classes
AuthenticatableInterface
$user->authIdentifier(); $user->authPasswordHash(); $user->authDisplayName(); $user->canAuthenticate();
UserProviderInterface
$provider->retrieveById( $identifier ); $provider->retrieveByCredentials( $credentials ); $provider->validateCredentials( $user, $credentials );
GuardInterface
$guard->provider(); $guard->check(); $guard->guest(); $guard->user(); $guard->id(); $guard->attempt( $credentials ); $guard->login($user); $guard->logout();
PasswordHasher
$hasher->hash($password); $hasher->verify( $password, $hash ); $hasher->needsRehash($hash); $hasher->verifyAndRehash( $password, $hash ); $hasher->information($hash); $hasher->isValidHash($hash); $hasher->algorithm(); $hasher->algorithmName(); $hasher->options();
ArrayUserProvider
$provider->hasher(); $provider->passwordField(); $provider->add( $user, $credentials, $replace ); $provider->replace( $user, $credentials ); $provider->has($identifier); $provider->remove($identifier); $provider->clear(); $provider->all(); $provider->count(); $provider->retrieveById( $identifier ); $provider->retrieveByCredentials( $credentials ); $provider->validateCredentials( $user, $credentials ); $provider->attributes( $identifier );
SessionGuard
$guard->name(); $guard->sessionKey(); $guard->provider(); $guard->check(); $guard->guest(); $guard->user(); $guard->id(); $guard->attempt( $credentials ); $guard->validate( $credentials ); $guard->login($user); $guard->logout(); $guard->forgetUser(); $guard->setUser($user); $guard->sessionActive();
AuthManager
AuthManager::fromConfig( $configuration ); $manager->defaultName(); $manager->setDefault($guard); $manager->registerGuard( $name, $definition, $replace ); $manager->guardInstance( $name, $guard, $replace ); $manager->replaceGuard( $name, $definition ); $manager->hasGuard($name); $manager->isGuardResolved($name); $manager->guardNames(); $manager->guard($name); $manager->get($name); $manager->forgetGuard($name); $manager->registerProvider( $name, $definition, $replace ); $manager->providerInstance( $name, $provider, $replace ); $manager->replaceProvider( $name, $definition ); $manager->hasProvider($name); $manager->isProviderResolved($name); $manager->providerNames(); $manager->provider($name); $manager->forgetProvider($name); $manager->forgetResolvedGuards(); $manager->forgetResolvedProviders(); $manager->flush();
Auth
Auth::fromConfig( $configuration ); $auth->manager(); $auth->guardName(); $auth->guard(); $auth->using($guard); $auth->usingDefault(); $auth->provider(); $auth->check(); $auth->guest(); $auth->user(); $auth->id(); $auth->attempt( $credentials ); $auth->login($user); $auth->logout(); $auth->defaultGuardName(); $auth->setDefaultGuard($guard); $auth->hasGuard($guard); $auth->guardNames(); $auth->hasProvider($provider); $auth->providerNames();
AuthException
$exception->getMessage(); $exception->context(); $exception->getPrevious();
Security considerations
Use HTTPS
Authentication credentials must be transmitted over HTTPS.
Never send login forms or authenticated session cookies over an unencrypted connection.
Configure session cookies securely
Recommended production settings include:
session.cookie_httponly = 1 session.cookie_secure = 1 session.cookie_samesite = Lax session.use_strict_mode = 1 session.use_only_cookies = 1
The correct SameSite value depends on the application architecture.
Regenerate session identifiers
The session guard regenerates identifiers after login and logout by default.
Do not disable this without another fixation-protection mechanism.
Add CSRF protection
Session authentication does not protect forms from cross-site request forgery.
Applications must add CSRF protection to state-changing requests.
Rate-limit login attempts
The package does not include rate limiting.
Applications should limit authentication attempts by:
- account;
- email;
- username;
- IP address;
- device identifier;
- combined risk signals.
Use generic login errors
Do not reveal whether an account exists.
Prefer:
Invalid credentials.
Avoid:
The email exists, but the password is incorrect.
Store only password hashes
Never store plain-text passwords.
Use:
$hasher->hash($password);
before persisting a password.
Protect session storage
Session storage must be accessible only to trusted application processes.
Do not store sessions in publicly readable directories.
Validate identity state on every restoration
SessionGuard calls:
$user->canAuthenticate()
when restoring the identity.
This allows suspended or disabled accounts to lose access on the next request.
Use constant-time password verification
Password verification is delegated to:
password_verify()
Do not compare password hashes manually.
Avoid authentication secrets in logs
Do not log:
- passwords;
- password hashes;
- session identifiers;
- session cookies;
- access tokens;
- reset tokens;
- API keys.
AuthException avoids adding credential values to diagnostic context.
Destroy broader session state when required
SessionGuard::logout() removes only the authentication identifier owned by the guard.
Applications handling highly sensitive data may also choose to:
- clear other user-specific session values;
- destroy the entire session;
- rotate CSRF tokens;
- revoke other active sessions;
- invalidate remember-me tokens.
Design principles
- Small framework-independent API
- Explicit authentication contracts
- Stable identity identifiers
- Secure native password hashing
- Named authentication guards
- Named user providers
- Lazy service resolution
- Session fixation protection
- Account-state verification
- Generic authentication failures
- No plain-text password storage
- No secret values in exception context
- No hidden global authentication state
- No built-in application model dependency
- Extensible provider and guard contracts
Package boundaries
This package is responsible for:
- authentication contracts;
- password hashing;
- password verification;
- identity retrieval;
- credential validation;
- authentication guards;
- native session authentication;
- named guard management;
- named provider management;
- authentication exceptions.
It is not responsible for:
- authorization;
- roles;
- permissions;
- policies;
- access-control lists;
- registration;
- email verification;
- password-reset workflows;
- multi-factor authentication;
- OAuth;
- OpenID Connect;
- JWT;
- API tokens;
- remember-me cookies;
- session databases;
- user models;
- database schemas;
- login controllers;
- HTML forms;
- CSRF protection;
- login rate limiting.
These features can be provided by higher-level IviPHP packages or application code.
Current limitations
The initial release does not include:
- database user providers;
- ORM integration;
- bearer-token guards;
- API-key guards;
- JWT authentication;
- persistent remember-me authentication;
- password-reset tokens;
- email verification;
- multi-factor authentication;
- social login;
- OAuth clients;
- authorization policies;
- roles and permissions;
- login throttling;
- concurrent session management;
- session revocation lists.
The package intentionally starts with focused authentication primitives.
Ecosystem
This package is part of the IviPHP ecosystem:
iviphp/contractsiviphp/supportiviphp/configiviphp/containeriviphp/httpiviphp/validationiviphp/databaseiviphp/cacheiviphp/viewiviphp/authiviphp/framework
Contributing
Contributions should preserve the focused and secure design of the package.
Changes should:
- avoid exposing credential values;
- preserve constant-time password verification;
- preserve secure session regeneration defaults;
- keep authentication separate from authorization;
- remain independent from application user models;
- preserve disabled-account checks;
- avoid hidden global state;
- avoid user-enumeration leaks;
- provide explicit failures for invalid configuration;
- support PHP 8.2 and later.
Security
Please report security vulnerabilities privately to the Softadastra maintainers instead of opening a public issue.
License
This project is licensed under the MIT License.
Maintainer
Created and maintained by Softadastra.