This post originally appeared on Symfony Station.
It’s long past time to make your Symfony applications as secure as possible.
As should be obvious to anyone not living under a rock, cyber security is critical. This has always been the case, but it’s even more important now thanks to Russian war crimes in Ukraine.
The potential for a cyberwar has never been higher. As Russia struggles with illegally occupying Ukraine it may lash out with cyberattacks against anyone supporting the Ukrainian nation.
Russia’s fellow rogue state allies, Belarus, Iran, North Korea, and China, could increase their current efforts. Plus, many cyber-criminal gangs are based in these countries.
In this article, we will examine Symfony’s security system. I am not a Symfony security professional so I will quote Symfony often and paraphrase it even more.
Non-Symfony Security
But before we look at Symfony, there are many things you should be doing for security, regardless of the platform you use.
Techniques you should implement include:
- Using HTTPS instead of HTTP for websites
- Using secure hosting if not SymfonyCloud from Platform.sh
- Using Dependabot and other security solutions from GitHub or its competitors
- Writing more secure code with the OWASP Top 10 Proactive Controls
- Continually researching and reading cybersecurity information
- Maxing out the security settings on an IDE like PHP Storm
- Securing the front-end of headless applications
- Serialization security
- Using a secure CDN
- Input validation
How to maximize your Symfony Application’s Security
First of all, after reading this overview, be sure to review the Symfony security docs and the SymfonyCasts security course. Bookmark these links, as everything will not be covered in detail in this article. However, we will provide links to the relevant security techniques we explore here.
SymfonyCasts is a paid service well worth the expense. Here’s their description of the security course, “It's security time! Symfony 5.3 comes with a reimagined version of its security system and I ❤️it! Yes, it's still super flexible & dependable. But the "guts" have been streamlined and simplified, making it easier to get your job done and giving you readable code if you need to dive into the core.
In this course, we'll go from an introduction into Symfony security into a full-blown application with users, permissions, custom voters and multiple ways to authenticate.”
So, check it out.
Symfony notes that it “provides many tools to secure your application. Some HTTP-related security tools, like secure session cookies and CSRF protection, are provided by default. The SecurityBundle provides all the authentication and authorization features needed to secure your application.
A bundle is similar to a plugin in other software but even better. The core features of the Symfony framework are implemented with bundles (FrameworkBundle, SecurityBundle, DebugBundle, etc.)”
There are also third-party security bundles. Below we will look at a 2FA one.
Symfony Flex
For ease of use with the installation of them, you should implement Symfony Flex.
Again, Symfony notes “A common practice when developing Symfony applications is to install packages (Symfony calls them bundles) that provide ready-to-use features. Packages usually require some setup before using them (editing some files to enable the bundle, creating some files to add some initial config, etc.)
This setup can be automated and that's why Symfony includes Symfony Flex, a tool to simplify the installation/removal of packages in Symfony applications. Technically speaking, Symfony Flex is a Composer plugin that is installed by default when creating a new Symfony application and which automates the most common tasks of Symfony applications.”
What are we going to review?
Symfony security includes techniques like:
- Password Hashing
- Two-Factor Authentication Bundle
- Firewall/Authentication
- Authorization/User Control
- Security Events
- Cross-site request forgery prevention
- LDAP Server Authentication
User Permissions
Symfony permissions are linked to a user object. If you need to secure (parts of) your application, you need to create a user class. It will implement UserInterface. While it’s often a Doctrine entity, you can also use a dedicated Security user class.
Symfony notes “Symfony comes with several built-in user providers:
- Entity User Provider - Loads users from a database using Doctrine;
- LDAP User Provider - Loads users from an LDAP server;
- Memory User Provider - Loads users from a configuration file;
- Chain User Provider - Merges two or more user providers into a new user provider.
The built-in user providers cover the most common needs for applications, but you can also create a custom user provider.”
Password Hashing
Most applications require their users to log in via a password. If this is the case for you, the Symfony SecurityBundle provides password hashing and verification functionality.
You can access the details in the documentation.
SchebTwoFactorBundle
Symfony notes “the bundle hooks into the security layer and listens for authentication events. When a user login appears and the user has two-factor authentication enabled, access and privileges are temporarily withheld, putting the authentication status into an intermediate state. The user is challenged to enter a valid two-factor authentication code. Only when that code is entered correctly, the associated roles are granted.
To represent the state between login and a valid two-factor code being entered, the bundle introduces the role-like attribute IS_AUTHENTICATED_2FA_IN_PROGRESS
, which can be used in is_granted()
calls. IS_AUTHENTICATED_FULLY
is – just like roles – withheld until the two-factor authentication step has been completed successfully.”
The bundle works out of the box with:
Firewall/Authentication
Again Symfony notes “The firewalls
section of config/packages/security.
YAML is the most
important section. A "firewall" is your authentication system: the firewall defines which parts of your application are secured and how your users will be able to authenticate (e.g. login form, API token, etc).
Only one firewall is active on each request: Symfony uses the pattern
key to find the first match (you can also match by the host or other things).
The dev
firewall is a fake firewall: it makes sure that you don't accidentally block Symfony's dev tools - which live under URLs like /_profiler
and /_wdt
.
All real URLs are handled by the main
firewall (no pattern
key means it matches all URLs). A firewall can have many modes of authentication, in other words, it enables many ways to ask the question "Who are you?".”
The SecurityBundle comes with many authenticators:
- Form Login
- JSON Login
- HTTP Basic
- Login Link
- X.509 Client Certificates
- Remote users
- Custom Authenticators
Limiting Login Attempts
Symfony provides basic protection against brute force login attacks via enabling the login_throttling
setting.
Authorization/Access Control
This security technique is fairly standard. Users log in to your app using your login form. You can deny access and work with the User object. That’s authorization, and it decides if a user can access some resource (a URL, a model object, a method call, etc).
The process has two sides:
- The user receives a specific role when logging in (e.g.
ROLE_ADMIN
). - You add code so that a resource (e.g. URL, controller) requires a specific "attribute" (e.g. a role like
ROLE_ADMIN
) to be accessed.
Voters are Symfony's most powerful way of managing permissions. They allow you to centralize all permission logic, then reuse them in many places.
Security Events
Symfony notes “During the authentication process, multiple events are dispatched that allow you to hook into the process or customize the response sent back to the user. You can do this by creating an event listener or subscriber for these events.”
Authentication events include:
- CheckPassportEvent - Dispatched after the authenticator created the security passport. Listeners of this event do the actual authentication checks (such as checking the passport, validating the CSRF token, etc.)
- AuthenticationTokenCreatedEvent - Dispatched after the passport was validated and the authenticator created the security token (and user). This can be used in advanced use-cases where you need to modify the created token (e.g. for multi-factor authentication).
-
AuthenticationSuccessEvent - Dispatched when authentication is nearing success. This is the last event that can make an authentication fail by throwing an
AuthenticationException
. - LoginSuccessEvent - Dispatched after authentication was fully successful. Listeners to this the event can modify the response sent back to the user.
-
LoginFailureEvent - Dispatched after an
AuthenticationException
was thrown during authentication. Listeners to this event can modify the error response sent back to the user.
Other Events
- LogoutEvent - Dispatched just before a user logs out of your application.
- TokenDeauthenticatedEvent - Dispatched when a user is deauthenticated, for instance, because the LogoutEvent password was changed.
- SwitchUserEvent - Dispatched after impersonation is completed.
How to Implement CSRF Protection
Symfony notes “CSRF - or Cross-site request forgery - is a method by which a malicious user attempts to make your legitimate users unknowingly submit data that they don't intend to submit.
CSRF protection works by adding a hidden field to your form that contains a value that only you and your user know. This ensures that the user - not some other entity - is submitting the given data.
Authenticating against an LDAP server
If you are using an LDAP server, Symfony provides several ways to work with it.
They note “the Security component offers:
- The LDAP user provider uses the LdapUserProvider class. Like all other user providers, it can be used with any authentication provider.
- The
form_login_ldap
authentication provider, for authenticating against an LDAP server using a login form. Like all other authentication providers, it can be used with any user provider. - The
http_basic_ldap
authentication provider, for authenticating against an LDAP server using HTTP Basic. Like all other authentication providers, it can be used with any user provider.
This means that the following scenarios will work:
- Checking a user's password and fetching user information against an LDAP server. This can be done using both the LDAP user provider and either the LDAP form login or LDAP HTTP Basic authentication providers.
- Checking a user's password against an LDAP server while fetching user information from another source (database using FOSUserBundle, for example).
- Loading user information from an LDAP server, while using another authentication strategy (token-based pre-authentication, for example).”
Conclusion
We have now reviewed the basics of Symfony security.
You can see how it is (fortunately) very extensive and flexible. It allows you many options for how you want to implement its best practices. You can customize security based on your server, business logic, security events, authorization roles, authenticators, user providers, plus other practices not covered here.
As noted above explore the Symfony security documentation in detail. Also, consider subscribing to SymfonyCasts and taking the security course.
To close please make sure your Symfony application is as secure as possible by implementing these best practices. It’s vital that you keep your users safe from cybercriminals and the enemies of democracy. Security is just as important as speed, usability, accessibility, search optimization, and the other aspects of your app.
Stay safe Symfonistas!
Reuben Walker
Founder
Symfony Station
Top comments (2)
That appears to be excellent however i am still not too sure that I like it. At any rate will look far more into it and decide personally! horse stance
Great ! I'm gonna take a look to SchebTwoFactorBundle. I did not know it .