Sitemap

codeburst

Bursts of code to power through your day. Web Development articles, tutorials, and news.

JWT Authentication in Angular

Let’s build an Angular app with JWT authentication.

6 min readJul 29, 2020

--

Press enter or click to view image in full size

In my last article, JWT Auth in ASP.NET Core, we talked about the implementation of JWT in the back-end. To follow up, this article will focus on the front-end part of the JWT story. You can find the front-end source code from the same GitHub repository as the back-end part.

To make JWT authentication work, the front-end application at least operates in the following scenes:

  1. Displays a login form, and sends user credentials to the back-end service to get user’s claims, a JWT access token, and a refresh token.
  2. Stores the JWT access token and refresh token in a browser’s localStorage, so that the application in different browser tabs can use the same tokens.
  3. Adds an authorization header when sending HTTP requests.
  4. Tracks the expiration time of the access token and sends a request to refresh tokens when the access token is about to expire.
  5. Removes the tokens from localStorage when the user logs out.

Today, we will build a simple app using Angular. We will implement an AuthService class to handle login, logout, and refresh token processes, as well as operations for localStorage key-value pairs. We will create a JwtInterceptor class to add a JWT Bearer token to the HTTP request headers, and an UnauthorizedInterceptor class to redirect the user to the login page if an HTTP status code 401 is received. We will use an AuthGuard to prevent unauthenticated users from visiting the application pages.

If you pull the code from my GitHub repository, then you can run the demo application on Linux Docker containers. Or, if you want, you can run the Angular app and the ASP.NET Core app separately. The following screen recording shows a demo of this app.

Press enter or click to view image in full size

Now let’s dive into the code.

auth.service.ts

Let’s use Angular CLI to generate an auth.service.ts file. I find that this AuthService class is a little bit lengthy, so I have decided to first paste the skeleton of the AuthService class, then I will explain its methods.

In the code above, the first three lines are the default decorator for an Angular service, which means that the service will be available globally for dependency injection.

Line 5 sets the back-end API URL, which is configured in the environment.ts file and it will talk to the Account controller in the ASP.NET Core web API project.

Line 6 refers to a private field timer, which is used in two private methods startTokenTimer() and stopTokenTimer(). The startTokenTimer() method is called inside the login() method and the refreshToken() method. We use rxjs observables to track the access token’s lifetime, so that when the token is about to expire, the timer will trigger the refreshToken() method to exchange a new set of tokens. On the other hand, when the logout() method is called, the stopTokenTimer() method will halt the timer.

Lines 7 and 8 show a common pattern in Angular for sharing the user’s state. The user$ observable can be broadcast to all observers in services, components, guards, interceptors, and so on. Note that I don’t save the user’s state in the browser localStorage, so that end-users are not able to modify its value. Using the user$ observable can guarantee an immutable user state, which may contain information about user permissions and other claims.

Lines 10 to 20 are optional touch-ups. We define a storageEventListener which will watch the value change events in the browser’s localStorage when the application starts, and the event listener will be removed when the application terminates. We need this global event listener because we want all browser tabs to sync with the user’s information upon login and logout events. As a result, if a user logs in the app from a browser tab, then the other tabs will also reflect the login status. Similarly, if a user logs out of the app from a tab, then all other tabs will be logged out as well. Most of online articles or tutorials miss this feature.

The following code snippet shows some more implementation details.

The storageEventListener (lines 1 to 10) monitors the value changes for the login-event and the logout-event which are dispatched in the login() and logout() methods, respectively. When a user logs out, then other tabs will have a null user, which could invalidate those sessions. When a user logs in, then other tabs will reload their current pages which are bonded with new session parameters. Line 19 is an example for dispatching the login-event value change events.

The login() method takes the username and password, and passes them to the login API endpoint. Upon a successful login, line 17 emits a new value to the _user subject, so that all observers will get its latest value. Line 18 saves the access token and the refresh token in a browser’s localStorage, so that the tokens can be shared across browser tabs or windows.

