Authentication guards, user providers and secure password hashing for the IviPHP ecosystem.
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.
composer require iviphp/auth- 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());'src/
├── Auth.php
├── AuthManager.php
├── PasswordHasher.php
├── Contracts/
│ ├── AuthenticatableInterface.php
│ ├── GuardInterface.php
│ └── UserProviderInterface.php
├── Exceptions/
│ └── AuthException.php
├── Guards/
│ └── SessionGuard.php
└── Providers/
└── ArrayUserProvider.php
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.
Application identities must implement:
Ivi\Auth\Contracts\AuthenticatableInterfaceExample:
<?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;
}
}The authentication identifier must be stable across requests.
Supported types:
int|stringExamples include:
- database primary keys;
- UUIDs;
- immutable account identifiers;
- external identity identifiers.
The identifier should not contain passwords, access tokens or other secrets.
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.
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.
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.
$valid = $hasher->verify(
'correct horse battery staple',
$hash
);The method returns:
true
when the password matches, otherwise:
false
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.
$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
);
}$hasher = new PasswordHasher(
PASSWORD_BCRYPT,
[
'cost' => 12,
]
);The bcrypt cost must be between 4 and 31.
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.
$information = $hasher->information($hash);Example result:
[
'algorithm' => '2y',
'algorithm_name' => 'bcrypt',
'options' => [
'cost' => 12,
],
]The original hash is not included in the result.
if (!$hasher->isValidHash($hash)) {
throw new RuntimeException(
'Unsupported password hash.'
);
}The initial package includes an in-memory user provider:
Ivi\Auth\Providers\ArrayUserProviderIt is suitable for:
- automated tests;
- development tools;
- small internal applications;
- predefined administrative identities;
- applications that already have identity objects in memory.
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',
],
],
]);$provider->add(
$user,
[
'email' => 'gaspard@example.com',
'username' => 'gaspard',
]
);Built-in searchable attributes are added automatically:
id
identifier
display_name
$provider->replace(
$updatedUser,
[
'email' => 'new@example.com',
'username' => 'gaspard',
]
);$provider->has(1);Integer and string identifiers remain distinct:
$provider->has(42);
$provider->has('42');These can represent different identities.
$user = $provider->retrieveById(1);When the identity is missing:
nullis returned.
$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.
$valid = $provider->validateCredentials(
$user,
[
'email' => 'gaspard@example.com',
'password' => 'secret-password',
]
);$provider = new ArrayUserProvider(
users: [],
passwordField: 'passphrase'
);Credentials then use:
[
'email' => 'gaspard@example.com',
'passphrase' => 'secret-password',
]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.
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();The initial authentication guard uses native PHP sessions:
Ivi\Auth\Guards\SessionGuardCreate 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.
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'
);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.
$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.
if ($guard->check()) {
$user = $guard->user();
}Check whether the current request is unauthenticated:
if ($guard->guest()) {
redirectToLogin();
}$user = $guard->user();Possible result:
AuthenticatableInterface|nullThe identity is restored lazily from the session.
$id = $guard->id();Possible result:
int|string|null$guard->login($user);Direct login still checks:
$user->canAuthenticate()The user's identifier is stored in the session.
$guard->logout();Only the authentication key owned by the guard is removed.
Other application session data remains available.
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.
SessionGuard provides an additional method:
$user = $guard->validate([
'email' => 'gaspard@example.com',
'password' => 'secret-password',
]);The method returns:
AuthenticatableInterface|nullIt does not create an authenticated session.
This can be useful for:
- confirming a password;
- sensitive account actions;
- reauthentication;
- API token creation;
- administrative confirmation.
$guard->forgetUser();The session identifier remains unchanged.
The next call to user() reloads the identity through the provider.
$guard->setUser($updatedUser);The supplied identity must have the same identifier as the current authenticated session.
$active = $guard->sessionActive();The method returns true when:
session_status() === PHP_SESSION_ACTIVECreate 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'
);$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',
],
],
]);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,
],
],
],
],Session-guard configuration:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
'session_key' => '_application_auth',
'auto_start' => true,
'regenerate_on_login' => true,
'regenerate_on_logout' => true,
],
],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.
$provider = $manager->provider('users');Providers are also resolved lazily and reused.
$manager->registerGuard(
'admin',
[
'driver' => 'session',
'provider' => 'administrators',
'session_key' => '_admin_auth',
]
);$manager->guardInstance(
'web',
$guard
);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.
$manager->replaceGuard(
'web',
[
'driver' => 'session',
'provider' => 'users',
'session_key' => '_new_auth_key',
]
);The previously resolved guard is discarded.
The replacement is resolved lazily.
$manager->forgetGuard('admin');When the removed guard was the default, the first remaining guard becomes the default.
$manager->registerProvider(
'administrators',
[
'driver' => 'array',
'users' => $administrators,
]
);$manager->providerInstance(
'users',
$provider
);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();
}
);$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.
$manager->forgetProvider('users');Guards referencing the removed provider fail when resolved again.
Return the default guard name:
$name = $manager->defaultName();Change it:
$manager->setDefault('admin');The guard must already be registered.
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();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.
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',
],
],
]);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();$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(); // adminReturn to the default guard:
$defaultAuth = $adminAuth->usingDefault();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();Applications can implement:
Ivi\Auth\Contracts\UserProviderInterfaceExample 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()
)
);Applications can implement:
Ivi\Auth\Contracts\GuardInterfaceA 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 failures may throw:
Ivi\Auth\Exceptions\AuthExceptionExample:
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.
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.
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
);<?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.';
}$user->authIdentifier();
$user->authPasswordHash();
$user->authDisplayName();
$user->canAuthenticate();$provider->retrieveById(
$identifier
);
$provider->retrieveByCredentials(
$credentials
);
$provider->validateCredentials(
$user,
$credentials
);$guard->provider();
$guard->check();
$guard->guest();
$guard->user();
$guard->id();
$guard->attempt(
$credentials
);
$guard->login($user);
$guard->logout();$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();$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
);$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::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::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();$exception->getMessage();
$exception->context();
$exception->getPrevious();Authentication credentials must be transmitted over HTTPS.
Never send login forms or authenticated session cookies over an unencrypted connection.
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 = 1The correct SameSite value depends on the application architecture.
The session guard regenerates identifiers after login and logout by default.
Do not disable this without another fixation-protection mechanism.
Session authentication does not protect forms from cross-site request forgery.
Applications must add CSRF protection to state-changing requests.
The package does not include rate limiting.
Applications should limit authentication attempts by:
- account;
- email;
- username;
- IP address;
- device identifier;
- combined risk signals.
Do not reveal whether an account exists.
Prefer:
Invalid credentials.
Avoid:
The email exists, but the password is incorrect.
Never store plain-text passwords.
Use:
$hasher->hash($password);before persisting a password.
Session storage must be accessible only to trusted application processes.
Do not store sessions in publicly readable directories.
SessionGuard calls:
$user->canAuthenticate()when restoring the identity.
This allows suspended or disabled accounts to lose access on the next request.
Password verification is delegated to:
password_verify()Do not compare password hashes manually.
Do not log:
- passwords;
- password hashes;
- session identifiers;
- session cookies;
- access tokens;
- reset tokens;
- API keys.
AuthException avoids adding credential values to diagnostic context.
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.
- 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
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.
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.
This package is part of the IviPHP ecosystem:
iviphp/contractsiviphp/supportiviphp/configiviphp/containeriviphp/httpiviphp/validationiviphp/databaseiviphp/cacheiviphp/viewiviphp/authiviphp/framework
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.
Please report security vulnerabilities privately to the Softadastra maintainers instead of opening a public issue.
This project is licensed under the MIT License.
Created and maintained by Softadastra.