There are several issues that need to be covered when you want to secure a frontend module of a Spring Boot and Angular app. Learn how to handle security based on JWT and enhance user experience with the AuthGuard functionality.
What we are going to build
In the Securing your Spring Boot and Angular app with JWT #1 – Introduction post you can find the description of the secured multi-module application which we are going to create.
In the Securing your Spring Boot and Angular app with JWT #2 – Backend post you can find the details of safeguarding the backend module.
The finished project is available in the GitHub repository – little-pinecone/jwt-spring-boot-angular-scaffolding.
In this post we are focusing on the frontend module.
|To keep the code snippets reasonably short in this post, I don’t include the
You can see the final directory tree for the frontend module on the image below:
Start the Angular app
Let’s start the frontend module with the following command:
The landing page displays only the link to the
Cookie dispenser page, where the cookies received from the API should be listed:
Clicking on the
Cookie dispenser link triggers the
The request for cookies was send to the API, but the backend module expects a valid JWT token to grant access to the secured resources. So far, everything works as expected – the API security blocks unauthorized calls to protected endpoints.
|We need to apply login functionality, obtain a valid token and attach it to every API call. Furthermore, we have to guard routes that shouldn’t be even reached without the token.|
Secure the frontend
Handle the login form submission
At this moment, the application serves a simple login page with a dummy form. Whatever we insert into the
password inputs won’t be processed:
We need to bind the credentials provided by users to the data model as in any ordinary Angular form.
Add data model
credentials.ts file with the following class:
login.component.ts file import the
Credentials class and instantiate the object with empty
Update the form template
Bind both inputs with
[(ngModel)] and provide validation messages. Below is the code for the
Then copy the following code for the
Login button until the form is completed correctly. You also want to call the
login() function when the form is submitted, therefore add the following code to the view:
We have to add the
login() function to the
At this moment the method is empty, we will implement the login functionality later.
As a result, the form submission is turned off until the credentials are provided. In the screenshot below you can see that removing the
username value produces the validation message for that field:
The work done in this section is contained in the commit c9be87c3acae01148703bbcd449026eb90bccd79.
Send a login request
We need to call the API to authenticate a user who tries to log in. In case of a successful authentication, the backend sends the
Authorization header containing the JWT token that we need to extract, store and append to every subsequent API call.
Create the token service with the following command:
We will use it to call the API
login endpoint. Copy the following code to the service:
apiUrl value is already defined in the
|Adjust the apiUrl value to your development and production configuration.|
Obtain the token
We created the service that sends the login requests. In the next step we are going to provide the logic for extracting the right header from the response.
Create the authorization service:
We are going to define the key that will be used for storing the JWT token in the fronted module. What’s more, after users are authenticated, we want to redirect them from the
'/login' path. The
redirectToUrl variable is set to
'/cookies' by default, but will carry any path that user wanted to reach before logging in to the application. Of course, we need to inject the
TokenService to call the API and
Router to handle redirections.
Copy the following code to the service:
Furthermore, we are going to add the core responsibility – the
login() function. It calls the
getResponseHeaders from the
TokenService and saves the token from the
Authorization header in the
LocalStorage. Add the following code to the
We also need a method to reach for the currently stored token, so add this last function to the file:
The authorization service is ready to be used in the app.
Implement the login function
Let’s get back to our empty
login() function in the
login.component.ts file. We are going to inject the
AuthService to the constructor so we can use it inside the
login() method :
After those changes, submitting the login form with legitimate credentials will result in receiving a valid token from the API that will be stored in the
LocalStorage. A user is authenticated in the app and is automatically redirected to the
"/cookies" page. However, the token is not added to this request – instead of the page with tasty treats we are getting the
401 error. We are going to deal with this issue in the next section.
The work done in this section is contained in the commit 71b13383b4b77819234b854e97fec419c60bc450.
Append the token to every API request
We need to intercept every request to the API and add the token from the
LocalStorage. Create the
jwt.token.interceptor.ts file and copy the following code to it:
We are injecting the
AuthService to access the
getToken() function. Then, we are appending the Bearer token to any request send to the API.
We have to declare usage of this interceptor in
Refresh the page and you will see the cookies served by the API.
The work done in this section is contained in the commit e561ff512771a06404cda39e293432429a691c5e.
Add logout functionality
The API security configuration allows us to call the logout endpoint:
Let’s use it and add the
logout() function to the
After a successful API call we can remove the token from the
LocalStorage. We will also need a function to check the current state of a user. Copy both methods to your
To display the
Logout option in the application header we need to inject the
AuthService into the
HeaderComponent and implement the
We are going to display the
Logout option depending of the current state of a user returned by the
We need to declare the
hidden class in the file for the header styles:
The header on your login page should look like the one on the screenshot below:
The work done in this section is contained in the commit bc5a317d7606e8ae56b4df4580f8988e1c3ef1d4.
Our API will return
401 error to the web console when unauthorized users try to access any secured route. That’s not a user friendly solution. We should redirect those users to the login page instead of sending requests without tokens. We are going to add AuthGuard to enhance the usability of our application.
Generate the guard with the following command:
The guard will use
AuthService to verify if users are logged in and
Router to redirect them to the login form if they are not. Let’s inject them to the constructor:
We will replace the default code in the
canActivate() function with the following lines:
We are declaring the
url variable which holds the path that the user tried to access. Depending on the boolean returned by the
checkLogin() method the access to that path will be granted or not. Copy the function to the file:
The last thing that needs to be done is to declare paths that are guarded. Add thepart to your routing module:
The page available for the authenticated and authorized users should look like the one on the screenshot below:
The work done in this section is contained in the commit f9d82ce0c3c4ee26b058c374f999df3f360a0d65.
Handle 401 errors returned from the backend
Right now, when an unauthorised user tries to reach the protected
/cookies route, the Angular AuthGuard blocks sending a request to the backend and the user is redirected to the
/login page. However, when we have a route that is not guarded explicitly by Angular, the request will be sent to the backend and the user will receive a
In our example we don’t deal with expired tokens. Therefore, if the token is no longer valid and the user wants to log out, the unauthorised request will be sent to the backend. Right now the frontend does not handle properly the API response shown in the screenshot below:
We can manage this situation by adding error handling in the interceptor:
We created the
handleErrors function that redirects users to the
/login page when the API returns
401 error and we use it when the intercepted request is returned. To achieve this we had to inject the
Router instance in the constructor.
The work done in this section is contained in the commit e26dbc7649a0bb48bbf0f2f66ae32179490d120a.