Authentication and Authorization¶
Overview¶
Phalcon\Auth provides authentication (who the user is) and authorization (what the user may do) behind a single facade, Phalcon\Auth\Manager.
The layer is composed of four building blocks:
- Guards answer "is there an authenticated user, and who is it?". Two ship in the box:
session(cookie/session backed, with remember-me and HTTP Basic) andtoken(bearer/API token). - Adapters are user providers - they load user records from a backing store. Three ship in the box:
memory,model(Phalcon ORM), andstream(JSON file). - Access gates answer "is this action allowed?". Two ship in the box:
auth(requires a logged-in user) andguest(requires no logged-in user). AuthUseris the authenticated user value object returned by guards.
Guards resolve the framework services they need (request, cookies, session manager) from a container, so they wire against the real application singletons. The container can be the modern Phalcon\Container\Container or the classic Phalcon\Di\Di.
Bootstrapping with ManagerFactory¶
Phalcon\Auth\ManagerFactory builds a fully wired Manager from a configuration tree. It needs a password hasher (Phalcon\Encryption\Security) and a container that provides the framework services the guards consume - either a Phalcon\Container\Container or a Phalcon\Di\DiInterface. The built-in Phalcon\Container\Provider\Web registers those services on a Phalcon\Container\Container.
<?php
use Phalcon\Auth\Access\Auth;
use Phalcon\Auth\Access\Guest;
use Phalcon\Auth\ManagerFactory;
use Phalcon\Container\ContainerFactory;
use Phalcon\Container\Provider\Web;
use Phalcon\Encryption\Security;
$container = (new ContainerFactory())
->addProvider(new Web())
->newContainer();
/**
* Alternative
*
$container = new Phalcon\Di\FactoryDefault();
*/
$factory = new ManagerFactory(new Security(), $container);
$manager = $factory->load([
'guards' => [
'web' => [
'type' => 'session',
'default' => true,
'adapter' => [
'name' => 'model',
'options' => ['model' => Users::class],
],
],
'api' => [
'type' => 'token',
'adapter' => [
'name' => 'model',
'options' => ['model' => Users::class],
],
'options' => [
'inputKey' => 'api_token',
'storageKey' => 'api_token',
],
],
],
'access' => [
'auth' => Auth::class,
'guest' => Guest::class,
],
]);
load() accepts an array or a Phalcon\Config\ConfigInterface. Each guard entry declares its type (session or token), an adapter (name plus adapter options), and optional guard options. Exactly one guard should be marked default.
The Container¶
Every entry point that takes a container - ManagerFactory::__construct(), the guards' fromOptions() factories, and the adapter, guard, and access locators - accepts either of two container types:
Phalcon\Container\Container, the modern service container (it implementsPhalcon\Contracts\Container\Service\Collection).Phalcon\Di\DiInterface, the classic dependency-injection container (for examplePhalcon\Di\FactoryDefault).
Passing any other type throws a \TypeError.
The layer resolves services by their interface ID, through the container's has() and get() methods. Whichever container is used, these services must be bound under the following IDs:
| Service ID | Required by |
|---|---|
Phalcon\Http\RequestInterface | session and token guards |
Phalcon\Http\Response\CookiesInterface | session guard |
Phalcon\Session\ManagerInterface | session guard |
A service that is not bound throws Phalcon\Auth\Exception. Phalcon\Container\Provider\Web registers these IDs on a Phalcon\Container\Container. A classic Phalcon\Di\FactoryDefault does not: it registers request and cookies under short names and ships no session manager. Bind the three interface IDs explicitly before passing it to the factory.
<?php
use Phalcon\Auth\ManagerFactory;
use Phalcon\Di\FactoryDefault;
use Phalcon\Encryption\Security;
use Phalcon\Http\Request;
use Phalcon\Http\Response\Cookies;
use Phalcon\Session\Adapter\Stream;
use Phalcon\Session\Manager as SessionManager;
$di = new FactoryDefault();
// Bind the services the guards resolve, under their interface IDs
$di->setShared('Phalcon\Http\RequestInterface', Request::class);
$di->setShared('Phalcon\Http\Response\CookiesInterface', Cookies::class);
$di->setShared('Phalcon\Session\ManagerInterface', function () {
$session = new SessionManager();
$session->setAdapter(new Stream(['savePath' => '/var/app/cache/session']));
return $session;
});
$factory = new ManagerFactory(new Security(), $di);
A token-only setup needs only Phalcon\Http\RequestInterface bound.
The Manager¶
The Manager is the entry point for both authentication and authorization. Authentication calls delegate to the active guard; authorization calls drive the active access gate.
<?php
// Authenticate against the default guard
if ($manager->attempt(['email' => 'jane@example.com', 'password' => 's3cret'])) {
$user = $manager->user();
echo $user->getAuthIdentifier();
}
$manager->check(); // bool - is someone authenticated on the default guard
$manager->id(); // int|string|null - the authenticated identifier
// Reach a specific guard by name
$manager->guard('api')->validate(['api_token' => $token]);
$manager->logout();
| Method | Returns | Purpose |
|---|---|---|
guard(?string $name = null) | Guard | The named guard, or the default guard when $name is null |
attempt(array $credentials = [], bool $remember = false) | bool | Authenticate against the default guard (requires a stateful guard) |
validate(array $credentials = []) | bool | Verify credentials without logging in |
check() | bool | Whether a user is authenticated |
user() | AuthUser or null | The authenticated user |
id() | int, string, or null | The authenticated identifier |
logout() | void | Log out of the default guard (requires a stateful guard) |
access(string $name) | self | Activate an access gate for the current request |
only(string ...$actions) | self | Restrict the active gate to the listed actions |
except(string ...$actions) | self | Apply the active gate to every action except those listed |
attempt() and logout() throw Phalcon\Auth\Exception when the default guard is not stateful. The token guard is stateless; use validate() with it.
Guards¶
Session Guard¶
Phalcon\Auth\Guard\Session keeps the authenticated identifier in the session manager and supports remember-me cookies and HTTP Basic. It implements GuardStateful and BasicAuth.
<?php
$session = $manager->guard('web');
// Verify credentials, start a session, set a remember-me cookie
$session->attempt(
['email' => 'jane@example.com', 'password' => 's3cret'],
true
);
// Log a known user in by identifier
$session->loginById(42);
// One-off authentication that does not persist a session
$session->once(['email' => 'jane@example.com', 'password' => 's3cret']);
// HTTP Basic authentication against the 'email' field
$session->basic('email');
$session->logout();
Remember-me requires an adapter that implements Phalcon\Contracts\Auth\Adapter\RememberAdapter (the model adapter does). Passing true to attempt()/login() with an adapter that does not implement it throws Phalcon\Auth\Exceptions\DoesNotImplement.
The session key and remember-cookie name are configurable through the guard options:
| Option | Default | Purpose |
|---|---|---|
name | auth | Session key holding the authenticated identifier |
rememberName | remember | Remember-me cookie name |
suffix | none | Derives auth_<suffix> / remember_<suffix> names for multi-guard apps |
Token Guard¶
Phalcon\Auth\Guard\Token authenticates API requests by a token. It reads the token from the request input key, or from an Authorization: Bearer <token> header. It is stateless - it has no login()/logout().
<?php
$api = $manager->guard('api');
// Resolves the user from the request token (input key or Bearer header)
$user = $api->user();
// Verify a token explicitly
$api->validate(['api_token' => 'the-token-value']);
| Option | Purpose |
|---|---|
inputKey | Request field that carries the token (for example api_token) |
storageKey | Column/field the adapter matches the token against |
Both options are required and must be non-empty; an empty value throws Phalcon\Auth\Exceptions\ConfigRequiresNonEmptyValue.
User Providers (Adapters)¶
Adapters load user rows and verify passwords. All three share password verification via Phalcon\Encryption\Security::checkHash() against the user's stored hash.
Model Adapter¶
Phalcon\Auth\Adapter\Model loads users through the Phalcon ORM. The model class must implement Phalcon\Contracts\Auth\AuthUser; for remember-me it must also implement Phalcon\Contracts\Auth\AuthRemember.
'adapter' => [
'name' => 'model',
'options' => [
'model' => Users::class,
'idColumn' => 'id', // optional, defaults to 'id'
],
],
Memory Adapter¶
Phalcon\Auth\Adapter\Memory serves an in-memory list of user rows. It is useful for tests and small read-only user sets.
'adapter' => [
'name' => 'memory',
'options' => [
'users' => [
['id' => 1, 'email' => 'jane@example.com', 'password' => '<hashed>'],
['id' => 2, 'email' => 'john@example.com', 'password' => '<hashed>'],
],
],
],
Stream Adapter¶
Phalcon\Auth\Adapter\Stream reads users from a JSON file containing an array of user records.
[
{"id": 1, "email": "jane@example.com", "password": "<hashed>"},
{"id": 2, "email": "john@example.com", "password": "<hashed>"}
]
The stream adapter throws Phalcon\Auth\Exceptions\FileDoesNotExist, FileCannotRead, FileNotValidJson, or FileDoesNotContainJson when the file is missing, unreadable, or not a JSON array.
The Authenticated User¶
When a model adapter is not configured, array-backed adapters (memory, stream) return a Phalcon\Auth\AuthUser value object. Its data must contain a scalar id key (int or string); otherwise the constructor throws Phalcon\Auth\Exceptions\DataMustContainIdKey.
<?php
$user = $manager->user();
$user->getAuthIdentifier(); // int|string - the 'id'
$user->getAuthPassword(); // string - the stored hash, or '' when absent
$user->toArray(); // the underlying user row
A model adapter returns your model instance directly, which implements the same Phalcon\Contracts\Auth\AuthUser contract.
Authorization (Access Gates)¶
An access gate decides whether the current action is allowed. The built-in gates read guard state:
Phalcon\Auth\Access\Authallows the action when a user is authenticated.Phalcon\Auth\Access\Guestallows the action when no user is authenticated.
Activate a gate on the manager and scope it to specific actions with only() or except():
<?php
// Require authentication for 'dashboard' and 'profile' only
$manager->access('auth')->only('dashboard', 'profile');
// Apply the gate to every action except 'logout'
$manager->access('auth')->except('logout');
Gates are enforced per dispatch by a listener. Phalcon\Auth\Mvc\AuthDispatcherListener guards MVC dispatch; Phalcon\Auth\Cli\AuthDispatcherListener guards CLI tasks. Attach the listener to the dispatcher events manager:
<?php
use Phalcon\Auth\Mvc\AuthDispatcherListener;
use Phalcon\Events\Manager as EventsManager;
$eventsManager = new EventsManager();
$eventsManager->attach('dispatch', new AuthDispatcherListener($manager));
$dispatcher->setEventsManager($eventsManager);
The listener is a no-op when no gate is active. When a gate denies the action, the listener forwards to the gate's redirectTo() target if one is provided; otherwise it throws Phalcon\Auth\Exceptions\AccessDenied. The default redirectTo() returns null - override a gate to supply a forward target.
<?php
use Phalcon\Auth\Access\Auth;
class RedirectToLogin extends Auth
{
public function redirectTo(): array | null
{
return ['controller' => 'session', 'action' => 'login'];
}
}
Events¶
The session guard fires events around login and logout on its events manager. Attach an events manager to the guard to observe them.
<?php
use Phalcon\Events\Manager as EventsManager;
$eventsManager = new EventsManager();
$eventsManager->attach('auth', function ($event, $guard) {
// $event->getType(): beforeLogin, afterLogin, beforeLogout, afterLogout
});
$manager->guard('web')->setEventsManager($eventsManager);
| Event | Fired |
|---|---|
auth:beforeLogin | Before a login is applied |
auth:afterLogin | After a successful login |
auth:beforeLogout | Before logout |
auth:afterLogout | After logout |
Exceptions¶
Every exception thrown by the layer extends Phalcon\Auth\Exception. Granular subclasses live under Phalcon\Auth\Exceptions. Catch the parent to handle any failure, or a subclass to react to a single cause.
<?php
use Phalcon\Auth\Exception;
use Phalcon\Auth\Exceptions\AccessDenied;
try {
$manager->access('auth')->only('admin');
// ... dispatch ...
} catch (AccessDenied $ex) {
// authorization denied for an action with no redirect target
} catch (Exception $ex) {
// any other auth failure
}
| Exception | Thrown when |
|---|---|
AccessDenied | An access gate denies an action and no redirect target is set |
ConfigRequiresNonEmptyValue | A guard/adapter config receives a required empty value |
DataMustContainIdKey | An AuthUser is built from data with no scalar id |
DoesNotImplement | Remember-me is used with an adapter/model that lacks the required contract |
FileDoesNotExist | The stream adapter file is missing |
FileCannotRead | The stream adapter file cannot be read |
FileNotValidJson | The stream adapter file is not valid JSON |
FileDoesNotContainJson | The stream adapter file does not decode to an array |