Securing the Admin Backend
The admin backend interface should only be accessible by trusted people. Securing this area of the website can be done using the Symfony Security component.
Defining a User Entity
Even if attendees won't be able to create their own accounts on the website, we are going to create a fully functional authentication system for the admin. We will therefore only have one user, the website admin.
The first step is to define a User
entity. To avoid any confusions, let's name it Admin
instead.
To integrate the Admin
entity with the Symfony Security authentication system, it needs to follow some specific requirements. For instance, it needs a password
property.
Use the dedicated make:user
command to create the Admin
entity instead of the traditional make:entity
one:
1
$ symfony console make:user Admin
Answer the interactive questions: we want to use Doctrine to store the admins (yes
), use username
for the unique display name of admins, and each user will have a password (yes
).
The generated class contains methods like getRoles()
, eraseCredentials()
, and a few others that are needed by the Symfony authentication system.
If you want to add more properties to the Admin
user, use make:entity
.
Let's add a __toString()
method as EasyAdmin likes those:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
--- a/src/Entity/Admin.php
+++ b/src/Entity/Admin.php
@@ -54,6 +54,11 @@ class Admin implements UserInterface, PasswordAuthenticatedUserInterface
return (string) $this->username;
}
+ public function __toString(): string
+ {
+ return $this->username;
+ }
+
/**
* @see UserInterface
*/
In addition to generating the Admin
entity, the command also updated the security configuration to wire the entity with the authentication system:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
--- a/config/packages/security.yaml
+++ b/config/packages/security.yaml
@@ -1,7 +1,15 @@
security:
+ password_hashers:
+ App\Entity\Admin:
+ algorithm: auto
+
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
- in_memory: { memory: null }
+ # used to reload user from session & other features (e.g. switch_user)
+ app_user_provider:
+ entity:
+ class: App\Entity\Admin
+ property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
We let Symfony select the best possible algorithm for hashing passwords (which will evolve over time).
Time to generate a migration and migrate the database:
1 2
$ symfony console make:migration
$ symfony console doctrine:migrations:migrate -n
Generating a Password for the Admin User
We won't develop a dedicated system to create admin accounts. Again, we will only ever have one admin. The login will be admin
and we need to generate the password hash.
Select App\Entity\Admin
and then choose whatever you like as a password and run the following command to generate the password hash:
1
$ symfony console security:hash-password
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Symfony Password Hash Utility
=============================
Type in your password to be hashed:
>
------------------ ---------------------------------------------------------------------------------------------------
Key Value
------------------ ---------------------------------------------------------------------------------------------------
Hasher used Symfony\Component\PasswordHasher\Hasher\MigratingPasswordHasher
Password hash $argon2id$v=19$m=65536,t=4,p=1$BQG+jovPcunctc30xG5PxQ$TiGbx451NKdo+g9vLtfkMy4KjASKSOcnNxjij4gTX1s
------------------ ---------------------------------------------------------------------------------------------------
! [NOTE] Self-salting hasher used: the hasher generated its own built-in salt.
[OK] Password hashing succeeded
Creating an Admin
Insert the admin user via an SQL statement:
1 2 3
$ symfony run psql -c "INSERT INTO admin (id, username, roles, password) \
VALUES (nextval('admin_id_seq'), 'admin', '[\"ROLE_ADMIN\"]', \
'\$argon2id\$v=19\$m=65536,t=4,p=1\$BQG+jovPcunctc30xG5PxQ\$TiGbx451NKdo+g9vLtfkMy4KjASKSOcnNxjij4gTX1s')"
Note the escaping of the $
sign in the password column value; escape them all!
Configuring the Security Authentication
Now that we have an admin user, we can secure the admin backend. Symfony supports several authentication strategies. Let's use a classic and popular form authentication system.
Run the make:auth
command to update the security configuration, generate a login template, and create an authenticator:
1
$ symfony console make:auth
Select 1
to generate a login form authenticator, name the authenticator class AppAuthenticator
, the controller SecurityController
, and generate a /logout
URL (yes
).
The command updated the security configuration to wire the generated classes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
--- a/config/packages/security.yaml
+++ b/config/packages/security.yaml
@@ -16,6 +16,13 @@ security:
security: false
main:
anonymous: lazy
+ guard:
+ authenticators:
+ - App\Security\AppAuthenticator
+ logout:
+ path: app_logout
+ # where to redirect after logout
+ # target: app_any_route
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#firewalls-authentication
As hinted by the command output, we need to customize the route in the onAuthenticationSuccess()
method to redirect the user when they successfully sign in:
1 2 3 4 5 6 7 8 9 10 11 12 13
--- a/src/Security/AppAuthenticator.php
+++ b/src/Security/AppAuthenticator.php
@@ -49,9 +49,7 @@ class AppAuthenticator extends AbstractLoginFormAuthenticator
return new RedirectResponse($targetPath);
}
- // For example:
- //return new RedirectResponse($this->urlGenerator->generate('some_route'));
- throw new \Exception('TODO: provide a valid redirect inside '.__FILE__);
+ return new RedirectResponse($this->urlGenerator->generate('admin'));
}
protected function getLoginUrl(Request $request): string
Tip
How do I remember that the EasyAdmin route is admin
(as configured in App\Controller\Admin\DashboardController
)? I don't. You can have a look at the file, but you can also run the following command that shows the association between route names and paths:
1
$ symfony console debug:router
Adding Authorization Access Control Rules
A security system is made of two parts: authentication and authorization. When creating the admin user, we gave them the ROLE_ADMIN
role. Let's restrict the /admin
section to users having this role by adding a rule to access_control
:
1 2 3 4 5 6 7 8 9 10 11
--- a/config/packages/security.yaml
+++ b/config/packages/security.yaml
@@ -35,7 +35,7 @@ security:
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
- # - { path: ^/admin, roles: ROLE_ADMIN }
+ - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
when@test:
The access_control
rules restrict access by regular expressions. When trying to access a URL that starts with /admin
, the security system will check for the ROLE_ADMIN
role on the logged-in user.
Authenticating via the Login Form
If you try to access the admin backend, you should now be redirected to the login page and prompted to enter a login and a password:
Log in using admin
and whatever plain-text password you chose earlier. If you copied my SQL command exactly, the password is admin
.
Note that EasyAdmin automatically recognizes the Symfony authentication system:
Try to click on the "Sign out" link. You have it! A fully-secured backend admin.
Note
If you want to create a fully-featured form authentication system, have a look at the make:registration-form
command.
Going Further
- The Symfony Security docs;
- SymfonyCasts Security tutorial;
- How to Build a Login Form in Symfony applications;
- The Symfony Security Cheat Sheet.