Config file
core/App/config/pbauth.php is the component's single control panel. It is where you change templates, validation rules, redirects, user groups, limits, social login, two-factor and even the controllers themselves.
The file belongs to the site: the component only reads it and never ships it, so an update cannot overwrite it. You do not need to create it — the installer puts down a stub with every setting listed, commented out.
The shipped values sit next to it, in core/components/pbauth/config/defaults.php. Reading is fine, editing is not: a component file gets overwritten on update.
How values are merged
Dictionaries are extended key by key, all the way down; lists the site replaces wholesale.
// shipped // the site // result
'rules' => [ 'rules' => [ 'rules' => [
'register' => [ 'register' => [ 'register' => [
'username' => '…', 'phone' => '…', 'username' => '…',
'email' => '…', ], 'email' => '…',
], ], 'phone' => '…',
], ],
],
'user_groups' => [], 'user_groups' => ['Users'] 'user_groups' => ['Users']Otherwise rules.profile would have to be rewritten in full for the sake of one field, and the list of groups could never be shortened — only extended.
Only the shipped side decides what is a dictionary
An empty section on the site's side ('rules' => [] — and that is exactly what an unfilled stub looks like) would itself count as a list and would wipe out every shipped rule. So "dictionary or list" is decided by the shipped value, not by yours.
Validation rules are merged per field: an unknown field is added, a known one is replaced, null removes the shipped one entirely.
Templates and forms
'views' => [
'auth' => 'file:auth/templates/auth',
'profile' => 'file:auth/templates/profile',
],
'forms' => [
'login' => 'form.login',
'register' => 'form.register',
'forgot_password' => 'form.forgotPassword',
'reset_password' => 'form.resetPassword',
'change_password' => 'form.changePassword',
'confirm_password' => 'form.confirmPassword',
'resend_verification' => 'form.resendVerification',
'two_factor_challenge'=> 'form.twoFactorChallenge',
'two_factor' => 'form.twoFactor',
'social_email' => 'form.socialEmail',
'profile' => 'form.profile',
],Template resolution: a key named after the action beats a key named after the wrapper — views.two_factor outranks views.profile. forms.<action> = null means "do not pass $form to the template at all": the site's template renders the form itself.
More detail, with examples, in the quick start.
Where to send people after success
'redirects' => [
'login' => '/profile',
'logout' => '/',
'reset_password' => '/profile',
'verify_email' => '/profile',
'impersonate' => '/',
],Back to where they came from
If someone was bounced to the login page off a closed one, you can send them back. The name of the GET parameter is a setting; an empty string turns the option off:
'login_redirect_param' => 'redirect',Add a hidden field to the login form, and build the login link with the parameter (/login?redirect=/profile/ads):
<input type="hidden" name="redirect" value="{$.get.redirect|escape}">Only paths inside the site are accepted
The value is reduced to a path: anything with a scheme (https://…), with a host, or protocol-relative //host is dropped, and the person goes to the usual address. Without that the login form would become an open redirect — a ready-made phishing tool with a link to your own domain.
Groups and avatars
'user_groups' => ['Users'],
'avatar_path' => 'assets/images/avatars/:user_id',user_groups is a list, so the site replaces it wholesale. The shipped value is empty: a new user lands in no group at all until you say otherwise.
Limits on sending mail
'register_ip_limit' => 3, // registrations from one IP per hour
'resend_verification_limit' => 3, // repeat links to one address per hour
'forgot_password_limit' => 3, // recovery letters to one address per hour0 means no limit. The limits are not there for spam as such: the recovery and resend forms accept any address and answer identically, so without a limit they can be used to mail letters to other people from your domain.
One session per account
'single_session' => false,By default one login can be used from any number of devices — that is how MODX works. For a paid account it means a single subscription gets passed around ten people.
With true a new login closes the previous one: whoever logged in earlier is logged out on their very next action and sees "this account has been logged in from another device" on the login form.
How it is done. On login a session id is pinned to the account, and on every request it is compared against the current one. A mismatch means another session has already replaced this one, so this one is closed. Nobody hunts down and kills other people's sessions: session storage varies — files, database, redis — and this way it behaves the same everywhere.
What it does not touch: the MODX manager (logging into the panel does not close the site session or the other way round) and the "Log in on the site" button — a manager who looked at the site through a user's eyes does not put that user out the door.
Turning it on throws nobody out. Users who are already logged in have no pinned session yet, and their very first request simply pins the current one. Eviction starts from the next login.
A plugin event is required
The setting needs OnHandleRequest attached to the pbAuth plugin with a priority earlier than PageBlocks (-10 in the package). The package does this itself; by hand only where the component was laid out as files, bypassing the package.
Two-factor and social login
Both subsystems are configured right here, but they have a page of their own covering how they work and what to watch out for: "Social login, 2FA, impersonation".
'two_factor_enabled' => true,
'two_factor_window' => 1,
'two_factor_challenge_ttl' => 300,
'two_factor_attempts' => 5,
'two_factor_backup_codes' => 8,
'two_factor_issuer' => '',
'social' => [
'enabled' => true,
'require_email' => true,
'providers' => [/* … */],
'drivers' => [],
],Controller replacement
'controllers' => [
'register' => \PageBlocks\App\Http\Controllers\Auth\RegisterController::class,
],The last lever, for when settings and events were not enough. Worked through with an example in "Events and your own code".
Event listeners
'listeners' => [
Dispatcher::AFTER_REGISTER => [SendWelcomeLetter::class],
],Fourteen events, the list and the contents of $params — in "Events and your own code".
The whole file
Write down only what differs from the shipped values.
<?php
use Boshnik\PbAuth\Events\Dispatcher;
return [
// Page wrapper templates
'views' => [
'auth' => 'file:auth/templates/auth',
'profile' => 'file:auth/templates/profile',
],
// Form chunk for the page. null — do not pass $form to the template
'forms' => [
'login' => 'form.login',
'register' => 'form.register',
'profile' => 'form.profile',
],
// Form fields and validation rules. null removes a shipped field
'rules' => [
'register' => ['phone' => 'required|string'],
'profile' => ['fullname' => null],
],
// Where to send people after success
'redirects' => [
'login' => '/',
'logout' => '/',
'reset_password' => '/',
'verify_email' => '/',
'impersonate' => '/',
],
// "Log in on the site" from the manager
'impersonate_enabled' => true,
// One session per account: a new login closes the previous one
'single_session' => false,
// Name of the GET parameter for the return after login. '' — turn off
'login_redirect_param' => 'redirect',
// Groups a new user lands in. A list — replaced wholesale
'user_groups' => ['Users'],
// Where to put avatars, :user_id is substituted
'avatar_path' => 'assets/images/avatars/:user_id',
// Limits on letters. 0 — no limit
'register_ip_limit' => 3,
'resend_verification_limit' => 3,
'forgot_password_limit' => 3,
// Login confirmation by code
'two_factor_enabled' => true,
'two_factor_window' => 1,
'two_factor_challenge_ttl' => 300,
'two_factor_attempts' => 5,
'two_factor_backup_codes' => 8,
'two_factor_issuer' => '',
// Login through third-party services
'social' => [
'enabled' => true,
'require_email' => true,
'providers' => [
'google' => ['client_id' => '', 'client_secret' => ''],
],
'drivers' => [],
],
// Your own controllers instead of the shipped ones
'controllers' => [],
// Your own classes on events
'listeners' => [
Dispatcher::AFTER_REGISTER => [],
],
];The component carries a commented sample itself: core/components/pbauth/docs/pbauth.config.example.php.