Secure Your Fullstack Angular - Spring Boot Application With the JWT Authentication

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Let's create a Fullstack project with Sprint Boot 3 and Angular 16. I will create a login form where I will need Sprint Security 6 and use the JWT to authenticate the requests. How does an authentication system work? My backend needs to know that somebody I trust makes the incoming requests. To create this trust, the backend validates a username and password from the frontend. But I do this only for the first request. Then, how to trust the following requests? I use the JWT. A JWT is an encoded Json document. When the backend first validates the username and password, it creates back a JWT. It's encoded with a secret key only known by the backend. Now, the frontend must store the JWT and use it for all the following requests. As the secret key is only known by the backend, if the received JWT is correctly decoded, the backend assumes the JWT was created by itself and it can trust the request. But when storing the JWT in the frontend, there is a risk that somebody steals the JWT and decrypts it. That's why, when generating the JWT, I add a validity date. As short as possible. Without compromising the user experience. So, as there is a risk to decrypting the JWT, I can't store critical information. That's why I will only store the user's information which is not critical, such as the first name, the last name, the roles and the username. This way, I don't need to request the database at each request. Let's Implement all of this by playing with an Angular frontend application and the backend Spring Boot application. But first, remember to click on the Subscribe button. Let's start by creating my Angular project. Let's install the project with the NG command. Accept the default configuration. Use the CSS. And wait to the creation. Let's take a look at the files. And the main component. Here is the root component. By default, it renders a home page with some Angular commands. I will clean it all and start by adding a new header. And now start the Angular project. This is the default homepage. Let's clean it. This is the default app component. I will leave it as it is. And this is the HTML which renders this page. Let's edit it. I start by creating a wrapper. Then, inside, I include the header component. With the title as a variable. And the logo path as a variable too. Let's create the header component. I start by adding the input variables: the page title and the logo path. Add the input import. Oh! I have to use a string as the input variables. Okay, now it works. Let's edit the header. I add the image with the logo path. And now the title using the variables of the component. But it's missing the logo. I will copy the image into assets. Here it is. Let's add some CSS to reduce the size. I fix the height. And now the header block. I put a color background. A fixed height too. And some padding. I put the white color for the text as the background is dark. I align all into the center. That's it. Let's switch to the backend side. I first need a simple backend with an endpoint to return some information. As always when creating a backend project, I will use Spring Initializer. I choose Maven, Java, the latest version of Spring Boot. A JAR packaging, and the version 17. Let's pick some dependencies I will need. Web as I need the endpoints. JPA for the database connection. I will use a Postgres database. And Lombok for the code generation. Let's download, unzip the project and open it with IntelliJ. Let's also add Mapstruct which is not available in Spring initializer. I can now create my endpoint to return some data. Let's first create the package which contains all the controllers. And now the messages controller which returns only a message. I define it as a rest controller because it will return a Json document. The name of the endpoint, messages. It will return a response entity, which is a list of strings. No input variables, and directly return the array of strings. Let's start my application to test this endpoint. Oh it's not starting correctly. I will disable JPA as I don't need the database now. And let's start my application to test this endpoint. Great! Let's go now to the frontend to request this endpoint. To request an endpoint, I need Axios. I will also add Bootstrap. Now, to use Axios, I will create a service which will handle all the requests to the backend. I will configure Axios to request my backend. I set by default the base URL of the backend. And by default all the post requests will have the header content type as Json. And now, create a new component where I will display the content from the backend. I name it auth, because later I will protect this endpoint with the Authentication. I start by importing the Axios service. I create a variable where I will store the backend response. And inject the Axios service into constructor. I will request the backend on the initialization of this component. I call the request method of the Axios service. It's just a get method to the messages endpoint, with no argument. And the response, I store it in the data variable. I add the bootstrap CSS style to be available all in my application. Let's start by creating a title. I will wrap it into a card. All those are Bootstrap components. I will include the component into a row and justify it into the center. And finally use a for Loop to display the content. The component is correctly rendered. Still, the backend doesn't send any response. If I take a look to the error, I see the CORS error. When connecting a frontend to my Spring Boot application, I need to enable the CORS. The CORS will accept an external frontend to access my backend. This is a typical error. So, let's configure my backend to accept an external frontend. Let's start by creating a package which will contain all the configurations. Inside, I create a class which will contain the CORS configuration. Now I created the bean which will contain the CORS configuration. This allows my backend to receive the headers which contain the authentication information. This is the URL of my frontend. Some typical headers my application must accept. The methods my backend must accept. The time the CORS configuration is accepted, 30 minutes. I apply the configuration to all my routes. And put this Bean at the lowest position to be executed before any Spring Bean. Let's try again. Great! I have now my frontend connected to my backend. Let's now create a login form to authenticate a user. I will create a new component which will switch between the backend content, the login form and a welcome message. This is the component to show a welcome message. This is the component with the login form. And this is the component which switch between all those components. Let's implement the content component with no logic, just displaying the welcome content component. I start by including the app content into the main component. And now the welcome content component. A big welcome message. I include nothing in the welcome component. Let's go directly to the HTML. I start with the title. Now a subtitle. Let's wrap all of these to justify it into the center. And now I will create the login form. I create an output field. This way, the submit method will be in the content component, in the parent component. Having the login request in the parent component allows me to handle the response, and switch from the login form to the auth content component once authenticated. And now create the variables which will contain the login and the password. And a method which will emit the output variable. To display a form I need to add a new module. Let's now edit the HTML of my login form. I start with the form. And link the method at the submit event. And now I create the input for the login. And link it to the variable. I add a label associated with the login. And wrap this. I've wrapped all the form to justify it into the center. And now do the same for the password. Finally, add the submit button. Now, I must go back to my content component to create the submit method. I start by linking the output variable into a method. And now create the method. I add the Axios service. Inject it in the constructor. And request the login endpoint. Let's return to the backend to create the login endpoint which accepts the username and the password. For that I will add the Spring Security dependency and protect the messages endpoint. Let's create now my login endpoint. It will be a rest controller. With the endpoint login. It will return a DTO. And receive as Json another DTO. I will create them later. Inside, I call a service with a login method. And directly return the user DTO. Let's create the credentials DTO as a record. And the user DTO as a POJO, a Plain Old Java Object. Why using a class or record? Because the credentials DTO is just for reception. I won't edit the content field by field. And for the user DTO, I will use it in the reception and in the transmission. And not all the fields will be set all together. Remember, the records are immutable. Let's continue with the login method in the service. But I want to check the credentials against my database. So, I first need to configure my database. I start by adding again the JPA dependency. Now, rename the configuration file to yaml. I prefer the yaml. I add the driver to a Postgres database. The URL of the database. The username and the password. This is the information to be able to connect to the database. This says to Spring JPA to create the schema at the startup of the application. Still I use this to quickly initialize my database. In production, I must use a database migration tool like Liquibase or Flyway. Let's start my database within a Docker image. And now create the entity which will be mapped to a table in the database. I start by creating a package which will contain all the entities. And now my user entity. I add some Lombok annotations. The Entity annotation and the table annotation with the name of the table. And now all the fields which will be mapped to the columns in the database. I add the ID annotation. With a generated value to have values automatically when creating an entity. And the column annotations to the other fields. I name the table app user because user is a table which is already reserved by Postgres. Let's finish the database configuration with the JPA Repository. I start by creating again the package. Now I extend the JPA Repository with the entity type and the type of the ID. I create a method which looks for a user by a login. Before continuing with the service, I need a mapper with Mapstruct to map the user entity to the user DTO. And finally I need a password encoder. This is very important to avoid storing plain passwords in my application. And now, I can create easily my service. I start by adding all the dependencies the repository. The password encoder. And the mapper. In the method, I first look by a user with the login. If I don't find it, I throw an exception. Then, I compare the given password with the password in the database. If it's correct, I map the user from the database to a user DTO. And if not, I throw another exception. Let's create the exception now. I want my application to intercept this custom exception and return the given message and the HTTP code. To do so, I will use an aspect, an advice. This time, I add the controller advice annotation. I map this method only to the app exception. And I return a Json body. In the method, I directly return a response entity with the given status and the body. I will create now the error DTO. Okay, I have my controller and my service to login the user. Still, I need to protect the rest of the endpoints and make the login endpoint public. Let's configure Spring Security. As always, I start creating the class. I add the necessary annotations. And create the security filter chain. For simplicity, I disable the CSRF. I indicate that I am in a stateless application. No need to handle the session. Make the login endpoint public. And the rest of the endpoints are protected via the Authentication. Let's test all of this. Great! My credentials are sent. But as I have my database empty, I have no user to compare the credentials. I need to create the register endpoint and the register form. I will create two buttons at the top to switch between the login form and the register form. I put each of them in a list item. Depending on the active value, I use different classes. And I bind the Click action to a method to update the active value. And now, the tab content. The login form, which is already created. And I will create another form with the register form. And now the register form. In the register form, I have the first name field. The last name. The login and the password. It doesn't work as I don't have the methods created. Let's create them in the typescript file. I first add the new fields to switch between the tabs and for the register form. This is the method to switch between the forms. And this is the method which is used when I submit the register form. Here it is. Let's now make the register request. Let's go to the backend and create the register endpoint. In the already created controller, I add a new endpoint, register. Which returns a user DTO as before. But as input, I have a sign up DTO, a new DTO. I will create it later. I call the register method from the service. I will create it later too. And return a created response which contains the URL of the created entity. And the body. Let's go now with the DTO. And the register method in the service. I return an exception if the user is already in the database. Otherwise, map the DTO into the entity. Encode the password. And save the entity. Let's now create the method in the mapper. I ignore the password because it has different format. It has a char array in in the sign up DTO and a string in the user object. Let's register now my user. I see now the 201 code returned when registering a new user. And I see the 200 code when correctly logging the user. As said at the beginning, those requests are validated because they contain the correct credentials. Still, I need to validate the following requests without the need to send the credentials again. For that, I will use the JWT. Let's create it in the backend. I start by creating a provider class which will create and validate the JWT. As said at the beginning, only the backend can encode and decode the JWT. And for that, it needs a secret key. This is to avoid having the raw secret key available in the JVM. I will need two methods: one to create the token and the other to validate it. Let's add the JWT dependency in the pom XML. Inside the JWT, I store the login. The created date. And a validity date. This corresponds to one hour. I will also add some custom claims, as the first name, and the last name. And now the method to validate, to decode the token. And I use the information in the JWT to create a user DTO, with the login, the first name and the last name. Let's use the first method in the controller, to return the new token. Let's now create an HTTP filter to intercept the incoming requests and validate the JWT. I first check if there is an authorization header. If it has two elements. And the first part is a Bearer. Then validate the token, which is the second part. And at the end, continue the filter chain. Let's now use this filter into Security Config. I place my filter before the basic authentication filter, because I want it to be the main authentication filter. I have now a freshly created token after the login. I will now add some logic in the frontend to switch to the authenticated component after the login is done. I also want to display the welcome message by default and switch to the login form if needed. For that, I will create a new component with some buttons. I add the buttons component in the content component with two events: the login to display the login form; and logout to come back to the welcome content. I bind those events to a method. And now display the welcome component if the variable is equal to welcome. Display the login component if the variable is equal to login. And display the auth component if the variable is equal to messages. I need now to create the variable and the method. Let's create now the output events. And now edit the HTML. The first button. And the logout button. Let's wrap all of these to center them. And bind the click event. Now I need to save the token in the frontend for the following requests. I will create two methods in the Axios service to manage the local storage access. And the second one to add the token in the local storage. If the received value is null, I remove the existing token. And now I add the authorization header in the request if the token is present in the local storage. And in the response of the login and register endpoints, I save back the token. Let's login with my username and password. But as I didn't register first the user is unknown. So, let's register. I see the register endpoint which returns me a 201 code. With my user and the token. The token is now used in the headers of the messages endpoint which returns me the protected content. To speed up the response time to the client, I've decided to always trust the JWT. But when the action requires some changes in the database, I should make a stronger validation, as checking if the users still exist in the database. In the HTTP filter, I first check the HTTP verb. If it's a get, I continue as before. Otherwise, if it's a put or delete, I use a stronger validation. The stronger validation starts as before. But I will also check in the repository the existence of the user. And then, as before, return an authentication bean. This is the way I develop fullstack features. I first try something in the frontend, then create the endpoint and test it in the backend. Test all together and go to the next step. When developing fullstack features you must go from the frontend to the backend all the time. Testing all the time small changes to make sure nothing is broken. All of this must be accompanied, of course, by unit tests. But this is out of the scope of this video. I hope this video was useful for you. Save the video to watch it again later. And share it to your network. And of course, click on the like button, subscribe to my channel, and see you soon. Bye!
Info
Channel: The Dev World - by Sergio Lema
Views: 23,236
Rating: undefined out of 5
Keywords: java, spring boot, spring security, spring security jwt, spring boot and angular
Id: YUqi1IjLX8I
Channel Id: undefined
Length: 60min 36sec (3636 seconds)
Published: Mon Jun 26 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.