iviphp/auth

Authentication guards, user providers and password hashing for the IviPHP ecosystem.

Maintainers

Package info

github.com/iviphp/auth

pkg:composer/iviphp/auth

Transparency log

Statistics

Installs: 2

Dependents: 1

Suggesters: 0

Stars: 2

Open Issues: 0

v0.1.0 2026-07-21 15:39 UTC

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/contracts
  • iviphp/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:

  1. Authenticatable identity An application user or account that can be authenticated.

  2. User provider Retrieves identities and validates supplied credentials.

  3. Guard Maintains authentication state for the current application context.

  4. 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/contracts
  • iviphp/support
  • iviphp/config
  • iviphp/container
  • iviphp/http
  • iviphp/validation
  • iviphp/database
  • iviphp/cache
  • iviphp/view
  • iviphp/auth
  • iviphp/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.