My take on user management with Symfony 4.4.
Continuation of symfony3-user-manager.
- Webpack Encore (SCSS and JS compilation)
- jQuery (AJAX and DOM manipulation)
- Bootstrap 4 (forms and alerts)
- zxcvbn (password strength estimation)
Feel free to tailor each feature to your needs.
- Content is compatible with translation files
- English and French translations included
- Add your own, modify existing one
- Service
- Add your own methods to send other emails
- Keep in mind that emails are translated in the locale of the current user, which could differ from the locale of the recipient, especially for password reset email and emails related to user enumeration prevention. If it's a problem for you, consider storing the locale of each user in the database and translating emails with this locale instead of the default one
- Registration form with expected validations on each field (see User entity for details)
- Form submitted with AJAX to avoid refresh, so you can freely embed it in another view (e.g. in a modal)
- Custom Symfony form errors
- Custom flash message with Bootstrap alert success on successful registration
- Activation link sent to newly registered user, leading to confirmation view with a button
- On button click, redirect to login page with custom flash message with Bootstrap alert success
- Login form, submitted with AJAX to avoid refresh, so you can freely embed it in another view (e.g. in a modal)
- Username/email compatible field
- Bootstrap alert danger with message on wrong credentials
- Redirect to login page on access attempt to page requiring authentication
- Remember me
- Guard
- Add your own logic to what happens when user logs in successfully (e.g. redirect to specific route depending on user role)
- Add your own logic to what happens when user fails to log in (e.g. count before account timeout)
- Logout route
- CSRF token support to prevent malicious logout by third-parties
- Account information edit form with username field (add fields as needed)
- Form embedded in parent view through Twig
{{ render(controler()) }}
, so you can group it together with other account related forms - Form submitted with AJAX to avoid refresh
- Custom Symfony form errors
- Bootstrap alert success with message on successful edit
- Email change form submitted with AJAX to avoid refresh
- Form embedded in parent view through Twig
{{ render(controler()) }}
, so you can group it together with other account related forms - Custom Symfony form errors
- Custom flash message with Bootstrap alert success on successful submit
- Verification link and link lifetime sent to the new email address, leading to confirmation view with a button
- On button click, redirect to login page with custom flash message with Bootstrap alert success
- Customizable delay between each change request
- Prevents user from spamming another email address
- If delay is not expired, shows successful submit flash message
- Customizable verification link lifetime
- Custom flash message with Bootstrap alert danger if verification link has expired
- Password change form submitted with AJAX to avoid refresh
- Form embedded in parent view through Twig
{{ render(controler()) }}
, so you can group it together with other account related forms - Current password field
- Repeat new password field
- Custom Symfony form errors
- Custom flash message with Bootstrap alert success on successful change
- Password reset form with username/email compatible field
- Request and reset forms submitted with AJAX to avoid refresh
- Custom flash message with Bootstrap alert success on successful submit
- Customizable delay between each reset request
- Shows previous success flash message even if delay is not expired (email is probably on the way and the user is impatient)
- Customizable reset link lifetime
- Custom flash message with Bootstrap alert danger if reset link has expired
- Email with reset link and link lifetime sent to user
- On reset password form submit, account is considered to be activated if it wasn't already
- On reset success, redirect to login page and custom flash message with Bootstrap alert success
- Request modal reachable only if user is logged-in
- Customizable deletion link lifetime
- Custom flash message with Bootstrap alert danger if deletion link has expired
- Confirmation link and link lifetime sent to user, leading to confirmation view with two buttons
- On delete button click, account is deleted and user is redirected to homepage with custom flash message with Bootstrap alert success
- On cancel button click, user fields related to account deletion are cleared, invalidating the deletion link, and user is redirected to homepage
- Event listener triggered on each request through
onKernelRequest()
method - Redirect to homepage if authenticated user attempts to access "logged-out only" routes (e.g. login, register and password reset)
- Add your own routes and modify existing list
- Event listener triggered on login through
onSecurityInteractiveLogin
method - Rehashes password on login if algorithm or algorithm options have been modified in
config/packages/security.yaml
- Works automatically with any algorithm supported by the
algorithm: auto
setting (Bcrypt, Argon2i and Argon2id)- User logs in with old algorithm/options
- Password is then automatically hashed and stored according to current preferred algorithm/options
- Without this listener, hash change would apply only to password persisted (registration) or updated (password change or reset) after the change
- This could be an issue if your existing users don't update their password
- A workaround would be to force your users to change password but it is bad practice for multiple reasons and you could have to deal with distrust ("Why are you asking me that? Have you been hacked? Is my data safe?")
- This listener prevents all that by working seamlessly in the backgroup while your users log in
- Works automatically with any algorithm supported by the
- Password checked through
password_needs_rehash
method - Argon2id implementation
- Modify listener and config files to implement algorithms not supported by the
auto
encoder. If you need to switch algorithm on an existing database, see here
- Prevents your users from choosing a password compromised in known data breaches
- Password validation through Troy Hunt haveibeenpwned.com API
- Custom Symfony form error
- Consider implementing this through something less strict than a validator if you think it could deter potential users (e.g. an informative message on user profile or a password strength meter)
- Usable separately or conjointly with the back-end HIBP password validator
- Visual indicator ONLY, to help your users choose a "good" password
- Password strength is based on length, zxcvbn password strength estimator from Dropbox and a check against previously leaked passwords through Troy Hunt haveibeenpwned.com API (if available)
- Command to delete users registered for more than
d
days if they haven't activated their account - Removes accounts that will most probably never be used
- Modify time between registration and removal as needed
- Execute
php bin/console app:remove-unactivated-accounts-older-than d
command (e.g. through a cron job)
- Registration
- If form is valid, shows success message even if email address is already registered to another account
- If form is valid, hashes password even if email address is already registered to another account (password hashing takes some time, not hashing the submitted password if user already exists shortens the response time and could help user enumeration)
- If email address is already registered to another account and is:
- verified: sends an email to the existing user, suggesting him to reset his password (we assume user is trying to create a new account because he forgot the password)
- unverified: sends an email to the existing user with a verification link (similar logic)
- If email address is already registered to another account, hashes password (password hashing takes some time, not hashing the submitted password if user already exists shortens the response time and could help user enumeration)
- Login
- Same error message if wrong password or if user doesn't exist
- If email address is not yet verified a new verification email is sent
- Password reset
- If form is valid, shows success message even if email address or username is not registered to any account
- If email address or username is registered to an account and retry delay is expired or inexistant (first try), sends the email
- Email address change
- If form is valid and new email address is not the same than current email address, shows success message
- If new email address is not already registered to another account and retry delay is expired or inexistant (first try), also sends a verification email
- if new email address is already registered to another account, only shows success message without sending a verification email (we assume this is an enumeration attempt)
- If form is valid but new email address is the same than current email address, shows error message
- If form is valid and new email address is not the same than current email address, shows success message
Important: Spool emails from file should be enabled in production environment or the delay between form submission and server response could hint that an email has been sent.
- Event listener triggered on each response through
onKernelResponse()
method - Adds custom headers to the response
- Support for "static" headers specified in
config/response_header_setter/response_headers.yaml
- Currently includes security / privacy related headers:
- Referrer-Policy
- Strict-Transport-Security (remember to register the domain on https://hstspreload.org/ or preload will not work)
- X-Content-Type-Options
- X-Frame-Options
- X-XSS-Protection
- Currently includes security / privacy related headers:
- Support for "dynamic" headers generated according to specific parameters (app environment, requested route...)
- Currently includes a Content Security Policy header generator and setter:
- Allows you to protect your users from malicious resources (e.g. malicious JavaScript code that could end up in your dependencies, like this one)
- Two level policy, lax & strict, in case you want to make sure critical routes are better protected (e.g. your website consumes an API with Ajax/fetch or requires a CDN for specific features, but you want to make sure this API or CDN cannot compromise your most critical routes, like login or checkout, if they ever become compromised themselves)
- Customizable directives for each policy level through a config file in
config/response_header_setter/content_security_policy.yaml
(modify existing ones, add your own) - Supports
report-uri
, two modes:plain
: specify the URL of your report-uri logger endpointmatch
: specify the route name, router will handle URL generation. Can only be used if your report-uri logger is part of the same application
- Dev environment directives to generate (less secure) directives allowing Symfony Profiler to work properly. The Profiler relies on inline JS and CSS, which you are strongly advised to block in production environment to counter XSS. Current whitelists block these by default in production environment.
- Currently includes a Content Security Policy header generator and setter:
You need to create .env.test.local
containing a valid DATABASE_URL
env variable so tests are able to use the database.
You need an activated user in database, otherwise tests requiring an authenticated user will fail.
These env variables are also required but you can leave them blank (VARIABLE_EXAMPLE=
):
MAILER_URL
MAILER_HOST
MAILER_SENDER
MAILER_REPLY_TO
Run tests with php bin/phpunit
.
Existing tests include:
- Basic routing tests (tests/RouterTest.php)
- Test if public page accessible while authenticated anonymously does not return 500 nor 404 response to anonymous user
- Test if page only accessible while authenticated redirects anonymous user to login page
- Test if page only accessible while authenticated does not return 500 nor 404 response to authenticated user
- Add your own routes to
providePrivateUrls()
andprovidePublicUrls()
- Test if logout is successful, needs improvements (tests/Controller/User/SecurityControllerTest.php)
- Test if public page accessible only while authenticated anonymously returns 302 response to authenticated user (tests/EventListener/RedirectIfAuthenticatedTest.php)