Skip to content

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:

/login/

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:

/admin/

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.

This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.
TOC
    Version