Skip to content

AuthManager

Ray Fung edited this page Feb 26, 2026 · 3 revisions

AuthManager

Razy provides a multi-guard authentication system with an authorization gate, middleware support, and pluggable guard implementations. The system supports multiple authentication strategies simultaneously (e.g. session-based for web, token-based for API).


Table of Contents


Quick Start

use Razy\Auth\AuthManager;

use Razy\Auth\CallbackGuard;

use Razy\Auth\GenericUser;



// Create auth manager

$auth = new AuthManager(defaultGuard: 'web');



// Register a session-based guard

$auth->addGuard('web', new CallbackGuard(

    userResolver: function () {

        $userId = $_SESSION['user_id'] ?? null;

        if ($userId) {

            return new GenericUser(['id' => $userId, 'name' => 'John']);

        }

        return null;

    },

    credentialValidator: function (array $credentials) {

        return $credentials['password'] === 'secret';

    }

));



// Check authentication

if ($auth->check()) {

    $user = $auth->user();

    echo "Welcome, " . $user->getAttribute('name');

}

AuthManager — Multi-Guard

The AuthManager manages multiple named guards and proxies common auth methods to the default guard:

use Razy\Auth\AuthManager;



$auth = new AuthManager(defaultGuard: 'web');



// Register guards

$auth->addGuard('web', $sessionGuard);

$auth->addGuard('api', $tokenGuard);



// Use default guard

$auth->check();          // is authenticated?

$auth->guest();          // is guest?

$auth->user();           // get authenticated user or null

$auth->id();             // get user identifier

$auth->validate($creds); // validate credentials

$auth->setUser($user);   // manually set user



// Use specific guard

$auth->guard('api')->check();

$auth->guard('api')->user();



// Inspect guards

$auth->hasGuard('api');         // true

$auth->getGuardNames();         // ['web', 'api']

$auth->getDefaultGuard();       // 'web'

$auth->setDefaultGuard('api');

Guards

CallbackGuard

A flexible guard that uses closures for user resolution and credential validation:

use Razy\Auth\CallbackGuard;

use Razy\Auth\GenericUser;



$guard = new CallbackGuard(

    userResolver: function () {

        // Resolve the authenticated user from session, token, etc.

        $token = $_SERVER['HTTP_AUTHORIZATION'] ?? '';

        if ($token && $userData = verifyToken($token)) {

            return new GenericUser($userData);

        }

        return null;

    },

    credentialValidator: function (array $credentials) {

        // Validate credentials (login attempt)

        $user = findUserByEmail($credentials['email']);

        return $user && password_verify($credentials['password'], $user['password_hash']);

    }

);



// Lazy resolution → userResolver is called once on first user() call

$user = $guard->user();    // calls resolver

$user = $guard->user();    // returns cached result



// Reset for next request (worker mode)

$guard->reset();           // clears cached user, re-resolves on next call

Custom Guards

Implement GuardInterface for custom authentication strategies:

use Razy\Contract\GuardInterface;

use Razy\Contract\AuthenticatableInterface;



class JwtGuard implements GuardInterface

{

    private ?AuthenticatableInterface $user = null;



    public function check(): bool

    {

        return $this->user() !== null;

    }



    public function guest(): bool

    {

        return !$this->check();

    }



    public function user(): ?AuthenticatableInterface

    {

        if ($this->user === null) {

            $token = $this->extractBearerToken();

            if ($payload = $this->validateJwt($token)) {

                $this->user = new GenericUser($payload);

            }

        }

        return $this->user;

    }



    public function id(): string|int|null

    {

        return $this->user()?->getAuthIdentifier();

    }



    public function validate(array $credentials): bool

    {

        // Validate credentials without setting user

        return $this->attemptLogin($credentials);

    }



    public function setUser(AuthenticatableInterface $user): void

    {

        $this->user = $user;

    }



    // ... private helper methods

}

GenericUser

A simple AuthenticatableInterface implementation using an associative array:

use Razy\Auth\GenericUser;



