Configuration In Security.yml
In the previous video we created a basic login form using a Bootstrap sign in template. We added a SecurityController
which enabled our site visitors to hit the /login
route and see this new login form.
However, if the user were to fill the form in, and then click 'Sign in' then unfortunately, not a lot would happen.
This is what we are going to fix in this video.
To help us get started as easily as possible we are going to create a 'hardcoded' user which will reside in our Symfony site configuration. This means we won't need to store off our user details to the database at this stage. Instead this user will be created in Symfony's security.yml
file.
By default we start with the following security.yml
file:
# /app/config/security.yml
security:
providers:
in_memory:
memory: ~
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
I've stripped out all the comments from this file. The section we are interested in is under security.providers.in_memory
. Let's change this up:
# /app/config/security.yml
security:
providers:
in_memory:
memory:
users:
admin:
password:
roles: 'ROLE_ADMIN'
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
Ok, so we've defined a new key under security.providers.in_memory.memory
called users
.
This users
key contains on user, the admin
user.
We could add more in here, just add in another entry at the same level of indentation as the admin
user. For now, however, we only need the one.
I've left the password
value blank, and that's intentional. More on this in a moment.
We also have defined the role of ROLE_ADMIN
. When our admin
user is successfully logged in, this user will get given this role. We could add any entries we like here. This roles
property accepts either a string, or an array. For now, we have simply passed in a single string.
Ok, so back to the password.
Currently it's not valid - it's not blank, it's simply empty.
To make this work, we could either configure plain text passwords - don't do this - or create an encoded password.
We're going to go with the encoded password. I strongly urge you not to use plaintext passwords for two reasons:
- passwords should never be in plain text - it's a bad habit to get into
- I saw this being used for real, in real live production site in the financial services industry... I kid you not.
The thing is, most developers are lazy. I know I am. We all seem to have good intentions, but terrible memories. And once things work, we tend to immediately forget about changing that little 'hack' we used because heck, it works and we have 999 other things that don't. So, let's move on, right?
Anyway, we need to generate an encoded password for our password
key / value pair.
To do this, fortunately, Symfony comes with a built-in command line utility which we can make use of:
php bin/console security:encode-password
Symfony Password Encoder Utility
================================
[RuntimeException]
No encoder has been configured for account "Symfony\Component\Security\Core\User\User".
security:encode-password [--empty-salt] [-h|--help] [-q|--quiet] [-v|vv|vvv|--verbose] [-V|--version] [--ansi] [--no-ansi] [-n|--no-interaction] [-e|--env ENV] [--no-debug] [--] <command> [<password>] [<user-class>]
Ok, so on the command line the output looks a little prettier than this.
The error message here is actually really useful:
No encoder has been configured for account "Symfony\Component\Security\Core\User\User".
We have no encoder
configured.
Also, notice that Symfony considers our in_memory
user an instance of Symfony\Component\Security\Core\User\User
. We will need to know this as we can change up the encoding method on a per user type basis. Don't worry about this for now, we will cover it when we get to adding Database-backed users.
Let's add in an encoder
now:
# /app/config/security.yml
security:
encoders:
Symfony\Component\Security\Core\User\User:
algorithm: bcrypt
providers:
in_memory:
memory:
users:
admin:
password:
roles: 'ROLE_ADMIN'
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
We're going to use bcrypt
for encoding passwords.
With this value in place, we can retry our call the security:encode-password
, and this time we should get a little further:
php bin/console security:encode-password
Symfony Password Encoder Utility
================================
Type in your password to be encoded:
> # you won't see anything here, but I typed: admin
------------------ ---------------------------------------------------------------
Key Value
------------------ ---------------------------------------------------------------
Encoder used Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder
Encoded password $2y$13$C3D/lnwWeh73axMnldcB.euo.Gkv4IThttEFp2.yaEWiIt585zbOa
------------------ ---------------------------------------------------------------
! [NOTE] Bcrypt encoder used: the encoder generated its own built-in salt.
[OK] Password encoding succeeded
Cool. We have encoded our password. Now, we simply need to copy this value back into the security.yml
config:
# /app/config/security.yml
security:
encoders:
Symfony\Component\Security\Core\User\User:
algorithm: bcrypt
providers:
in_memory:
memory:
users:
admin:
password: $2y$13$C3D/lnwWeh73axMnldcB.euo.Gkv4IThttEFp2.yaEWiIt585zbOa #here
roles: 'ROLE_ADMIN'
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
Perfect.
There's a few extra things to note here.
Firstly, you can use shorthand when typing in Symfony console commands - no need to type out the full thing every time. You just need something unique enough to identify any particular command, e.g.:
php bin/console security:enc
Secondly, you can pass in the password as part of the console command - or if you omit it, it will go into the interactive mode as shown above.
If you are going to pass in your password via the command line, remember that if you start your Bash line entry with a space then it won't be saved off to the history
command output. Probably safest just to use the interactive mode all the same.
Lastly, one of the nice things about using bcrypt
is that we don't need to worry about a salt
.
Functioning Form
With a user to use for testing we can go ahead and finish up the Sign In form integration.
To begin with, we need to explicitly set up the form's action
path. Remember that as we are rendering these form fields out completely by hand, Symfony isn't in control of any of this. We need to be as specific as possible to ensure this all works the way we expect it too.
<!-- /app/Resources/views/security/login.html.twig -->
{% extends 'base.html.twig' %}
{% block body %}
<form class="form-signin" action="{{ path('login') }}" method="POST">
<h2 class="form-signin-heading">Please sign in</h2>
<label for="_username" class="sr-only">Username</label>
<input type="text"
id="_username"
name="_username"
class="form-control"
placeholder="Username"
required
autofocus>
<label for="_password" class="sr-only">Password</label>
<input type="password"
id="_password"
name="_password"
class="form-control"
placeholder="Password"
required>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
{% endblock %}
There's three important changes here.
Firstly, we've explicitly set the action
and method
:
<form class="form-signin" action="{{ path('login') }}" method="POST">
We're using the build in Twig path
function to ensure we are always pointing the login
route, which we defined in our SecurityController
.
We've set the method
to POST
, as this is always going to be a POST
request.
Next, we've added a name
property to both of our form input
fields:
These two name
properties must be in the format _username
for our username, and ahem, _password
for our password :)
When I first started working with Symfony's login system, I distinctly remember missing out on this rather crucial part and subsequently lost a good few hours in the resulting confusion.
If you really don't want _username
and _password
, you can change them:
# /app/config/security.yml
security:
// * snip *
firewalls:
main:
username_parameter: '_custom_username'
password_parameter: '_custom_password'
anonymous: ~
If you do this, be sure to use the same value as your name
parameter in your login template. Starting with an underscore is not mandatory, it's just convention.
We're not quite done just yet.
Next, we must update our application's firewall.
Now, a firewall in Symfony is not quite the same as iptables
or some enterprise hardware firewall that retails for a rather cheap £42,000. I mean, for a start it's free. Well, open source.
Anyway, I digress.
A firewall in Symfony allows us as developers to configure authentication methods.
This is an incredibly flexible arrangement, which inevitably means it can also be incredibly confusing to begin with.
Let's start with what we have already, and then build on it.
# /app/config/security.yml
security:
// * snip *
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
Firstly, ordering matters.
We have dev
before main
.
If any of the pattern
s inside dev
match, then main
will never be hit.
This pattern
key takes a regular expression. Regular expressions are a pain to learn and understand, but are both incredibly useful and conveniently, a lot more easy to decipher than they used to be, thanks to online tools like regex101.com:

