Search by

echo-fusion / authmanager

amir-shadanfar

Authentication for EchoFusion: session and token guards, pluggable user providers and PSR-15 middleware. Depends only on echo-fusion/contracts and PSR interfaces.

Package info

github.com/echo-fusion/authmanager

pkg:composer/echo-fusion/authmanager

Statistics

Installs: 265

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.2 2026-10-12 12:22 UTC

This package is auto-updated.

Last update: 2026-10-12 16:07:19 UTC


README

CI Packagist PHP License

AuthManager handles authentication for the EchoFusion framework: who is the current user, log in, log out, and protecting routes. It supports session and token authentication from the start, chosen in configuration, and anything else you plug in.

It depends only on echo-fusion/contracts and PSR interfaces. It does not require sessionmanager, databasemanager, an ORM or an entity class: it talks to a SessionInterface, a ConnectionInterface and a PSR-17 response factory, and the application decides what sits behind them.

GuardInterface            (who is the user for this request?)
 ├─ SessionGuard          stateful: keeps the user id in the session
 ├─ TokenGuard            stateless: the user owns the token sent with the request
 └─ your own guard        e.g. JWT, API signature ...

UserProviderInterface     (where do users come from?)
 ├─ DatabaseUserProvider  any ConnectionInterface: PDO, Doctrine DBAL ...
 ├─ ArrayUserProvider     config / tests
 └─ your own provider     LDAP, an ORM, a remote API ...

Part of EchoFusion, a modular PHP framework built from small packages.

Installation

composer require echo-fusion/authmanager

Requirements

  • PHP 8.1, 8.2 or 8.3

Configuration

return [
    'default' => 'web',

    'guards' => [
        'web' => ['driver' => 'session', 'provider' => 'users', 'key' => 'auth_user'],
        'api' => ['driver' => 'token',   'provider' => 'users',
                  'header' => 'Authorization', 'scheme' => 'Bearer',   // Authorization: Bearer <token>
                  'query' => 'api_token'],                              // optional ?api_token=<token>
        'internal' => ['driver' => 'token', 'provider' => 'users',
                       'header' => 'X-Api-Key', 'scheme' => null],      // bare token in a custom header
        'custom' => ['class' => MyGuard::class, 'provider' => 'users'], // new MyGuard($provider, $definition, $container)
    ],

    'providers' => [
        'users' => ['driver' => 'database', 'table' => 'users', 'id' => 'id', 'password' => 'password', 'token' => 'api_token'],
        'staff' => ['driver' => 'database', 'connection' => 'staff_db', 'table' => 'staff'],   // a named connection
        'fixed' => ['driver' => 'array', 'users' => [['id' => 1, 'email' => 'a@b.c', 'password' => '<hash>']]],
        'ldap'  => ['class' => LdapProvider::class],                    // new LdapProvider($definition, $container)
    ],

    'hasher' => ['algorithm' => 'bcrypt', 'options' => ['cost' => 12]], // or 'argon2id'

    'middleware' => [
        'authenticate' => ['guard' => 'web', 'redirect_to' => '/login'],
        'guest'        => ['guard' => 'web', 'redirect_to' => '/'],
    ],
];

Usage

use EchoFusion\AuthManager\AuthManager;

$auth = new AuthManager($container, $config);   // $container: any PSR-11 container, see "Requirements of the container"

$web = $auth->statefulGuard('web');

if ($web->attempt(['email' => $email, 'password' => $password])) {
    // logged in: the session id was regenerated and the user id stored
}

$web->check();      // bool
$web->guest();      // bool
$web->id();         // the user id, or null
$web->user();       // UserInterface or null
$web->logout();

$web->login($user); // log a user in without checking a password (e.g. right after registration)

attempt() looks the user up by every credential except password (so ['email' => ...], or ['email' => ..., 'active' => 1]), then checks the password against the stored hash.

Register a user

/** @var \EchoFusion\AuthManager\Users\DatabaseUserProvider $users */
$users = $auth->provider('users');
$user = $users->create(['name' => 'Ann', 'email' => 'ann@example.com', 'password' => $plainPassword]);   // the password is hashed
$web->login($user);

API tokens

$api = $auth->guard('api');      // reads the token from the request, so give it one:
$api->setRequest($request);      // the middleware below does this for you
$api->user();

Tokens are stored as a SHA-256 hash, never in plain text. Create one like this:

$token = bin2hex(random_bytes(32));
$connection->execute('UPDATE users SET api_token = ? WHERE id = ?', [hash('sha256', $token), $userId]);
// give $token to the client once; it cannot be recovered from the database

Middleware (PSR-15)

use EchoFusion\AuthManager\Middleware\AuthenticateMiddleware;
use EchoFusion\AuthManager\Middleware\GuestMiddleware;

new AuthenticateMiddleware($auth, $responseFactory, 'web', '/login');   // guard name, redirect target
new GuestMiddleware($auth, $responseFactory, 'web', '/');
  • AuthenticateMiddleware lets authenticated requests through and adds the request attributes auth.user and auth.guard. Otherwise browsers are redirected to the given URL, and API clients (Accept: application/json, X-Requested-With: XMLHttpRequest, or no redirect configured) get a 401 JSON response.
  • GuestMiddleware is for pages like login: logged-in browsers are redirected, API clients get a 403.

Neither one ever calls header() or exit(); they return PSR-7 responses.

Security notes

  • Passwords use password_hash (bcrypt cost 12 by default, or argon2id). bcrypt only reads the first 72 bytes, so longer passwords are rejected instead of silently truncated.
  • The session id is regenerated on login and logout (session fixation).
  • A user that has been deleted since the last request is dropped from the session.
  • The database provider validates table and column names and binds every value; lookups by credentials never compare the password in SQL.
  • Token lookups use the hash of the token. A leaked database does not leak working tokens.

Requirements of the container

AuthManager receives a PSR-11 container and fetches what a configured guard needs, only when it is first used:

Needed for Container entry
a session guard EchoFusion\Contracts\SessionManager\SessionInterface
a database provider EchoFusion\Contracts\DatabaseManager\ConnectionInterface (or DatabaseManagerInterface when the provider names a connection)
the middleware (via the service provider) Psr\Http\Message\ResponseFactoryInterface

Service provider

EchoFusion\AuthManager\Providers\AuthManagerProvider registers AuthManagerInterface, GuardInterface (the default guard), PasswordHasherInterface, AuthenticateMiddleware and GuestMiddleware, and configures the manager from the auth section of your configuration on boot().

Your own guard or provider

Implement GuardInterface (or StatefulGuardInterface) or UserProviderInterface from the contracts and reference the class with 'class'. A guard that needs the request (like the token guard) can also implement EchoFusion\AuthManager\Guards\RequestAwareGuardInterface; the middleware hands it the request.

Testing

composer test

Credits

License

The MIT License (MIT). Please see LICENSE for more information.