Introduction to Symfony Security
What's up developers and welcome back to a new video where we will be diving into authorization and Symfony. Quick pause, do you want to support the channel and want me to continue on creating content? You can support the channel on Patreon right now where you get benefits such as a private Discord group where you can share your coding issues and other developers will help you out. If you are interested to join, the link will be in the description down below.
Setting up a basic user authentication system can consume a lot of time. Symfony has found a way to make an authentication system that's easy to use and understandable, but flexible enough to fix in a variety of settings. Whenever you want to use Symfony's authentication system, you need to pull in a package they created. So let's do that. Inside the CLI, let's navigate to Visual Studio Code. Inside the CLI, let's perform the composer require symfony/security-bundle command.
By default, Symfony projects provide tools for security. The command that we just performed is mainly for authentication and authorization. Before we dive into the actual code, I want to quickly let you know what the difference is between authentication and authorization, since these terms will get mixed up quite a lot.
When you're talking about authentication, you're basically verifying who someone is and either allowing him to act as a person in your system. Think about the login and log out process. A user identifies themselves in your application by logging in, and once a user wants to get out of your application, you'll be logging out.
Authorization, on the other hand, determines whether the authenticated user is allowed to perform a specific behavior in your application. Even if we think about the project we created, any user can create a post, edit a post, and even delete a post. To hide those elements on a webpage for certain users, authorization is needed.
Creating the User Entity with make:user
With the commands that we just performed, Symfony console added a new command which allows us to create a new user in our application.
If we perform the symfony console inside the CLI, scroll up to the make section right here, you can see that Symfony allows us to make a new user. So let's test it out. Let's say symfony console make:user. We can add one argument right there, where we should define the name of our table. So let's say with a capital User.
Hit enter. All right, let me zoom in for the next couple questions.
At the moment Symfony is asking us whether we want to store user data in the database through Doctrine, and that's actually what we want to do. So let's say yes. Next up, it's asking us if we want to change the default unique value of a user. By default it's email, but you can change it up to ID, username or even other properties that you'll like, but I want to keep it equal to email.
Now next up, Symfony is asking us if we want to hash incoming passwords. In the time we're living right now, you can't say no right here. So let's say yes. Now if we scroll up inside the CLI you can see that it has created two files for us, which is the user entity, the user repository. It has updated the user entity, and it has updated our security file.
User Entity Deep Dive & Database Migration
So let me make the terminal a little bit smaller and let's start off with our user entity. Right here you can see that it's implementing the User interface and the PasswordAuthenticatedUser interface. Now, as you can see, IntelliSense is throwing me an error, but this should work because it's good code. So don't worry about the red lines right here.
Now we have a couple properties. So our id, email, we even have our private roles, which is an array, which is awesome because a user can have a certain role inside your application. Now we have our password, and as you can see, it is a hashed password.
Then we have our getters and setters and let me actually scroll down to the roles because this is probably the coolest thing about Symfony's authentication scaffolding. In my opinion, the getRoles method that we have right here is pretty cool. You can add a default role for every single user right here, which is ROLE_USER, but you can change it up right here. And what you can do later on in your application is basically saying, well, we have one admin in our application and the rest are all role users.
In order to wire the user entity with the authentication scaffolding, it has updated the security configuration inside our application. Now this can be found inside the config folder, packages folder, where you have a security.yaml file. So let's open it. I won't go over this file since it's mostly configuration, but the most important addition right here is the password hasher section.
Now the command that we performed inside the CLI didn't create a migration for us, so let's do that ourselves. Inside the CLI, let's perform symfony console make:migration. We're getting a warning, so let's say yes to continue on. All right, it has been migrated. All right, it has created a migration for us. Now let's run it by saying symfony console doctrine:migrations:migrate. It's asking us if we want to continue because it will overwrite values. so let's say yes and it's asking us if we're very sure. Let's say yes again. And all right, this is pretty cool.
Now let's open our database tool. At the sidebar, let's refresh our table, and as you can see it has created a user table. If we open it, you'll see a user has an ID, email, roles, and passwords.
Now let's define a user, and we're definitely not going to use the database client tool for it since there's a CLI command for it. Symfony allows us to make a registration form with one simple command. Inside the CLI, let's say symfony console make:registration-form. Let's hit enter. All right, it's going to ask us a couple questions.
First off, it wants to know whether we want to use the User class that we just defined. So let's say yes. Then it's going to ask us whether we want to add email verification. I'm going to say no because I want to keep it very simple and adding email providers right now will cost us a lot of time. So let's hit enter. The next question is whether we want to automatically authenticate a user, so let's say yes. And once a user is authenticated, we need to send them back to a route. In our case, let's say number one because we want to send it back to the movies endpoint, which has a name of movies.
Let's hit enter and as you can see, we can go to the browser and open our /register endpoint. But before we do that, let's talk about the other files that have been pulled in. So let me close off my config folder. I don't need my public folder, but I do need a new controller, which is the RegistrationController. Now I'm not going over the code right here because it will just handle the registration part. Just pause the video and take a look at it and see what's going on right here.
Then we got a RegistrationFormType, which will handle the input fields from the front end. And finally, it has created a view for us inside the registration folder where it has a file called register.html.twig. If we open it, you'll just see our form_start, form_row, we have a label, another form_row, we have our button and our form_end. Now if we navigate back to the browser and open our /register endpoint, you'll see a form right under my header. So let me actually turn that off. So let me delete it. All right.
Right here, but it has minimal styling. So what I've done before this video was creating the style so we could basically copy-paste two pages and continue on with this tutorial. I've got my GitHub repository open, which will be linked in the description down below. And once you open it, open the templates folder, open the register.html.twig, copy it, let's navigate back to Visual Studio Code, select everything in the register.html.twig file and hit backspace, and paste what we just copied. It's basically what we had before, but with a little bit more styling added to it.
Now the second file will be the RegistrationFormType because our input field classes are defined right there. so let's go back to our static movies, open the src folder, Form folder, and let's open the RegistrationFormType. Copy it, navigate back to Visual Studio Code and let's open our... that was the wrong one... RegistrationFormType. Select everything and paste what we just copied. Save it.
Let's navigate back to Google Chrome. And I'm once again getting error messages of... I can't see them. Oh yeah, it's true, which is IntelliSense which is not actually working quite good with Symfony. So, let's navigate back to Google Chrome. Let's open our localhost. Oops, it's throwing an error because it's using the wrong class. Let's replace Admin with User. Save it, let's navigate back. All right, and as you can see, our register screen looks a lot better right now.
Testing User Registration
Now let's try to register an account. Let's add an email of [email protected] and a password of admin1234!. Let's agree the terms and click on register. I don't want to save my password. And as you can see, we have indeed been redirected to the movies endpoint, but we can't really see a user or whatever that has been logged in.
So let's navigate back to Visual Studio Code and let's quickly open our users table and let's run it one more time. We have one new row with an email of admin@gmail and a hashed password. The roles is empty because it has a default role, and whenever you want to redefine a new role for an admin, you can simply overwrite the value that has been defined right now.
Generating the Login System with make:auth
Now let's focus on the login part right now. Just like the register page, Symfony has a command that allows us to create a complete login scaffolding. So what we need to do is to go inside the CLI and say symfony console make:auth. Hit enter.
Now it's asking us whether we want to start from scratch or use their login form authenticator. So what I want to do is to use theirs, so let's say 1. Hit enter. The second question is the class name, which will be LoginFormAuthenticator. Hit enter. Then it's asking us what the name of our controller should be. By default, it is SecurityController, so let's say SecurityController. The last question is whether we want to have our logout endpoint. So let's say yes, because I definitely want to have that.
Once again, this will mess up our styling, so I've also created the login route. So let's navigate back to our GitHub repository. Let's go to our root templates folder, login folder, copy the code, navigate back to Visual Studio Code, open the security folder, login.html.twig, select everything, delete it, and paste it. Save it.
Configuring the Post-Login Redirect
Now before we can actually log in, we need to change up one thing, and that's the redirect once a user logs in. Now I can actually show you what goes wrong right now. If we navigate back to the endpoint of /login, this works. Now let's try to log in with an email of [email protected] and a password of admin1234!. Click on Sign in. And as you can see, we're getting an error message right now because there is one to-do left, because Symfony said, well you need to define where you want to be redirected to once you log in.
So let's do that. Let's navigate back to the code editor. There is a new security folder inside the route. Let's open it with our LoginFormAuthenticator. Now let's scroll down to the onAuthenticationSuccess method that we have. And instead of saying throw new Exception, let's copy what we have above and let's replace it with our exception. Instead of saying some_route, let's say the movies route. If we save it and navigate back to the browser, refresh it and click on continue, you'll see that we have been redirected to the movies endpoint.
Displaying Authentication State in Twig
Now you still can't really see if a user has been logged in or not, so let's double check that. Let's navigate back to Visual Studio Code. Let's open our index.html.twig file and somewhere right here, add Twig snippets. Now in order to access your session, you need to write down app.user. But the user is an array of the actual user so what we can say is go deeper into our array by saying .email. So print me out the email of the user. Save it, navigate back, refresh it, and as you can see, [email protected] has been printed out.
Now if we navigate back for a second and remove our Twig snippet, save it, and let's open our SecurityController. So right here at the top we have a SecurityController. You'll see that at the bottom we have our logout function. So let's make sure that we add this inside the URL, and once a user logs in, he has the opportunity to log out.
Our menu has been stored inside the base.html.twig file so let's open it. Let's scroll down. Open the base.html.twig file. Let's navigate to the bottom, so to our last list item, which is contact. Let's duplicate it on the line below. Alright. For our endpoint right here we're not going to /contact, so let's remove it, but we're going to add a ternary operator right here after our forward slash. So, that's that, Twig snippets. What we want to check is whether our app.user has been set or not. Question mark. If it has been set, print out logout. If the user has not been set, so colon, print out login.
Now the same thing needs to be done for the contact text that we have, so let's remove it. Let's add Twig snippets. Let's see if app.user has been set. Question mark. If it has, print out "Logout" with a capital L. If it hasn't, print out "Login".
If we save it, navigate back to the browser, refresh it, you'll see that Logout has been printed out. Let's click on it. Let's change our endpoint to /movies and as you can see, we're not logged in and Login has been printed out in our menu.
Conclusion & What's Next
This was it for this video, where I showed you how authentication works in Symfony. In the next video, we'll be diving into hiding certain elements and pages whenever a user is not logged in. If you do like my content and you want to see more, leave this video a thumbs up. And if you're new to this channel, please hit the subscribe button.
you