$user = new GenericUser([

    'id'       => 42,

    'name'     => 'John Doe',

    'email'    => 'john@example.com',

    'password' => '$2y$10$...',

    'role'     => 'admin',

]);



$user->getAuthIdentifier();     // 42

$user->getAuthIdentifierName(); // 'id' (default)

$user->getAuthPassword();       // '$2y$10$...'

$user->getAttribute('name');    // 'John Doe'

$user->getAttribute('role');    // 'admin'

$user->hasAttribute('email');   // true

$user->getAttributes();         // full array



// Custom identifier name

$user = new GenericUser([

    '__identifier_name' => 'uuid',

    'uuid' => 'abc-123',

    'name' => 'Jane',

]);

$user->getAuthIdentifierName(); // 'uuid'

$user->getAuthIdentifier();     // 'abc-123'

Authorization Gate

The Gate class provides ability-based and policy-based authorization:

Defining Abilities

use Razy\Auth\Gate;



$gate = new Gate($auth);



// Define abilities with closures

$gate->define('edit-post', function (?AuthenticatableInterface $user, $post) {

    return $user && $user->getAuthIdentifier() === $post['author_id'];

});



$gate->define('delete-post', function (?AuthenticatableInterface $user, $post) {

    return $user && $user->getAttribute('role') === 'admin';

});



$gate->define('create-post', function (?AuthenticatableInterface $user) {

    return $user !== null; // any authenticated user

});



// Check abilities

$gate->allows('edit-post', $post);    // true/false

$gate->denies('edit-post', $post);    // true/false

$gate->has('edit-post');               // true (ability exists)



// Check multiple

$gate->check(['edit-post', 'delete-post'], $post);  // ALL must pass

$gate->any(['edit-post', 'delete-post'], $post);     // at least one

$gate->none(['edit-post', 'delete-post'], $post);    // none pass



// Authorize → throws AccessDeniedException if denied

$gate->authorize('edit-post', $post);



// Check as specific user

$gate->forUser($anotherUser)->allows('edit-post', $post);

Policies

Map model classes to policy classes for organized authorization:

// Register policies

$gate->policy(Post::class, PostPolicy::class);

$gate->policy(Comment::class, CommentPolicy::class);



// Policy class → methods match ability names (kebab → camelCase)

class PostPolicy

{

    // 'edit-post' → editPost()

    public function editPost(?AuthenticatableInterface $user, Post $post): bool

    {

        return $user && $user->getAuthIdentifier() === $post->authorId;

    }



    // 'delete-post' → deletePost()

    public function deletePost(?AuthenticatableInterface $user, Post $post): bool

    {

        return $user && $user->getAttribute('role') === 'admin';

    }



    // 'create-post' → createPost()

    public function createPost(?AuthenticatableInterface $user): bool

    {

        return $user !== null;

    }

}



// Usage

$gate->allows('edit-post', $post);   // invokes PostPolicy::editPost()

Interceptors

Before/after interceptors can override individual ability checks:

// Before → runs BEFORE ability check; return non-null to override

$gate->before(function (?AuthenticatableInterface $user, string $ability) {

    // Super admin can do anything

    if ($user && $user->getAttribute('role') === 'superadmin') {

        return true; // bypass all checks

    }

    return null; // continue to normal check

});



// After → runs AFTER ability check; can override the result

$gate->after(function (?AuthenticatableInterface $user, string $ability, bool $result) {

    // Log all authorization decisions

    logger()->info("Gate: {$ability} = " . ($result ? 'allowed' : 'denied'));

    return null; // return non-null to override $result

});

Middleware

AuthMiddleware

Require authentication for a route/controller:

use Razy\Auth\AuthMiddleware;



$middleware = new AuthMiddleware(

    auth: $authManager,

    guard: 'api',                    // null = default guard

    onUnauthorized: function () {    // custom 401 handler

        http_response_code(401);

        echo json_encode(['error' => 'Unauthorized']);

    }

);



// In a pipeline context