Anyway, if we match any of the configuration in the dev
firewall then security
is false
(or disabled to you and me), so we can effectively ignore them for the purposes of this tutorial.
As it stands our main
firewall is configured just enough to stop Symfony throwing a massive Benny. But aside from that, we need to start working again here.
We'll start off by defining our own pattern
.
We want to match any other URI that's not covered by dev
. Thankfully, regex has us covered here:
# /app/config/security.yml
security:
// * snip *
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
anonymous: ~
Literally our new rule says - match anything starting with a /
. In other words, if it wasn't matched by dev
, we're interested in it.
But then we have anonymous: ~
, where ~
is YAML for null
. In other words, this means any site visitor will end up being anonymously identified. Huh?
Right, so there is no noticeable difference between a user who is “anonymously authenticated” and an unauthenticated user. This concept is used for authorisation. A firewall is concerned with authentication. Authorisation is not the same as authentication.
Imagine you own a shop. You allow potential customers to come inside your shop. You don't know who they are, but you know they are somebody. This is similar to anonymous authentication.
Then, to go behind your shop counter and operate the till - hopefully - this person is an authorised member of staff.
I hope that makes sense - but do feel free to shout up (by way of a comment below) if at all unsure. It's a tricky subject.
To continue, we are going to describe our login process. For this we will use form_login
, and we're going to need two keys - login_path
, and check_path
configuring underneath form_login
to tell Symfony which routes to intercept.
form_login
is an authentication provider that comes included with Symfony. You can create custom authentication providers, but your time may be better spent first checking if an existing bundle is available to meet any particular needs you may have.
login_path
, and check_path
are parts of the form_login
configuration.
I use /login
for both, but you can change these to be anything you like. For my needs, /login
has always worked fine.
We will add in logout: true
, which won't immediately work, but will be needed in a forthcoming video.
Finally, we will add in the key of provider
, and the value of in_memory
. This ensures that when trying to authenticate users for routes matching the given firewall pattern
, that these users will be found in the in_memory
value under the providers
section we configured earlier.
# /app/config/security.yml
security:
encoders:
Symfony\Component\Security\Core\User\User:
algorithm: bcrypt
providers:
in_memory:
memory:
users:
admin:
password: $2y$13$C3D/lnwWeh73axMnldcB.euo.Gkv4IThttEFp2.yaEWiIt585zbOa #here
roles: 'ROLE_ADMIN'
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
provider: in_memory
form_login:
login_path: login
check_path: login
logout: true
anonymous: ~
At this point you should be able to login with admin
/ admin
.
There's still a few more steps we need to take to make this function more in line with our basic expectations. For example, when logging in currently you will simply be redirected to the /login
route after successfully logging in. Kinda odd.
We will continue on with this, and more, in the very next video.