tobento / app-user-web
User web support for the app providing features such as login, registration, password reset, logout and more.
Requires
- php: >=8.0
- tobento/app: ^1.0.7
- tobento/app-event: ^1.0
- tobento/app-http: ^1.0
- tobento/app-language: ^1.0
- tobento/app-message: ^1.0
- tobento/app-migration: ^1.0
- tobento/app-notifier: ^1.0
- tobento/app-rate-limiter: ^1.0
- tobento/app-spam: ^1.0
- tobento/app-translation: ^1.0
- tobento/app-user: ^1.0
- tobento/app-view: ^1.0
Requires (Dev)
- phpunit/phpunit: ^9.5
- tobento/app-console: ^1.0
- tobento/app-logging: ^1.0
- tobento/app-profiler: ^1.0
- tobento/app-testing: ^1.0
- vimeo/psalm: ^4.0
README
The User Web provides authentication features such as:
- login/logout
- two factor login
- remember me
- forgot password
- channel verification (email and smartphone)
- simple profile page where the user may update their profile data
- user notifications page
- multi-language support
Table of Contents
- Getting Started
- Documentation
- Credits
Getting Started
Add the latest version of the app user web project running this command.
composer require tobento/app-user-web
Requirements
- PHP 8.0 or greater
Documentation
App
Check out the App Skeleton if you are using the skeleton.
You may also check out the App to learn more about the app in general.
User Web Boot
The user web boot does the following:
- installs and loads user_web config
- installs view and translation files
use Tobento\App\AppFactory; // Create the app $app = (new AppFactory())->createApp(); // Add directories: $app->dirs() ->dir(realpath(__DIR__.'/../'), 'root') ->dir(realpath(__DIR__.'/../app/'), 'app') ->dir($app->dir('app').'config', 'config', group: 'config') ->dir($app->dir('root').'public', 'public') ->dir($app->dir('root').'vendor', 'vendor'); // Adding boots $app->boot(\Tobento\App\User\Web\Boot\UserWeb::class); // Run the app $app->run();
User Web Config
The configuration for the user web is located in the app/config/user_web.php
file at the default App Skeleton config location.
Features
Simply, configure any features you want to support in the app/config/user_web.php
feature section.
Home Feature
The Home Feature provides a simple home page. In case, you are not using this feature you need to adjust the "home" route in other features or add another route with the name home
.
Config
In the config file you can configure the home feature:
'features' => [ new Feature\Home(), // Or: new Feature\Home( // The view to render: view: 'user/home', // A menu name to show the home link or null if none. menu: 'main', menuLabel: 'Home', // A menu parent name (e.g. 'user') or null if none. menuParent: null, // If true, routes are being localized. localizeRoute: false, ), ],
Login Feature
The Login Feature provides a simple login page where a user can login by his email, smartphone or username and the password.
Config
In the config file you can configure the login feature:
use Tobento\App\RateLimiter\Symfony\Registry\SlidingWindow; 'features' => [ new Feature\Login(), // Or: new Feature\Login( // The view to render: view: 'user/login', // A menu name to show the login link or null if none. menu: 'header', menuLabel: 'Log in', // A menu parent name (e.g. 'user') or null if none. menuParent: null, // Specify a rate limiter: rateLimiter: new SlidingWindow(limit: 10, interval: '5 Minutes'), // see: https://github.com/tobento-ch/app-rate-limiter#available-rate-limiter-registries // Specify the identity attributes to be checked on login. identifyBy: ['email', 'username', 'smartphone', 'password'], // You may set a user verifier(s), see: https://github.com/tobento-ch/app-user#user-verifier /*userVerifier: function() { return new \Tobento\App\User\Authenticator\UserRoleVerifier('editor', 'author'); },*/ // The period of time from the present after which the auth token MUST be considered expired. expiresAfter: 1500, // int|\DateInterval // If you want to support remember. If set and the user wants to be remembered, // this value replaces the expiresAfter parameter. remember: new \DateInterval('P6M'), // null|int|\DateInterval // The message and redirect route if a user is authenticated. authenticatedMessage: 'You are logged in!', authenticatedRedirectRoute: 'home', // or null (no redirection) // The message shown if a login attempt fails. failedMessage: 'Invalid user or password.', // The redirect route after a successful login. successRoute: 'home', // The message shown after a user successfully log in. successMessage: 'Welcome back :greeting.', // or null // If set, it shows the forgot password link. Make sure the Feature\ForgotPassword is set too. forgotPasswordRoute: 'forgot-password.identity', // or null // The two factor authentication route. twoFactorRoute: 'twofactor.code.show', // If true, routes are being localized. localizeRoute: false, ), ],
Supporting Remember Me
You may support remember me functionality by the following steps:
1. Enable remember me
In the config, specify a value for the remember
parameter:
'features' => [ new Feature\Login( // If you want to support remember. If set and the user wants to be remembered, // this value replaces the expiresAfter parameter. remember: new \DateInterval('P6M'), //null|int|\DateInterval ), ],
The auth token will expire after 6 months unless the user logs out!
2. Use Suitable Token Storage
As remember me tokens are often long-lived, make sure you use the Repository Storage to store tokens, which is configured as default.
3. Add RememeredToken Middleware (optional)
In the app/config/user.php
add the RememberedToken::class
middleware after the User::class
middleware and specify the period of time from the present after which the token is considered as remembered.
use Tobento\App\User; 'middlewares' => [ // You may uncomment it and set it on each route individually // using the User\Middleware\AuthenticationWith::class! User\Middleware\Authentication::class, User\Middleware\User::class, [User\Web\Middleware\RememberedToken::class, 'isRememberedAfter' => 1500], // or with date interval: [User\Web\Middleware\RememberedToken::class, 'isRememberedAfter' => new \DateInterval('PT2H')], ],
After the token is considered as remembered, a new token will be created setting the parameter authenticatedVia
as remembered
. In addition, on every request it will verify the token with the token verifiers defined in the middleware such as checking the password hash.
Once the middleware is added, you may force users to re-authenticate before accessing certain resources if the token is considered as remembered by using the Authenticated Middleware:
use Tobento\App\User\Middleware\Authenticated; $app->route('GET', 'account-info', function() { return 'account'; })->middleware([ Authenticated::class, 'exceptVia' => 'remembered', 'redirectRoute' => 'login', ]);
Two-Factor Authentication Code Feature
The Two-Factor Authentication Code Feature provides a simple way for two-factor authentication using verification codes.
Config
In the config file you can configure the feature:
'features' => [ new Feature\TwoFactorAuthenticationCode(), // Or: new Feature\TwoFactorAuthenticationCode( // The view to render: view: 'user/twofactor-code', // The period of time from the present after which the verification code MUST be considered expired. codeExpiresAfter: 300, // int|\DateInterval // The seconds after a new code can be reissued. canReissueCodeAfter: 60, // The message and redirect route if a user is unauthenticated. unauthenticatedMessage: 'You have insufficient rights to access the requested resource!', unauthenticatedRedirectRoute: 'home', // or null (no redirection) // The redirect route after a successful code verification. successRoute: 'home', // The message shown after a successful code verification. successMessage: 'Welcome back :greeting.', // or null // If true, routes are being localized. localizeRoute: false, ), ],
Determine When Two Factor Auth Is Required
To enable two-factor authentication you will need to determine when two-factor authentication is required by extending the Login::class
and customizing the isTwoFactorRequiredFor
method:
use Tobento\App\User\Web\Feature\Login; use Tobento\App\User\UserInterface; class CustomLoginFeature extends Login { /** * Returns true if the user is required to perform two factor authentication, otherwise false. * * @param UserInterface $user * @return bool */ protected function isTwoFactorRequiredFor(UserInterface $user): bool { // Your conditions here: if (in_array($user->getRoleKey(), ['business'])) { return true; } return false; } }
In the config/user_web.php
replace the default login feature with your customized:
'features' => [ new CustomLoginFeature( //... ), ],
Once this is set up, on successful login, any user with the role business will be redirected to the two-factor authentication page where he can confirm the sent code.
However, the user is not obliged to confirm the code, he could just leave the two-factor authentication page and will be logged in as normal. It is up to you how to handle this. You can use any of the User Permissions Strategies or you may create a middleware to force him to confirm the code before he can access any other routes for instance.
User Permissions Strategies For Two Factor Auth
Using The Authenticated Middleware
The simplest way is just to protect routes from users which are not authenticated via two-factor authentification by using the Authenticated::class
middleware and defining the via
parameter with twofactor-code
:
use Tobento\App\User\Middleware\Authenticated; $app->route('GET', 'account-info', function() { return 'account'; })->middleware([ Authenticated::class, 'via' => 'twofactor-code', 'redirectRoute' => 'home', ]);
You may check out the Authenticated Middleware section for more detail.
Using A Custom Token Authenticator To Change The Users Role
You may change the users role when he has just logged in and is (required) to perform two-factor authentification.
First, create a custom token authenticator and use the authenticatedVia
token method to check for the loginform-twofactor
value which is set by the Login feature when two-factor authentication is required. Once the user has confirmed the code the value of the authenticatedVia
token method will be set to twofactor-code
and the users original role will be used again:
use Tobento\App\User\Authentication\Token\TokenInterface; use Tobento\App\User\Authenticator\TokenAuthenticator; use Tobento\App\User\Authenticator\TokenVerifierInterface; use Tobento\App\User\Exception\AuthenticationException; use Tobento\App\User\UserInterface; use Tobento\App\User\UserRepositoryInterface; use Tobento\Service\Acl\AclInterface; class CustomTokenAuthenticator extends TokenAuthenticator { public function __construct( protected AclInterface $acl, protected UserRepositoryInterface $userRepository, protected null|TokenVerifierInterface $tokenVerifier = null, ) {} /** * Authenticate token. * * @param TokenInterface $token * @return UserInterface * @throws AuthenticationException If authentication fails. */ public function authenticate(TokenInterface $token): UserInterface { $user = parent::authenticate($token); if ($token->authenticatedVia() === 'loginform-twofactor') { $role = $this->acl->getRole('registered'); if (is_null($role)) { throw new AuthenticationException('Registered role not set up'); } $user->setRole($role); $user->setRoleKey($role->key()); $user->setPermissions([]); // clear user specific permissions too. } return $user; } }
Next, in the config/user.php
file implement your created custom token authenticator:
use Tobento\App\User\Authenticator; 'interfaces' => [ // ... Authenticator\TokenAuthenticatorInterface::class => CustomTokenAuthenticator::class, // ... ],
Finally, just use the Verify Permission Middleware or the Verify Role Middleware to protect any routes from unauthorized users.
Logout Feature
The Logout Feature provides a simple logout functionality.
Config
In the config file you can configure the logout feature:
'features' => [ new Feature\Logout(), // Or: new Feature\Logout( // A menu name to show the logout link or null if none. menu: 'header', menuLabel: 'Log out', // A menu parent name (e.g. 'user') or null if none. menuParent: null, // The redirect route after a successful logout. redirectRoute: 'home', // The message and redirect route if a user is unauthenticated. unauthenticatedMessage: 'You have insufficient rights to access the requested resource!', unauthenticatedRedirectRoute: 'home', // or null (no redirection) // If true, routes are being localized. localizeRoute: false, ), ],
Forgot Password Feature
The Forgot Password Feature provides a simple way for users to reset their forgotten passwords.
Config
In the config file you can configure the forgot password feature:
'features' => [ new Feature\ForgotPassword(), // Or: new Feature\ForgotPassword( viewIdentity: 'user/forgot-password/identity', viewReset: 'user/forgot-password/reset', // Specify the identity attributes to be checked on identity. identifyBy: ['email', 'username', 'smartphone'], // You may set a user verifier(s), see: https://github.com/tobento-ch/app-user#user-verifier /*userVerifier: function() { return new \Tobento\App\User\Authenticator\UserRoleVerifier('editor', 'author'); },*/ // The period of time from the present after which the verification token MUST be considered expired. tokenExpiresAfter: 300, // int|\DateInterval // The seconds after a new token can be reissued. canReissueTokenAfter: 60, // The message shown if an identity attempt fails. identityFailedMessage: 'Invalid name or user.', // The message and redirect route if a user is authenticated. authenticatedMessage: 'You are logged in!', authenticatedRedirectRoute: 'home', // or null (no redirection) // The redirect route after a successful password reset. successRedirectRoute: 'home', // The message shown after a successful password reset. successMessage: 'Your password has been reset!', // or null // If true, routes are being localized. localizeRoute: false, ), ],
Customize Reset Password Notification
You may customize the reset password notification in two ways:
By adding a custom notification
By customizing the feature
Extend the Tobento\App\User\Web\Feature\ForgotPassword::class
and customize the sendLinkNotification
method. Within this method, you may send the notification using any notification class of your own creation:
use Tobento\App\User\Web\Feature\ForgotPassword; use Tobento\App\User\Web\TokenInterface; use Tobento\App\User\Web\Notification; use Tobento\App\User\UserInterface; use Tobento\Service\Notifier\NotifierInterface; use Tobento\Service\Notifier\UserRecipient; use Tobento\Service\Routing\RouterInterface; class CustomForgotPasswordFeature extends ForgotPassword { protected function sendLinkNotification( TokenInterface $token, UserInterface $user, NotifierInterface $notifier, RouterInterface $router, ): void { $notification = new Notification\ResetPassword( token: $token, url: (string)$router->url('forgot-password.reset', ['token' => $token->id()]), ); // The receiver of the notification: $recipient = new UserRecipient(user: $user); // Send the notification to the recipient: $notifier->send($notification, $recipient); } }
Finally, in the config replace the default Forgot Password feature with your customized:
'features' => [ new CustomForgotPasswordFeature(), ],
Register Feature
The Register Feature provides a simple way for users to register.
Config
In the config file you can configure the register feature:
'features' => [ new Feature\Register(), // Or: new Feature\Register( // The view to render: view: 'user/register', // A menu name to show the register link or null if none. menu: 'header', menuLabel: 'Register', // A menu parent name (e.g. 'user') or null if none. menuParent: null, // The message and redirect route if a user is authenticated. authenticatedMessage: 'You are logged in!', authenticatedRedirectRoute: 'home', // The default role key for the registered user. roleKey: 'registered', // The redirect route after a successful registration. successRedirectRoute: 'login', // You may redirect to the verification account page // see: https://github.com/tobento-ch/app-user-web#account-verification // If true, user has the option to subscribe to the newsletter. newsletter: false, // If a terms route is specified, users need to agree terms and conditions. termsRoute: null, /*termsRoute: static function (RouterInterface $router): string { return (string)$router->url('blog.show', ['key' => 'terms']); },*/ // If true, routes are being localized. localizeRoute: false, ), ],
Customize Registration Fields
You may customize the registration fields by the following steps:
1.A Customize the view
In the views/user/
directory create a new file custom-register.php
where you write your custom view code.
1.B Or customize the view using a theme (recommended way)
In your theme create a new file my-view-theme/user/register.php
where you write your custom view code.
Check out the App View - Themes section to learn more about it.
2. Customize the validation rules
Customize the registration rules corresponding to the customized view (step 1) by extending the Register::class
and customizing the validationRules
method:
use Tobento\App\User\Web\Feature\Register; class CustomRegisterFeature extends Register { protected function validationRules(): array { return [ 'user_type' => 'string', 'address.fistname' => 'required|string', 'address.lastname' => 'required|string', // ... ]; } }
Finally, in the config replace the default register feature with your customized:
'features' => [ new CustomRegisterFeature( // specify your custom view if (step 1.A): view: 'user/custom-register', // No need to change the default view for (step 1.B) view: 'user/register', //... ), ],
Customize The Role For Registered Users
You may customize the role for registered users by extending the Register::class
and customizing the determineRoleKey
method:
use Tobento\App\User\Web\Feature\Register; use Tobento\Service\Acl\AclInterface; use Tobento\Service\Validation\ValidationInterface; class CustomRegisterFeature extends Register { protected function determineRoleKey(AclInterface $acl, ValidationInterface $validation): string { return match ($validation->valid()->get('user_type', '')) { 'business' => $acl->hasRole('business') ? 'business' : $this->roleKey, default => $this->roleKey, }; } }
In the config replace the default register feature with your customized:
'features' => [ new CustomRegisterFeature( //... ), ],
Make sure you have added the roles, otherwise the guest
role key would be used as the fallback.
Auto Login After Registration
By default, after successful registration users get not authenticated (logged in).
If you want them to get auto logged in just add the AutoLoginAfterRegistration::class
listener in the config/event.php
file:
use Tobento\App\User\Web\Listener\AutoLoginAfterRegistration; 'listeners' => [ \Tobento\App\User\Web\Event\Registered::class => [ AutoLoginAfterRegistration::class, // Or you may set the expires after: [AutoLoginAfterRegistration::class, ['expiresAfter' => 1500]], // in seconds [AutoLoginAfterRegistration::class, ['expiresAfter' => new \DateInterval('PT1H')]], ], ],
In the config/user_web.php
file you may redirect users to the profile edit page or any other page you desire:
'features' => [ new Feature\Register( // redirect users to the profile page // after successful registration: successRedirectRoute: 'profile.edit', ), ],
Account Verification
After users have successfully registered, you may require them to verify at least one channel such as their email address before using the application or individual routes. You can achieve this by the following steps:
1. Auto login users after successful registration
In the config/event.php
file add the AutoLoginAfterRegistration::class
listener:
'listeners' => [ \Tobento\App\User\Web\Event\Registered::class => [ \Tobento\App\User\Web\Listener\AutoLoginAfterRegistration::class, ], ],
Because only authenticated users are allowed to verify its account!
2. Redirect users to the verification page after successful registration
In the config/user_web.php
file:
'features' => [ new Feature\Register( // redirect users to the verification account page // after successful registration: successRedirectRoute: 'verification.account', ), // make sure the verification feature is set: Feature\Verification::class, ],
3. Protect routes from unverified users
Use the Verified Middleware to protect any routes from unverified users.
4. Protect the profile feature from unverified users
Extend the Profile::class
and customize the configureMiddlewares
method:
use Tobento\App\User\Web\Feature\Profile; use Tobento\App\AppInterface; use Tobento\App\User\Middleware\Authenticated; use Tobento\App\User\Middleware\Verified; class CustomProfileFeature extends Profile { protected function configureMiddlewares(AppInterface $app): array { return [ // The Authenticated::class middleware protects routes from unauthenticated users: [ Authenticated::class, // you may specify a custom message to show to the user: 'message' => $this->unauthenticatedMessage, // you may specify a message level: //'messageLevel' => 'notice', // you may specify a route name for redirection: 'redirectRoute' => $this->unauthenticatedRedirectRoute, ], // The Verified::class middleware protects routes from unverified users: [ Verified::class, // you may specify a custom message to show to the user: 'message' => 'You have insufficient rights to access the requested resource!', // you may specify a message level: 'messageLevel' => 'notice', // you may specify a route name for redirection: 'redirectRoute' => 'verification.account', ], ]; } }
In the config replace the default profile feature with your customized:
'features' => [ new CustomProfileFeature( //... ), ],
5. Protect the profile settings feature from unverified users
Same as step 4. just with the Tobento\App\User\Web\Feature\ProfileSettings::class
.
Account Verification For Specific User Roles Only
Instead of Account Verification for all users, you may do it only for specific user roles. You can achieve this by the following steps:
1. Customize The Register Feature
Extend the Register::class
and customize the configureSuccessRedirectRoute
method:
use Tobento\App\User\Web\Feature\Register; use Tobento\App\User\UserInterface; class CustomRegisterFeature extends Register { protected function configureSuccessRedirectRoute(UserInterface $user): string { if (in_array($user->getRoleKey(), ['business'])) { return 'verification.account'; } return 'login'; } }
2. Customize The Role For Registered Users (optional)
Check out the Customize The Role For Registered Users section.
3. Auto Login Users After Registration
You will need to auto login the users which need to verify its account, as only authenticated users are allowed to verify its account:
In the config/event.php
file add the CustomAutoLoginAfterRegistration::class
listener:
use Tobento\App\User\Web\Listener\AutoLoginAfterRegistration; 'listeners' => [ \Tobento\App\User\Web\Event\Registered::class => [ [AutoLoginAfterRegistration::class, ['userRoles' => ['business']]], ], ],
4. Protect the profile and profile settings feature from unverified users as well as any other routes
See Account Verification step 3, 4 and 5.
Terms and Conditions Agreement
You may users to agree your terms and conditions before they can register:
In the config/user_web.php
file:
'features' => [ new Feature\Register( // If a terms route is specified, users need to agree terms and conditions. termsRoute: 'your.terms.route.name', // Or you may use router directly: termsRoute: static function (RouterInterface $router): string { return (string)$router->url('blog.show', ['key' => 'terms']); }, ), ],
Make sure you have registered your terms route somewhere in your application.
Spam Protection For Registering
The registration form is protected against spam by default using the App Spam bundle. It uses the default
spam detector as the defined named register
detector does not exist. In order to use a custom detector, you will just need to define it on the app/config/spam.php
file:
use Tobento\App\Spam\Factory; 'detectors' => [ 'register' => new Factory\Composite( new Factory\Honeypot(inputName: 'hp'), new Factory\MinTimePassed(inputName: 'mtp', milliseconds: 1000), ), ]
Notifications Feature
The Notifications Feature provides a simple way for users to view their notifications.
Config
In the config file you can configure the notifications feature:
'features' => [ new Feature\Notifications(), // Or: new Feature\Notifications( // The view to render: view: 'user/notifications', // The notifier storage channel used to retrieve notifications. notifierStorageChannel: 'storage', // A menu name to show the notifications link or null if none. menu: 'main', menuLabel: 'Notifications', // A menu parent name (e.g. 'user') or null if none. menuParent: null, // The message and redirect route if a user is unauthenticated. unauthenticatedMessage: 'You have insufficient rights to access the requested resource!', unauthenticatedRedirectRoute: 'login', // or null (no redirection) // If true, routes are being localized. localizeRoute: false, ), ],
Creating And Sending Notifications
To send notifications to be display on the notifications page you will need to send to the storage channel configured:
use Tobento\Service\Notifier\NotifierInterface; use Tobento\Service\Notifier\Notification; use Tobento\Service\Notifier\Recipient; class SomeService { public function send(NotifierInterface $notifier): void { // Create a Notification that has to be sent: // using the "email" and "sms" channel $notification = new Notification( subject: 'New Invoice', content: 'You got a new invoice for 15 EUR.', channels: ['mail', 'sms', 'storage'], ); // with specific storage message. Will be displayed on the notifications page: $notification->addMessage('storage', new Message\Storage([ 'message' => 'You received a new order.', 'action_text' => 'View Order', 'action_route' => 'orders.view', 'action_route_parameters' => ['id' => 55], ])); // The receiver of the notification: $recipient = new Recipient( email: 'mail@example.com', phone: '15556666666', id: 'unique-user-id', ); // Send the notification to the recipient: $notifier->send($notification, $recipient); } }
Format Notifications
Check out the Storage Notification Formatters section to learn more about formatting the displayed notifications.
Customize Unread Notifications Count
You may customize the unread notifications count logic for the menu badge by extending the Notifications::class
and customizing the getUnreadNotificationsCount
method.
Example caching the count
Install the App Cache bundle to support caching.
use Tobento\App\AppInterface; use Tobento\App\User\Web\Feature\Notifications; use Tobento\Service\Notifier\ChannelsInterface; use Tobento\Service\Notifier\Storage; use Psr\SimpleCache\CacheInterface; class CustomNotificationsFeature extends Notifications { /** * Returns the user's unread notifications count for the menu badge. * * @param ChannelsInterface $channels * @param UserInterface $user * @param AppInterface $app * @return int */ protected function getUnreadNotificationsCount( ChannelsInterface $channels, UserInterface $user, AppInterface $app, ): int { $channel = $channels->get(name: $this->notifierStorageChannel); if (!$channel instanceof Storage\Channel) { return 0; } $key = sprintf('unread_notifications_count:%s', (string)$user->id()); $cache = $app->get(CacheInterface::class); if ($cache->has($key)) { return $cache->get($key); } $count = $channel->repository()->count(where: [ 'recipient_id' => $user->id(), 'read_at' => ['null'], ]); $cache->set($key, $count, 60); return $count; } }
In the config replace the default notifications feature with your customized:
'features' => [ new CustomNotificationsFeature( //... ), ],
Clear Read Notifications
If you have installed the App Console you may easily delete read notifications running the following command:
php ap notifications:clear --read-only --channel=storage --older-than-days=10
You may check out the App Notifier - Clear Notifications Command section for more information about the notifications:clear
command.
If you would like to automate this process, consider installing the App Schedule bundle and using a command task:
use Tobento\Service\Schedule\Task; use Butschster\CronExpression\Generator; $schedule->task( (new Task\CommandTask( command: 'notifications:clear --read-only', )) // schedule task: ->cron(Generator::create()->weekly()) );
Profile Feature
The Profile Feature provides a simple way for users to update their profile data, delete their account and to verify their channels.
Config
In the config file you can configure the profile feature:
'features' => [ new Feature\Profile(), // Or: new Feature\Profile( // The view to render: view: 'user/profile/edit', // A menu name to show the profile link or null if none. menu: 'main', menuLabel: 'Profile', // A menu parent name (e.g. 'user') or null if none. menuParent: null, // The message and redirect route if a user is unauthenticated. unauthenticatedMessage: 'You have insufficient rights to access the requested resource!', unauthenticatedRedirectRoute: 'login', // or null (no redirection) // If true, it displays the channel verification section to verify channels. // Make sure the Verification Feature is enabled. channelVerifications: true, // The redirect route after a successfully account deletion. successDeleteRedirectRoute: 'home', // If true, routes are being localized. localizeRoute: false, ), ],
Customize Information Fields
You may customize the information fields by the following steps:
1.A Customize the view
In the views/user/
directory create a new file profile/custom-edit.php
where you write your custom view code.
1.B Or customize the view using a theme (recommended way)
In your theme create a new file my-view-theme/user/profile/edit.php
where you write your custom view code.
Check out the App View - Themes section to learn more about it.
2. Customize the validation rules
Customize the settings rules corresponding to the customized view (step 1) by extending the Profile::class
and customizing the validationUpdateRules
method:
use Tobento\App\User\Web\Feature\Profile; use Tobento\App\User\UserInterface; class CustomProfileFeature extends Profile { /** * Returns the validation rules for updating the user's profile information. * * @param UserInterface $user * @return array */ protected function validationUpdateRules(UserInterface $user): array { // your rules: $rules = [ 'user_type' => 'string', 'address.fistname' => 'required|string', 'address.lastname' => 'required|string', ]; // your may merge the default rules. return array_merge($rules, parent::validationUpdateRules($user)); } }
Finally, in the config replace the default profile settings feature with your customized:
'features' => [ new CustomProfileFeature( // specify your custom view if (step 1.A): view: 'user/profile/custom-edit', // No need to change the default view for (step 1.B) view: 'user/profile/edit', //... ), ],
Customize Available Verification Channels To Display
You may customize the available verification channels by extending the Profile::class
and customizing the configureAvailableChannels
method:
use Tobento\App\User\Web\Feature\Profile; use Tobento\App\User\UserInterface; use Tobento\App\Notifier\AvailableChannelsInterface; class CustomProfileFeature extends Profile { /** * Configure the available verification channels to display. * * @param AvailableChannelsInterface $channels * @param UserInterface $user * @return AvailableChannelsInterface */ protected function configureAvailableChannels( AvailableChannelsInterface $channels, UserInterface $user, ): AvailableChannelsInterface { if (! $this->channelVerifications) { // do not display any at all: return $channels->only([]); } return $channels->only(['mail', 'sms']); } }
In the config replace the default profile feature with your customized:
'features' => [ new CustomProfileFeature( //... ), ],
If you allow other channels than mail
and sms
, you will need to customize the verification feature.
Profile Settings Feature
The Profile Settings Feature provides a simple way for users to update their profile settings such as his preferred locale and notification channels.
Config
In the config file you can configure the profile feature:
'features' => [ new Feature\ProfileSettings(), // Or: new Feature\ProfileSettings( // The view to render: view: 'user/profile/settings', // A menu name to show the profile settings link or null if none. menu: 'main', menuLabel: 'Profile Settings', // A menu parent name (e.g. 'user') or null if none. menuParent: null, // The message and redirect route if a user is unauthenticated. unauthenticatedMessage: 'You have insufficient rights to access the requested resource!', unauthenticatedRedirectRoute: 'login', // or null (no redirection) // If true, routes are being localized. localizeRoute: false, ), ],
Customize Settings Fields
You may customize the settings fields by the following steps:
1.A Customize the view
In the views/user/
directory create a new file profile/custom-settings.php
where you write your custom view code.
1.B Or customize the view using a theme (recommended way)
In your theme create a new file my-view-theme/user/profile/settings.php
where you write your custom view code.
Check out the App View - Themes section to learn more about it.
2. Customize the validation rules
Customize the settings rules corresponding to the customized view (step 1) by extending the ProfileSettings::class
and customizing the validationRules
method:
use Tobento\App\User\Web\Feature\ProfileSettings; use Tobento\App\User\UserInterface; class CustomProfileSettingsFeature extends ProfileSettings { /** * Returns the validation rules for updating the user's profile settings. * * @param UserInterface $user * @return array */ protected function validationRules(UserInterface $user): array { // your rules: $rules = [ 'settings.something' => 'required|string', ]; // your may merge the default rules. return array_merge($rules, parent::validationRules($user)); } }
Finally, in the config replace the default profile settings feature with your customized:
'features' => [ new CustomProfileSettingsFeature( // specify your custom view if (step 1.A): view: 'user/profile/custom-settings', // No need to change the default view for (step 1.B) view: 'user/profile/settings', //... ), ],
Customize Available Notification Channels
You may customize the available notification channels by extending the ProfileSettings::class
and customizing the configureAvailableChannels
method:
use Tobento\App\User\Web\Feature\ProfileSettings; use Tobento\App\User\UserInterface; use Tobento\App\Notifier\AvailableChannelsInterface; class CustomProfileSettingsFeature extends ProfileSettings { /** * Configure the available channels. * * @param AvailableChannelsInterface $channels * @param UserInterface $user * @return AvailableChannelsInterface */ protected function configureAvailableChannels( AvailableChannelsInterface $channels, UserInterface $user, ): AvailableChannelsInterface { return $channels ->only(['mail', 'sms', 'storage']) ->withTitle('storage', 'Account') ->sortByTitle(); // Or you may return no channels at all: return $channels->only([]); } }
In the config replace the default profile settings feature with your customized:
'features' => [ new CustomProfileSettingsFeature( //... ), ],
Verification Feature
The Verification Feature provides a simple way for users to verify their email and smartphone.
Config
In the config file you can configure the verification feature:
'features' => [ new Feature\Verification(), // Or: new Feature\Verification( // The view to render: viewAccount: 'user/verification/account', viewChannel: 'user/verification/channel', // The period of time from the present after which the verification code MUST be considered expired. codeExpiresAfter: 300, // int|\DateInterval // The seconds after a new code can be reissued. canReissueCodeAfter: 60, // The message and redirect route if a user is unauthenticated. unauthenticatedMessage: 'You have insufficient rights to access the requested resource!', unauthenticatedRedirectRoute: 'login', // or null (no redirection) // The redirect route after a verified channel. verifiedRedirectRoute: 'home', // If true, routes are being localized. localizeRoute: false, ), ],
Protect Routes From Unverified User
Use the Verified Middleware to protect any routes from unverified users.
Customize Available Channels To Verify
You may customize the available channels which can be verified by extending the Verification::class
and customizing the configureAvailableChannels
, getNotifierChannelFor
and canVerifyChannel
methods.
Do not forget to configure the notifier channels in the app/config/notifier.php
config file!
use Tobento\App\User\Web\Feature\Verification; use Tobento\App\User\UserInterface; use Tobento\App\Notifier\AvailableChannelsInterface; class CustomVerificationFeature extends Verification { /** * Configure the available channels. * * @param AvailableChannelsInterface $channels * @param UserInterface $user * @return AvailableChannelsInterface */ protected function configureAvailableChannels( AvailableChannelsInterface $channels, UserInterface $user, ): AvailableChannelsInterface { //return $channels->only(['mail', 'sms']); // default return $channels->only(['mail', 'sms', 'chat/slack']); } /** * Returns the notifier channel for the given verification channel. * * @param string $channel * @return null|string */ protected function getNotifierChannelFor(string $channel): null|string { return match ($channel) { 'email' => 'mail', 'smartphone' => 'sms', 'slack' => 'chat/slack', default => null, }; } /** * Determine if the channel can be verified. * * @param string $channel * @param AvailableChannelsInterface $channels * @param null|UserInterface $user * @return bool */ protected function canVerifyChannel(string $channel, AvailableChannelsInterface $channels, null|UserInterface $user): bool { if (is_null($user) || !$user->isAuthenticated()) { return false; } if (! $channels->has((string)$this->getNotifierChannelFor($channel))) { return false; } return match ($channel) { 'email' => !empty($user->email()) && ! $user->isVerified([$channel]), 'smartphone' => !empty($user->smartphone()) && ! $user->isVerified([$channel]), 'slack' => !empty($user->setting('slack')) && ! $user->isVerified([$channel]), default => false, }; } }
In the config replace the default profile feature with your customized:
'features' => [ new CustomVerificationFeature( //... ), ],
Deleting Expired Tokens
Verificator Tokens
The following features use the token or pin code verificator creating tokens which will still be present within your token repository even if expired.
If you have installed the App Console you may easily delete these records running the following command:
php ap user-web:clear-tokens
If you would like to automate this process, consider installing the App Schedule bundle and using a command task:
use Tobento\Service\Schedule\Task; use Butschster\CronExpression\Generator; $schedule->task( (new Task\CommandTask( command: 'user-web:clear-tokens', )) // schedule task: ->cron(Generator::create()->weekly()) );
Auth Tokens
php ap auth:purge-tokens
Or automate this process using a command schedule task:
use Tobento\Service\Schedule\Task; use Butschster\CronExpression\Generator; $schedule->task( (new Task\CommandTask( command: 'auth:purge-tokens', )) // schedule task: ->cron(Generator::create()->weekly()) );
Visit User - Console for more detail.
View
Acl View Macro
In your view files, you may use the acl macro to check for permission for instance:
use Tobento\Service\Acl\AclInterface; var_dump($view->acl() instanceof AclInterface); // bool(true) if ($view->acl()->can('comments.write')) { <div>Only users with the permission "comments.write" can see this!</div> }
Events
Available Events
use Tobento\App\User\Web\Event;
Learn More
Login With Smartphone
By default, login with smarthone is enabled. Make sure you have configured the sms channel in the app/config/notifier.php
file for sending sms to verify its account for instance.
List Available Routes
Use the Route List Command to get an overview of the available routes.
Newsletter Subscription
You may use the provided Events to subscribe/unsubscribe registered users to an newsletter provider.
Example of Listener:
use Tobento\App\User\Web\Event; class UserNewsletterSubscriber { public function subscribe(Event\Registered $event): void { if ($event->user()->newsletter()) { // subscribe... } } public function resubscribe(Event\UpdatedProfile $event): void { if ($event->user()->email() !== $event->oldUser()->email()) { // unsubscribe user with the old email address... // subscribe user with the new email address... } } public function subscribeOrUnsubscribe(Event\UpdatedProfileSettings $event): void { if ($event->user()->newsletter()) { // subscribe... } else { // unsubscribe... } } public function unsubscribe(Event\DeletedAccount $event): void { // just unsubscribe... } }
In the config/event.php
file add the the listener:
'listeners' => [ // Specify listeners without event: 'auto' => [ UserNewsletterSubscriber::class, ], ],
You may check out the App Event - Add Listeners section to learn more about it.
Customize Verification Code Notification
You may customize the verification code notification in two ways:
By adding a custom notification
By customizing the pin code verificator
Extend the Tobento\App\User\Web\PinCodeVerificator::class
and customize the createNotification
method. Within this method, you may send the notification using any notification class of your own creation:
use Tobento\App\User\Web\Notification; use Tobento\App\User\Web\PinCodeVerificator; use Tobento\App\User\Web\TokenInterface; use Tobento\App\User\UserInterface; use Tobento\Service\Notifier\NotifierInterface; use Tobento\Service\Routing\RouterInterface; class CustomPinCodeVerificator extends PinCodeVerificator { protected function createNotification( TokenInterface $token, UserInterface $user, array $channels, ): NotificationInterface { return (new Notification\VerificationCode(token: $token))->channels($channels); } }
Finally, in the config file replace the default implementation with your custom:
use Tobento\App\User\Web; 'interfaces' => [ Web\PinCodeVerificatorInterface::class => CustomPinCodeVerificator::class, ],
Localization
If you enable feature routes being localized, you can define the languages you support in the app/config/language.php
.
In the config file:
'features' => [ new Feature\Home( localizeRoute: true, ), new Feature\Register( localizeRoute: true, ), ],
Check out the App Language to learn more about the languages.
Translations
By default, en
and de
translation are available. If you want to support more locales, check out the App Translation to learn more about it.