$result = $middleware->handle($context, function ($context) {

    // Only reached if authenticated

    return handleRequest($context);

});

AuthorizeMiddleware

Require a specific ability:

use Razy\Auth\AuthorizeMiddleware;



$middleware = new AuthorizeMiddleware(

    gate: $gate,

    ability: 'edit-post',

    argumentResolver: function (array $context) {

        return [$context['post']]; // arguments for the ability check

    },

    onForbidden: function () {

        http_response_code(403);

        echo json_encode(['error' => 'Forbidden']);

    }

);

Password Hashing

The Hash class provides a static utility for password hashing:

use Razy\Auth\Hash;



// Hash a password (bcrypt by default)

$hash = Hash::make('my-secret-password');

$hash = Hash::make('password', PASSWORD_ARGON2ID, ['memory_cost' => 65536]);



// Verify

$valid = Hash::check('my-secret-password', $hash); // true



// Check if rehash needed (algorithm/options changed)

if (Hash::needsRehash($hash)) {

    $newHash = Hash::make($plainTextPassword);

    // update stored hash

}



// Inspect hash info

$info = Hash::info($hash);

// ['algo' => 1, 'algoName' => 'bcrypt', 'options' => ['cost' => 10]]

Contracts

GuardInterface

interface GuardInterface {

    public function check(): bool;

    public function guest(): bool;

    public function user(): ?AuthenticatableInterface;

    public function id(): string|int|null;

    public function validate(array $credentials): bool;

    public function setUser(AuthenticatableInterface $user): void;

}

AuthenticatableInterface

interface AuthenticatableInterface {

    public function getAuthIdentifier(): string|int;

    public function getAuthIdentifierName(): string;

    public function getAuthPassword(): string;

}

API Reference

AuthManager

| Method | Signature | Description |

| --- | --- | --- |

| __construct | (array $guards = [], string $defaultGuard = 'default') | Create manager |

| addGuard | (string $name, GuardInterface $guard): static | Register guard |

| guard | (?string $name = null): GuardInterface | Get guard (throws on unknown) |

| setDefaultGuard | (string $name): static | Change default |

| getDefaultGuard | (): string | Get default name |

| hasGuard | (string $name): bool | Check guard exists |

| getGuardNames | (): array | All guard names |

| check | (): bool | Authenticated? (default guard) |

| guest | (): bool | Guest? (default guard) |

| user | (): ?AuthenticatableInterface | Get user (default guard) |

| id | (): string\|int\|null | Get user ID (default guard) |

| validate | (array $credentials): bool | Validate (default guard) |

| setUser | (AuthenticatableInterface $user): void | Set user (default guard) |

Gate

| Method | Signature | Description |

| --- | --- | --- |

| define | (string $ability, Closure $callback): static | Define ability |

| policy | (string $modelClass, string $policyClass): static | Register policy |

| before | (Closure $callback): static | Before interceptor |

| after | (Closure $callback): static | After interceptor |

| allows | (string $ability, mixed ...$args): bool | Check allowed |

| denies | (string $ability, mixed ...$args): bool | Check denied |

| check | (array $abilities, mixed ...$args): bool | All must pass |

| any | (array $abilities, mixed ...$args): bool | At least one |

| none | (array $abilities, mixed ...$args): bool | None pass |

| authorize | (string $ability, mixed ...$args): void | Throws AccessDeniedException |

| forUser | (AuthenticatableInterface $user): static | Scoped clone |

| has | (string $ability): bool | Ability exists? |

Hash

| Method | Signature | Description |

| --- | --- | --- |

| make | (string $password, string\|int\|null $algo = PASSWORD_BCRYPT, array $options = []): string | Hash password |

| check | (string $password, string $hash): bool | Verify password |

| needsRehash | (string $hash, string\|int\|null $algo = PASSWORD_BCRYPT, array $options = []): bool | Check rehash needed |

| info | (string $hash): array | Hash info |

← Previous: Authenticator

RateLimiter

Clone this wiki locally