Line 20 executes the startTokenTimer() method, which starts a timer to count down. The getTokenRemainingTime() method computes the access token’s expiration time by decoding the access token. The timer runs until the JWT access token is about to expire, then the timer calls the refreshToken() method to refresh the tokens. If your app is a highly sensitive website, you may want to stop refresh tokens after certain amount of times, then you can create another key-value pair in localStorage to track it.

I will omit the code for logout() and refreshToken() methods for simplicity. Let’s move on to the AuthGuard.

auth.guard.ts

We use Angular CLI to generate a guard which controls the access of desired routes. In this demo app, we implement the canActivate method which listens to the user$ observable in the AuthService class, so that if the user$ observable emits a null value, then route navigation will end up at the login page. The following code snippet shows an example implementation of the AuthGuard class.

Then in the app-routing.module.ts file, we can protect some routes using the canActivate lifecycle hook like below.

jwt.interceptor.ts and unauthorized.interceptor.ts

We need an HTTP interceptor to add an authorization header, so that all requests sent to the back-end API endpoints will have the access token for identity purposes. Angular CLI can easily generate the interceptor’s skeleton for us. We simply need to clone the original HTTP request, and attach the Bearer token to the Authorization header. The following code snippet shows an example implementation of the JwtInterceptor class.

If a request returns an HTTP status code 401, then it means the current user’s identity is no longer permitted to the resource, so we should redirect the user to the login page. We can use another HttpInterceptor to deal with the 401 responses. An example UnauthorizedInterceptor class is shown below.

In a production-ready app, we may need to implement another service to gracefully handle all errors. We will not discuss that in this article.

app-initializer.ts and core.module.ts

It is a good practice to refresh tokens when the app is first loaded in a browser tab, in order to improve user experience. To do that, we write an appInitializer function like below.

The appIntializer, JwtInterceptor, and UnauthorizedInterceptor are registered in an Angular module as follows.

Finally, we can import the CoreModule into the AppModule, so that the three providers above can work globally.

I think I have touched all the bases for implementing our Angular app. We can try out the app using the ng serve command in Angular CLI, after we start the ASP.NET Core web API app. Please also try the app in two or more browser tabs and play with the login/logout functionalities.

Serve the Angular App with NGINX on Docker

I also include a Dockerfile for the Angular app, so that it can be served by an NGINX server in a Docker container. I have written another article, Get Started with NGINX on Docker, which talks about the configurations of Docker and NGINX, so I won’t repeat them here.

To echo the beginning of this article, we can also run the app using Docker Compose, so that both the back-end app and the front-end app can run simultaneously.

Conclusion

That’s all for today. We have implemented an Angular app with JWT authentication, and you can play with it on multiple browser tabs/windows. Again, the complete solution is in my GitHub repository. Hope it helps. Thanks for reading.

--

--

codeburst
codeburst

Published in codeburst

Bursts of code to power through your day. Web Development articles, tutorials, and news.

Changhui Xu
Changhui Xu

Written by Changhui Xu

Lead Application Developer. MBA. I write blogs about .NET, Angular, JavaScript/TypeScript, Docker, AWS, DDD, and many others.

Responses (5)

In my own experience I've found using timers to be problematic when multiple tabs are open. Also, localStorage was problematic when the user simply closed the browser instead of logging out. I've moved to checking for a valid token on every api call…

--

Excellent article, thanks!
Can you explain this line please
this.http.get<LoginResult>(`${this.apiUrl}/user`).subscribe((x) => {
What is the logic of this endpoint, i undestand that the method storageEventListener is for work with multiple tabs... but a not know how get the user in that endpoint without params.

--

Nice article! I am refactoring my authentication flow based on this article. I was wondering; in de login method you seem to set the User twice. Once in the login itself, and then it throws the login-event through the local file system, which sets the user again. Why?

--