User Bundle
The Roadiz User Bundle provides a comprehensive public user management system for your Roadiz application. It handles user registration, authentication, password reset, and account management for front-end users.
Features
- User Registration: Sign-up functionality with email validation
- Authentication: Traditional password-based and passwordless login options
- Password Management: Password reset and change functionality
- Email Validation: Token-based email verification system
- Login Links: Passwordless authentication via email magic links
- Rate Limiting: Built-in protection against brute-force attacks
- User Roles: Flexible role-based access control for public users
Installation
Install the bundle using Composer:
composer require roadiz/user-bundleIf you're not using Symfony Flex, you'll need to manually enable the bundle in config/bundles.php:
// config/bundles.php
return [
// ...
\RZ\Roadiz\UserBundle\RoadizUserBundle::class => ['all' => true],
];Configuration
Step 1: Copy API Platform Resources
Copy the API Platform resource configurations to your project:
cp vendor/roadiz/user-bundle/config/api_resources/user.yaml config/api_resources/
cp vendor/roadiz/user-bundle/config/api_resources/me.yaml config/api_resources/Step 2: Configure Rate Limiters
Add rate limiter configuration to config/packages/framework.yaml:
# config/packages/framework.yaml
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'Step 3: Configure Cache Pools
Add cache pools in config/packages/cache.yaml:
# config/packages/cache.yaml
framework:
cache:
pools:
cache.user_signup_limiter: ~
cache.password_request_limiter: ~
cache.password_reset_limiter: ~Step 4: Configure Security Access Control
Update config/packages/security.yaml to define public and protected routes:
# config/packages/security.yaml
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 }
# Protected routes
- { path: "^/api", roles: ROLE_BACKEND_USER, methods: [ POST, PUT, PATCH, DELETE ] }
- { path: "^/api/users", methods: [ GET, PUT, PATCH, POST ], roles: ROLE_USER }Step 5: Configure Environment Variables
Add the following to your .env or .env.local file:
# User bundle configuration
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=3600Replace the URLs with your actual front-end application URLs where users will complete password reset and email validation.
Step 6: Configure CORS
Update CORS configuration to allow required headers:
# config/packages/nelmio_cors.yaml
nelmio_cors:
defaults:
allow_headers: ['Content-Type', 'Authorization', 'Www-Authenticate', 'x-g-recaptcha-response']
expose_headers: ['Link', 'Www-Authenticate']User Roles
The bundle provides the following user roles:
ROLE_PUBLIC_USER: Default role for all public usersROLE_PASSWORDLESS_USER: Role for users authenticated via login linkROLE_EMAIL_VALIDATED: Role assigned after email validation
Passwordless Authentication
The bundle supports passwordless authentication using login links sent via email.
Configuration
- Add the login link check route to
config/routes.yaml:
# config/routes.yaml
public_login_link_check:
path: /api/users/login_link_check
methods: [POST]- Configure the security firewall in
config/packages/security.yaml:
# config/packages/security.yaml
security:
providers:
# Combined provider for both backend and public users
all_users:
chain:
providers: ['roadiz_user_provider', 'roadiz_backend_user_provider']
firewalls:
api:
pattern: ^/api
stateless: true
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: 600
max_uses: 3Creating a Login Link Controller
Create a controller to handle login link requests:
<?php
// src/Controller/SecurityController.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 {
$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);
}
$loginLinkDetails = $loginLinkHandler->createLoginLink($user, $request);
$this->loginLinkSender->sendLoginLink($user, $loginLinkDetails);
return new JsonResponse(null, Response::HTTP_NO_CONTENT);
}
}Register the controller in config/services.yaml:
# config/services.yaml
services:
App\Controller\SecurityController:
tags: [ 'controller.service_arguments' ]Customizing Login Link URLs
To use a different base URL for login links (e.g., your front-end application):
# 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'Maintenance Commands
The bundle provides useful maintenance commands:
Purge Expired Validation Tokens
Remove expired user validation tokens:
bin/console users:purge-validation-tokensRemove Inactive Users
Delete inactive users who haven't logged in for a specified period:
# Delete public users inactive for 60 days without ROLE_EMAIL_VALIDATED
bin/console users:inactive -d 60 -r ROLE_PUBLIC_USER -m ROLE_EMAIL_VALIDATED -vOptions:
-d, --days: Number of days of inactivity (default: 60)-r, --role: Filter by role (can be used multiple times)-m, --missing-role: Filter by missing role-v: Dry run (display users without deleting)
TIP
Run maintenance commands regularly using cron jobs or Symfony Scheduler to keep your user database clean.
API Endpoints
The bundle provides the following API endpoints:
POST /api/users/signup: User registrationPOST /api/users/login_link: Request login linkPOST /api/users/login_link_check: Verify login linkPOST /api/users/password_request: Request password resetPUT /api/users/password_reset: Reset password with tokenGET /api/users/me: Get current user informationGET /api/users/{id}: Get specific user (requires authentication)PUT /api/users/{id}: Update user informationPATCH /api/users/{id}: Partially update user
Security Considerations
WARNING
- Always use HTTPS in production
- Configure appropriate rate limits based on your traffic
- Set reasonable token expiration times
- Regularly purge expired tokens and inactive users
- Validate email addresses to prevent spam registrations
- Consider implementing CAPTCHA for registration and login endpoints
Troubleshooting
Users Not Receiving Emails
Check:
- Mailer configuration in
config/packages/mailer.yaml - Environment variables for SMTP settings
- Email logs and queue
Rate Limiting Issues
If legitimate users are being rate-limited:
- Adjust rate limiter settings in
framework.yaml - Check cache pool configuration
- Consider using different rate limits for different environments
Login Links Not Working
Verify:
- Login link handler is properly configured
- Routes are registered correctly
- Token lifetime is appropriate
- Front-end URL configuration matches your application
More Information
For detailed API documentation, refer to the auto-generated OpenAPI documentation available in your Roadiz application at /api/docs.
