Voyti

Voyti

User management, authentication & authorization

войти
/vɐjˈtʲi/
verb

"to enter" or "to log in"

Voyti is a highly customizable user management, authentication, and authorization extension for Yii Framework 3, ported from 2amigos/yii2-usuario and rebuilt with PSR-15 middleware, PSR-11 DI, ActiveRecord entities, FormModel forms, and yiisoft/rbac.

Requirements

Quick Start

1. Install

For reCAPTCHA support (optional):

For 2FA TOTP support (optional):

2. Run migrations

Voyti provides its migration path through config/params-console.php using the standard yiisoft/db-migration configuration keys. With yiisoft/db-migration enabled in your console app, run:

One migration creates the user, user_profile, user_social_account, user_token, user_sessions, user_backup_code, user_password_history, and audit_log tables with all columns (2FA, GDPR, password expiration, last login IP, etc.) included. config/params-console.php also registers yiisoft/rbac-db's own item/assignment migrations, so ./yii migrate:up creates the RBAC tables too — no separate step needed.

If the user table is still empty after these migrations run, a default admin account is seeded automatically: username admin, email admin@example.com, and a random 20-character password printed to the console — copy it immediately, it isn't stored anywhere else. The account is assigned the administrator role, which is granted the administratorPermissionName permission (voyti-admin-dashboard by default) needed to reach the admin dashboard. Change this password immediately after first login. If the user table already has rows (e.g. re-running migrations on an existing database), seeding is skipped entirely.

3. Register routes

Routes are not auto-registered — you must add them to your router configuration.

Pull the voyti-routes config group into your router definition. The example below mounts them under a /user/ prefix as their own group, alongside your app's own routes — change the prefix as needed:

use Yiisoft\Config\Config;
use Yiisoft\Definitions\DynamicReference;
use Yiisoft\Router\Group;
use Yiisoft\Router\RouteCollection;
use Yiisoft\Router\RouteCollectionInterface;
use Yiisoft\Router\RouteCollector;
use Yiisoft\Session\SessionMiddleware;
use YiiRocks\Voyti\Middleware\VoytiMiddleware;

/** @var Config $config */

return [
    RouteCollectionInterface::class => [
        'class' => RouteCollection::class,
        '__construct()' => [
            'collector' => DynamicReference::to(
                static fn() => (new RouteCollector())
                    ->addRoute(
                        Group::create('/')
                            ->middleware(
                                SessionMiddleware::class,                 // required for site-wide session support
                                VoytiMiddleware::class,                   // see "Site-wide enforcement" below
                            )
                            ->routes(...$config->get('routes')),          // your own app routes
                        Group::create('/user/')
                            ->routes(...$config->get('voyti-routes')),
                    )
            ),
        ],
    ],
];

voyti-routes already wraps itself with its own required middleware (see config/routes.php), so the second group above doesn't repeat any of it, and adding VoytiMiddleware to your own group only extends that same coverage to your app's pages.

When enableRestApi is true, the API routes are mounted under adminRestPrefix . '/v1/' and expose user CRUD endpoints.

The privacy/GDPR routes (settings/privacy/, settings/privacy/gdpr-consent, settings/privacy/export, settings/privacy/anonymize, settings/privacy/delete) and the two-factor routes (settings/two-factor/, settings/two-factor/enable, settings/two-factor/disable/) are likewise only registered when their governing config flag (enableGdprCompliance, allowAccountDelete, and/or enableTwoFactorAuthentication) is true — see the route table below. When a flag is off, the corresponding route doesn't exist at all, so a request to it falls through to the host application's own router-level not-found handling.

4. Done

DI bindings, event listeners, and console commands are auto-registered via the Yii3 config plugin. No manual wiring needed.

Configuration

Override Voyti params in your app's config/params.php using the yiirocks/voyti key:

use YiiRocks\Voyti\Enum\RecaptchaVersion;

return [
    'yiirocks/voyti' => [
        'appName' => 'My Project',
        'recaptchaVersion' => RecaptchaVersion::V3,
    ],
];

Below are all top-level yiirocks/voyti options, followed by the nested socialNetworkClients options.

General

