Authentication & authorization ^2.3
Authentication verifies who the user is. Authorization verifies what the user is allowed to do. These are two independent mechanisms — you can use either or both.
Looking for a ready-made solution?
See ProcessWire authentication for a built-in authenticator and login/logout service that uses ProcessWire's session system.
Authentication
Authentication is configured by passing an Authenticator subclass to the authenticate() method. This can be done at three levels: API, service, or endpoint.
use PwJsonApi\Api;
$api = new Api();
$api->authenticate(new ExampleAuth());When set on a level, all children inherit it. If the API instance has an authenticator, every service and endpoint under it is protected — there is no way to opt out. If you need both public and protected endpoints, set the authenticator on specific services instead of the API.
// Only MyProtectedService requires authentication
$api->addService(new MyPublicService());
$api->addService(new MyProtectedService(), function ($service) {
$service->authenticate(new ExampleAuth());
});Authenticator is not chained
If multiple levels define an authenticator, the closest to the endpoint wins (endpoint > service > API).
The Authenticator class
All authenticators extend the abstract Authenticator class. It provides access to the ProcessWire API via $this->wire (like ApiPlugin). The authenticate() method receives an AuthenticateArgs object and should throw AuthenticationException on failure.
use PwJsonApi\{AuthenticateArgs, AuthenticationException, Authenticator};
class ExampleAuth extends Authenticator
{
public function authenticate(AuthenticateArgs $args): void
{
if ($this->wire->user->isLoggedin() === false) {
throw new AuthenticationException();
}
}
}AuthenticateArgs contains:
| Property | Type | Description |
|---|---|---|
$request | Request | The current request |
$user | ProcessWire\User | The current ProcessWire user |
$event | ProcessWire\HookEvent | ProcessWire URL hook event |
AuthenticationException extends ApiException and returns a 401 response.
Authorization
Authorization is configured by passing a callback to the authorize() method. The callback receives an AuthorizeArgs object and returns bool — false results in a 403 response.
$service->authorize(function (AuthorizeArgs $args) {
return $args->user->hasRole('editor');
});Authorization is chained
Unlike authentication, all authorization callbacks in the chain are executed in order: API → services → endpoint. If any callback returns false, the request is rejected with AuthorizationException (403).
// API: must be logged in
$api->authorize(function (AuthorizeArgs $args) {
return $args->user->isLoggedin();
});
// Service: must have editor role
$api->addService(new ContentService(), function ($service) {
$service->authorize(function (AuthorizeArgs $args) {
return $args->user->hasRole('editor');
});
});
// Both callbacks run: first API, then serviceAuthorizeArgs
| Property | Type | Description |
|---|---|---|
$request | Request | The current request |
$user | ProcessWire\User | The current ProcessWire user |
$event | ProcessWire\HookEvent | ProcessWire URL hook event |
Execution order
Authentication and authorization run before plugins and request hooks:
- Authenticate — closest authenticator runs
- Authorize — all authorizers in chain run (API → services → endpoint)
- Before hooks
- Endpoint handler
- After hooks
Exceptions
Both AuthenticationException and AuthorizationException extend ApiException, so they can be caught in error hooks:
use PwJsonApi\{AuthenticationException, AuthorizationException};
$api->hookOnError(function ($args) {
if ($args->exception instanceof AuthenticationException) {
$args->response->with(['login_url' => '/login']);
}
});