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!