appName string
Application name — used as TOTP issuer in 2FA QR codes and {app} placeholder in mail subjects. Default: 'Voyti'.
homeRoute string
Route to redirect to after a successful login (password, 2FA, or social) or logout. Must be a route registered by the host app — an unregistered route name throws a LogicException naming the misconfigured option, rather than a bare router exception. Default: 'home'.

Authentication & Registration

enableRegistration bool
Allow new user registration. Default: true.
enableSocialNetworkRegistration bool
Allow social network registration. Default: true.
socialNetworkClients array
OAuth client IDs, secrets, and provider-specific options. Default: [].
enableEmailConfirmation bool
Require email confirmation. Default: true.
allowPasswordRecovery bool
Allow password recovery. Default: true.
allowAdminPasswordRecovery bool
Allow admin-initiated password recovery. Default: false.
allowAccountDelete bool
Allow users to delete their account. Default: false.
emailChangeConfirmation EmailChangeConfirmation
NONE (change immediately), NEW (confirm new address only), or BOTH (confirm both old and new addresses). Default: EmailChangeConfirmation::NEW.
rememberLoginLifespan int
Remember-me cookie lifetime and idle auth timeout in seconds. Default: 2592000.
tokenConfirmationLifespan int
Confirmation token validity. Default: 86400.
tokenRecoveryLifespan int
Recovery token validity. Default: 21600.
enableSwitchIdentities bool
Allow admin to switch user identities. Default: true.
mailAdminOnRegister ?string
Email notified on new registration. Default: null.
recaptchaVersion ?RecaptchaVersion
RecaptchaVersion::V2, RecaptchaVersion::V3, or null to disable. Default: null.

Two-Factor Authentication

enableTwoFactorAuthentication bool
Enable 2FA. Default: false.
twoFactorAuthenticationForcedPermissions array
Permissions that require 2FA. Default: [].
'yiirocks/voyti' => [
    'enableTwoFactorAuthentication' => true,
    'twoFactorAuthenticationForcedPermissions' => ['admin'],
],

GDPR

enableGdprCompliance bool
Enable GDPR features. Default: false.
gdprAnonymizePrefix string
Prefix for anonymized usernames. Default: 'GDPR'.
gdprExportProperties array
Properties included in the data export (JSON). userSessions exports each login's ip, user_agent, created_at, updated_at (the internal session_id is excluded); userSocialAccount exports each linked account's provider, username, email, created_at, and data (the decoded provider profile payload). The OAuth code field is excluded — it's a one-time linking secret, not user data. Default: ['email', 'username', 'userProfile.public_email', 'userProfile.name', 'userProfile.gravatar_email', 'userProfile.location', 'userProfile.website', 'userProfile.bio', 'userProfile.birthday', 'userSessions', 'userSocialAccount'].
'yiirocks/voyti' => [
    'enableGdprCompliance' => true,
    'gdprAnonymizePrefix' => 'ANON-',
],

Session & Security

disableIpLogging bool
Disable IP address logging. Default: false.
enablePasswordExpiration bool
Enable password expiration. Default: false.
maxPasswordAge ?int
Max password age in days. Default: null.
enablePasswordComplexity bool
Require passwords to contain an uppercase letter, a lowercase letter, a digit, and a special character. Default: false.
passwordHistoryLimit int
Number of previous passwords remembered per user to prevent reuse. Only enforced when enablePasswordExpiration is true. Default: 10.
administratorPermissionName string
Permission name granting admin access. Default: 'voyti-admin-dashboard'.
profileVisibility ProfileVisibility
Profile visibility: OWNER = owner only, ADMIN = owner + admins, USERS = any authenticated user, PUBLIC = public. Default: ProfileVisibility::USERS.
enableAuditLog bool
Record admin actions (RBAC and user management changes) to the audit_log table, viewable at admin/audit-log/. Default: true.

Views & Mail

viewPath string
Base path for web templates. Default: __DIR__ . '/../resources/views/bootstrap5'.
mailPath string
Base path for mail templates. Default: __DIR__ . '/../resources/mail'.

REST API

