Public user management bundle for Roadiz v2
Make sure Composer is installed globally, as explained in the installation chapter of the Composer documentation.
Open a command console, enter your project directory and execute:
$ composer require roadiz/user-bundleOpen a command console, enter your project directory and execute the following command to download the latest stable version of this bundle:
$ composer require roadiz/user-bundleThen, enable the bundle by adding it to the list of registered bundles
in the config/bundles.php file of your project:
// config/bundles.php return [ // ... \RZ\Roadiz\UserBundle\RoadizUserBundle::class => ['all' => true], ];
- Copy API Platform resource configuration files to your Roadiz project
api_resourcefolder:./config/api_resources/user.yaml./config/api_resources/me.yaml
- Edit your
./config/packages/framework.yamlfile with:
framework: rate_limiter: user_signup: policy: 'token_bucket' limit: 5 rate: { interval: '1 minutes', amount: 3 } cache_pool: 'cache.user_signup_limiter' password_request: policy: 'token_bucket' limit: 3 rate: { interval: '1 minutes', amount: 3 } cache_pool: 'cache.password_request_limiter' password_reset: policy: 'token_bucket' limit: 3 rate: { interval: '1 minutes', amount: 3 } cache_pool: 'cache.password_reset_limiter'
- Edit your
./config/packages/cache.yamlfile with:
framework: cache: pools: cache.user_signup_limiter: ~ cache.password_request_limiter: ~ cache.password_reset_limiter: ~
- Edit your
./config/packages/security.yamlfile with:
security: access_control: # Prepend user routes configuration before API Platform ones # Public routes must be defined before protected ones - { path: "^/api/users/login_link_check", methods: [ POST ], roles: PUBLIC_ACCESS } - { path: "^/api/users/login_link", methods: [ POST ], roles: PUBLIC_ACCESS } - { path: "^/api/users/signup", methods: [ POST ], roles: PUBLIC_ACCESS } - { path: "^/api/users/password_request", methods: [ POST ], roles: PUBLIC_ACCESS } - { path: "^/api/users/password_reset", methods: [ PUT ], roles: PUBLIC_ACCESS } # ... - { path: "^/api", roles: ROLE_BACKEND_USER, methods: [ POST, PUT, PATCH, DELETE ] } - { path: "^/api/users", methods: [ GET, PUT, PATCH, POST ], roles: ROLE_USER }
- Edit your
./.envfile with:
USER_PASSWORD_RESET_URL=https://your-public-url.test/reset USER_VALIDATION_URL=https://your-public-url.test/validate USER_PASSWORD_RESET_EXPIRES_IN=600 USER_VALIDATION_EXPIRES_IN=3600
- Update your CORS configuration with additional headers
Www-Authenticateandx-g-recaptcha-response:
# config/packages/nelmio_cors.yaml nelmio_cors: defaults: # ... allow_headers: ['Content-Type', 'Authorization', 'Www-Authenticate', 'x-g-recaptcha-response'] expose_headers: ['Link', 'Www-Authenticate']
You can switch your public users to PasswordlessUser and set up a login link authentication process along with
user creation process.
First you need to configure a public login link route:
# config/routes.yaml public_login_link_check: path: /api/users/login_link_check methods: [POST]
Then you need to configure your security.yaml file to use login_link authentication process in your API firewall.
You must use all_users provider to be able to use Roadiz User provider during the login_link authentication process.
# config/packages/security.yaml # https://symfony.com/bundles/LexikJWTAuthenticationBundle/current/8-jwt-user-provider.html#symfony-5-3-and-higher api: pattern: ^/api stateless: true # We need to use all_users provider to be able to use Roadiz User provider # during the login_link authentication process provider: all_users jwt: ~ login_link: check_route: public_login_link_check check_post_only: true success_handler: lexik_jwt_authentication.handler.authentication_success failure_handler: lexik_jwt_authentication.handler.authentication_failure signature_properties: [ 'email' ] # lifetime in seconds lifetime: 600 max_uses: 3
Then you'll need a public route to request a login-link. In your project create a new App\Controller\SecurityController
and add a new route /api/users/login_link:
<?php declare(strict_types=1); namespace App\Controller; use RZ\Roadiz\CoreBundle\Repository\UserRepository; use RZ\Roadiz\CoreBundle\Security\LoginLink\LoginLinkSenderInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\LoginLink\LoginLinkHandlerInterface; final readonly class SecurityController { public function __construct( private LoginLinkSenderInterface $loginLinkSender, ) { } #[Route('/api/users/login_link', name: 'public_user_login_link_request', methods: ['POST'])] public function requestLoginLink( LoginLinkHandlerInterface $loginLinkHandler, UserRepository $userRepository, Request $request ): Response { // load the user in some way (e.g. using the form input) $email = $request->getPayload()->get('email'); $user = $userRepository->findOneBy(['email' => $email]); if (null === $user) { // Do not leak if a user exists or not return new JsonResponse(null, Response::HTTP_NO_CONTENT); } // create a login link for $user this returns an instance // of LoginLinkDetails $loginLinkDetails = $loginLinkHandler->createLoginLink($user, $request); $this->loginLinkSender->sendLoginLink($user, $loginLinkDetails); return new JsonResponse(null, Response::HTTP_NO_CONTENT); } }
Register your controller:
# config/services.yaml services: App\Controller\SecurityController: tags: [ 'controller.service_arguments' ]
Roadiz User Bundle provides a custom Symfony\Component\Security\Http\LoginLink\LoginLinkHandlerInterface service to generate a login-link with a different base-uri,
all you need is to register RZ\Roadiz\UserBundle\Security\FrontendLoginLinkHandler service in your project with its mandatory arguments:
# config/services.yaml services: RZ\Roadiz\UserBundle\Security\FrontendLoginLinkHandler: decorates: Symfony\Component\Security\Http\LoginLink\LoginLinkHandlerInterface arguments: $decorated: '@RZ\Roadiz\UserBundle\Security\FrontendLoginLinkHandler.inner' $frontendLoginCheckRoute: '%frontend_login_check_route%' $frontendLoginLinkRequestRoutes: - 'frontend_user_login_link_request' - 'public_user_login_link_request' - 'api_user_signup' $signatureHasher: '@security.authenticator.login_link_signature_hasher.api_login_link'
Now for each $frontendLoginLinkRequestRoutes login_link will be generated using $frontendLoginCheckRoute base URL
ROLE_PUBLIC_USER: Default role for public usersROLE_PASSWORDLESS_USER: Role for public users authenticated with a login linkROLE_EMAIL_VALIDATED: Role for public users added since they validated their email address, through a validation token or a login link
bin/console users:purge-validation-tokens: Delete all expired user validation tokensbin/console users:inactive -d 60 -r ROLE_PUBLIC_USER -m ROLE_EMAIL_VALIDATED -v: Delete all inactive public users that did not logged-in for 60 days. Notice that this command example only displays users that do not haveROLE_EMAIL_VALIDATEDrole.
Report issues and send Pull Requests in the main Roadiz repository