enableRestApi bool
Enable REST API. Default: false.
adminRestPrefix string
REST API base URL prefix. Default: 'api'.
apiTokenLifespan int|null
API token lifetime in seconds; null means tokens never expire. Default: null.

The REST API authenticates via a Bearer token, not the web session/CSRF cookie — issue one with voyti:api-token:generate (see Console commands) and send it as Authorization: Bearer <token>. AccessRuleMiddleware still applies afterwards to enforce administratorPermissionName.

Social Authentication Providers

socialNetworkClients is a keyed array where each key is a provider name such as github, google, or keycloak.

Every provider accepts these options unless noted otherwise:

clientId string
OAuth client/application ID issued by the provider. Required.
clientSecret string
OAuth client secret issued by the provider. Required.
redirectUri string
Overrides the callback URL; otherwise Voyti uses the absolute route URL for voyti/session-auth. Optional, default: generated callback URL.
scope string
Replaces the built-in default scope string. Optional, default: provider default.
enabled bool
If false, the provider is not registered and no button is rendered. Optional, default: true.
authorizationParams array<string, scalar>
Extra query parameters appended to the authorization request. Optional, default: [].
tokenParams array<string, scalar>
Extra fields merged into the token exchange request body. Optional, default: [].
userInfoQuery array<string, scalar>
Extra query parameters merged into the user-info request. Optional, default: [].

Only Keycloak adds recognized extra options:

Provider key Extra option Type Required Description
keycloakbaseUrlstringyesBase Keycloak URL, for example https://sso.example.com
keycloakrealmstringyesKeycloak realm name used to build auth, token, and userinfo endpoints

Social Authentication

Various auth clients are included. Each implements the auth client interface and maps provider attributes to the SocialNetworkAccount entity:

The SocialAuthProviderService handles the OAuth redirect/callback flow. The UserSocialAuthenticateService handles account lookup, creation, and user login. The UserSocialAccountConnectService links a social account to an existing user.

Built-in defaults by provider

The following table shows Voyti's built-in endpoints and scopes. These are used unless you override scope or redirectUri.

Provider key Default scope Authorization URL Token URL User info URL
facebookemailhttps://www.facebook.com/v19.0/dialog/oauthhttps://graph.facebook.com/v19.0/oauth/access_tokenhttps://graph.facebook.com/me
githubuser:emailhttps://github.com/login/oauth/authorizehttps://github.com/login/oauth/access_tokenhttps://api.github.com/user
googleopenid email profilehttps://accounts.google.com/o/oauth2/v2/authhttps://oauth2.googleapis.com/tokenhttps://openidconnect.googleapis.com/v1/userinfo
keycloakopenid email profile{baseUrl}/realms/{realm}/protocol/openid-connect/auth{baseUrl}/realms/{realm}/protocol/openid-connect/token{baseUrl}/realms/{realm}/protocol/openid-connect/userinfo
linkedinopenid profile emailhttps://www.linkedin.com/oauth/v2/authorizationhttps://www.linkedin.com/oauth/v2/accessTokenhttps://api.linkedin.com/v2/userinfo
microsoft365openid profile email User.Readhttps://login.microsoftonline.com/common/oauth2/v2.0/authorizehttps://login.microsoftonline.com/common/oauth2/v2.0/tokenhttps://graph.microsoft.com/oidc/userinfo
vkontakteemailhttps://oauth.vk.com/authorizehttps://oauth.vk.com/access_tokenhttps://api.vk.com/method/users.get
xtweet.read users.read offline.accesshttps://twitter.com/i/oauth2/authorizehttps://api.twitter.com/2/oauth2/tokenhttps://api.twitter.com/2/users/me
yandexlogin:email login:infohttps://oauth.yandex.com/authorizehttps://oauth.yandex.com/tokenhttps://login.yandex.ru/info

Provider-specific built-in request behavior

Voyti also applies a few provider-specific defaults during the user-info step:

Provider key Built-in behavior
facebookSends access_token and fields=id,name,email on the user-info request.
githubIf /user does not include an email, Voyti also requests https://api.github.com/user/emails and picks the first primary or verified address.
vkontakteSends access_token, fields=screen_name, and v=5.199; email is read from the token response when present.
xSends user.fields=id,name,username,profile_image_url; normalized social identity does not include email (X API v2 does not expose the user's email address without elevated access).
yandexSends format=json on the user-info request.

Example

return [
    'yiirocks/voyti' => [
        'socialNetworkClients' => [
            'github' => [
                'clientId' => $_ENV['GITHUB_CLIENT_ID'] ?? '',
                'clientSecret' => $_ENV['GITHUB_CLIENT_SECRET'] ?? '',
            ],
            'google' => [
                'clientId' => $_ENV['GOOGLE_CLIENT_ID'] ?? '',
                'clientSecret' => $_ENV['GOOGLE_CLIENT_SECRET'] ?? '',
            ],
        ],
    ],
];

With credentials configured:

  1. The login page shows social login buttons for configured providers.
  2. settings/networks lists connected providers and renders connect buttons for the remaining configured providers.
  3. New social identities redirect to the registration connect screen, where users can log in to an existing account or register a new one before the identity is linked.

Console Commands

Command Description
voyti:create Create a new user
voyti:delete Delete a user
voyti:confirm Confirm a user's email
voyti:password Change a user's password
voyti:api-token:generate Generate a REST API access token for a user (printed once)
voyti:api-token:revoke Revoke all REST API access tokens for a user

Middleware

The extension ships eight PSR-15 middleware classes for session handling and access control:

Middleware Description Auto-registered on the extension's own routes?
AccessRuleMiddleware Redirects guests to the login page (voyti/session-login); checks administratorPermissionName for admin access Yes — on admin/* (users and RBAC management) and the REST API group
RequireLoginMiddleware Redirects guests to the login page (voyti/session-login); unlike AccessRuleMiddleware, only requires an authenticated user, not an admin permission. Returns a JSON 401 instead of a redirect for the one AJAX-only route, voyti/user-two-factor-renew Yes — on settings/* (profile, account, social networks, sessions, privacy, two-factor)
ApiTokenAuthenticationMiddleware Resolves the Authorization: Bearer <token> header to a user for that request only (no session); returns 401 if missing/invalid Yes — on the REST API group, ahead of AccessRuleMiddleware, in place of the session cookie
RememberMeMiddleware Logs a guest back in from the autoLogin remember-me cookie, then writes the cookie back onto the response — either the immediate reissue after a session rotation or the periodic sliding-expiration refresh. Must run after session middleware and before the enforcement middleware below, since those need CurrentUser already resolved Yes
SessionRevocationEnforceMiddleware Logs out and redirects to the login page (voyti/session-login) when the current session's user_sessions row is gone — i.e. it was terminated from the sessions list (self-service or admin) on another request. Without this, terminating a session only removed the row; the browser that owned it stayed logged in until its PHP session expired on its own. Otherwise touches the row's updated_at on every request, so the sessions list can show "last seen" activity per device. Yes
PasswordAgeEnforceMiddleware Redirects to the account settings page (voyti/user-account) when maxPasswordAge is exceeded Yes, when enablePasswordExpiration is true — on the extension's whole web route group
TwoFactorAuthenticationEnforceMiddleware Redirects to the account settings page (voyti/user-account) when required permissions are assigned but 2FA isn't enabled No
VoytiMiddleware Convenience wrapper that chains RememberMeMiddleware, SessionRevocationEnforceMiddleware, PasswordAgeEnforceMiddleware, and TwoFactorAuthenticationEnforceMiddleware in a single middleware entry — add this to your app's route group instead of the four individual ones (see Site-wide enforcement) No

Site-wide enforcement

The auto-registration above only covers routes this extension definesconfig/routes.php can't attach middleware to routes your host app defines itself. Without it, a user with an expired password, missing 2FA, or a revoked session can still browse your app's own dashboard, home page, or any other route outside this extension — and a guest with a valid remember-me cookie won't be logged back in there either.

Add VoytiMiddleware to the Group wrapping your app's own routes — see the Register routes example above — or to a global middleware pipeline above routing if your app has one; make sure to place it after SessionMiddleware so CurrentUser is resolvable. Each sub-middleware checks its own feature flag, so disabled features are no-ops. Keep it scoped to your own routes, not the voyti-routes group.

RBAC Management

Built on yiisoft/rbac. The extension provides:

Available Routes

The library does not provide a menu model or navigation contract. It only exposes named routes that the host application can use in its own menu, sidebar, or access rules.

Every settings/* route below requires an authenticated user — RequireLoginMiddleware redirects a guest to voyti/session-login (or returns a JSON 401 for the AJAX-only voyti/user-two-factor-renew) before the request reaches the controller; see Middleware.

Route name Method Path Purpose
voyti/session-loginGET, POSTloginUser login
voyti/session-logoutGET, POSTlogoutUser logout
voyti/session-confirmGET, POSTconfirmTwo-factor confirmation step
voyti/session-authGETauth/{provider}Social auth callback
voyti/registration-registerGET, POSTregisterNew user registration
voyti/registration-confirmGET, POSTconfirm/{id}/{code}Email confirmation link
voyti/registration-resendGET, POSTresendResend confirmation email
voyti/registration-connectGETconnect/{code}Social registration link
voyti/password-reset-requestGET, POSTforgotPassword recovery request
voyti/password-reset-confirmGET, POSTrecover/{id}/{code}Password reset
voyti/profileGETprofile/{id}Public user profile
voyti/userGETsettings/User dashboard — welcome message and basic account info
voyti/user-profileGET, POSTsettings/profileProfile settings
voyti/user-accountGET, POSTsettings/accountAccount settings
voyti/user-account-confirmGETsettings/account/confirm/{code}Confirm account changes
voyti/user-social-networkGETsettings/networks/Linked social networks
voyti/user-social-network-deletePOSTsettings/networks/disconnect/{id}Disconnect social account
voyti/user-account-sessionsGETsettings/sessions/Self-service session/device list, current device highlighted
voyti/user-account-sessions-terminatePOSTsettings/sessions/terminate/{sessionId}Terminate one of the current user's own sessions
voyti/user-privacyGETsettings/privacy/Privacy settings. Only registered when enableGdprCompliance or allowAccountDelete is true
voyti/user-privacy-gdpr-consentGET, POSTsettings/privacy/gdpr-consentGDPR consent. Only registered when enableGdprCompliance is true
voyti/user-privacy-exportGETsettings/privacy/exportExport user data. Only registered when enableGdprCompliance is true
voyti/user-privacy-anonymizeGET, POSTsettings/privacy/anonymizeAnonymize account (blanks email/username, blocks login; row is kept). Only registered when enableGdprCompliance is true
voyti/user-privacy-deleteGET, POSTsettings/privacy/deleteAccount deletion (hard delete). Only registered when allowAccountDelete is true
voyti/user-two-factorGET, POSTsettings/two-factor/Two-factor status/entry point. Only registered when enableTwoFactorAuthentication is true
voyti/user-two-factor-googleGETsettings/two-factor/google/Google Authenticator setup page (method-selector buttons + QR/secret). Only registered when enableTwoFactorAuthentication is true
voyti/user-two-factor-emailGETsettings/two-factor/email/Email 2FA setup page (method-selector buttons + confirm/send screen). Only registered when enableTwoFactorAuthentication is true
voyti/user-two-factor-enablePOSTsettings/two-factor/enableEnable 2FA — shared by both the Google Authenticator and email code-entry forms. Only registered when enableTwoFactorAuthentication is true
voyti/user-two-factor-disablePOSTsettings/two-factor/disable/Disable 2FA. Only registered when enableTwoFactorAuthentication is true
voyti/user-two-factor-disable-send-codePOSTsettings/two-factor/disable/send-codeSend the disable-2FA one-time code. Only registered when enableTwoFactorAuthentication is true
voyti/user-two-factor-renewPOSTsettings/two-factor/google/renewRegenerate the Google Authenticator secret/QR code via AJAX. Only registered when enableTwoFactorAuthentication is true
voyti/user-two-factor-send-email-codePOSTsettings/two-factor/email/send-codeSend the email 2FA one-time code after explicit confirmation. Only registered when enableTwoFactorAuthentication is true
voyti/user-two-factor-regenerate-backup-codesPOSTsettings/two-factor/backup-codes/regenerateInvalidate existing backup codes and generate a fresh set (requires re-verifying the current 2FA method). Only registered when enableTwoFactorAuthentication is true
voyti/adminGETadmin/Admin dashboard
voyti/admin-usersGETadmin/users/Users
voyti/admin-users-createGET, POSTadmin/users/createCreate user
voyti/admin-users-updateGET, POSTadmin/users/update/{id}Update user
voyti/admin-users-update-profileGET, POSTadmin/users/update-profile/{id}Update user profile
voyti/admin-users-showGETadmin/users/info/{id}User details
voyti/admin-users-confirmPOSTadmin/users/confirm/{id}Confirm user
voyti/admin-users-deletePOSTadmin/users/delete/{id}Delete user
voyti/admin-users-blockPOSTadmin/users/block/{id}Block user
voyti/admin-users-switch-identityPOSTadmin/users/switch-identity/{id}Switch identity
voyti/admin-users-switch-identity-restorePOSTadmin/users/switch-identity/restoreRestore identity after impersonating
voyti/admin-users-password-resetPOSTadmin/users/password-reset/{id}Send password reset
voyti/admin-users-force-password-changePOSTadmin/users/force-password-change/{id}Force password change
voyti/admin-users-assignmentsGET, POSTadmin/users/assignments/{id}Manage RBAC assignments
voyti/admin-users-sessionsGETadmin/users/sessions/{id}Session management
voyti/admin-users-terminate-sessionsPOSTadmin/users/terminate-sessions/{id}Terminate sessions
voyti/admin-rbac-permissionsGETadmin/rbac/permissions/List permissions
voyti/admin-rbac-permissions-createGET, POSTadmin/rbac/permissions/createCreate permission
voyti/admin-rbac-permissions-updateGET, POSTadmin/rbac/permissions/update/{name}Update permission
voyti/admin-rbac-permissions-deletePOSTadmin/rbac/permissions/delete/{name}Delete permission
voyti/admin-rbac-rolesGETadmin/rbac/roles/List roles
voyti/admin-rbac-roles-createGET, POSTadmin/rbac/roles/createCreate role
voyti/admin-rbac-roles-updateGET, POSTadmin/rbac/roles/update/{name}Update role
voyti/admin-rbac-roles-deletePOSTadmin/rbac/roles/delete/{name}Delete role
voyti/admin-rbac-rulesGETadmin/rbac/rules/List rules
voyti/admin-rbac-rules-createGET, POSTadmin/rbac/rules/createCreate rule
voyti/admin-rbac-rules-updateGET, POSTadmin/rbac/rules/update/{name}Update rule
voyti/admin-rbac-rules-deletePOSTadmin/rbac/rules/delete/{name}Delete rule
voyti/admin-audit-logGETadmin/audit-log/Audit log of admin actions (RBAC and user management changes). Populated when enableAuditLog is true

The REST API routes below live in a separate route group mounted at adminRestPrefix and are only registered when enableRestApi is true — see REST API.

Route name Method Path Purpose
voyti/api-openapiGETopenapi.jsonOpenAPI 3.1 spec (JSON). Public, so tooling (Swagger UI, codegen) can fetch it without a Bearer token.
voyti/api-v1-users-indexGETv1/usersList users
voyti/api-v1-users-viewGETv1/users/{id}View a user
voyti/api-v1-users-createPOSTv1/usersCreate a user
voyti/api-v1-users-updatePATCHv1/users/{id}Update a user
voyti/api-v1-users-deleteDELETEv1/users/{id}Delete a user

Events & Listeners

Voyti dispatches events at key points in the user lifecycle, allowing your application to react, log, or extend behaviour. Each event carries a const string name to distinguish before/after variants. Attach your own listeners through the Yii3 event dispatcher configuration.

Events with default listeners

Event Trigger Default behavior
AfterLoginEventUser logs inTriggers password expiration check and session tracking
AfterRegisterEventNew user registrationSends admin notification email

Additional events (no default listeners)

Dispatched by the library, but nothing consumes them by default — attach your own listener via the event dispatcher configuration if you need to react to them.

Support

Create an issue →
Packagist Version PHP Version Downloads Last Commit GitHub Workflow Status