Angular

Best Practices for JWT Authentication in Angular Apps

Data without security is like a treasure box without a lock. In this tech-driven world, hackers are everywhere. To transmit your data’s securely, you need highly reliable standards. Considering this, JSON Web Tokens (JWT) provide the best security and authentication.

Angular is a widely used JavaScript platform. In this blog, we are going to see how to implement authenticated routings in Angular, manage tokens, and pass tokens to servers in client side. For logins, you can use any kind of authentication like OpenID, OAuth, or create your own login application logic.

This blog covers the following topics:

  • What is JWT?
  • What are the possibilities of storing JWT authentication tokens in Angular apps?
  • Where can tokens be stored securely in Angular apps?
  • How to create a service to access JWT tokens and storage?
  • How to protect Angular routing with stored JWT tokens?
  • How to pass a JWT token for every API request?

What is JWT?

JSON Web Tokens (JWT) are an internet standard for creating JSON-based access tokens that assert a number of claims. We can generate JWT with custom claims that may contain user information and permission-based values, information like whether the user is an admin can also be stored in JWT. Best practice is to not store any confidential information in JWT. Please refer to jwt.io for detailed information about JWT.

Note: An Angular project can be created using Angular CLI commands. Please refer to the Angular CLI command documentation for more information on how to create a project.

Syncfusion Angular component suite is the only suite you will ever need to develop an Angular application faster.

What are the ways to store authentication tokens in Angular apps?

There are three possible ways of storing access tokens in an Angular app. They are:

In-memory storage

In this technique, a token is stored in the application page itself. The only drawback of this option is the data is not persistent; it is lost on page refresh and must be retrieved again.

HTML5 web storage (local storage and session storage)

In this technique, data is stored in browser storage. Data stored this way is accessible by all tabs in the same browser within the same domain. The two types of web storage are:

  • Session storage: Data stored in session expires once the browser is closed.
  • Local storage: Data stored in local storage doesn’t expire until we clear the data from the browser. The server cannot directly access data stored in local storage.

In this technique, a token is stored in cookies. Data stored this way can be accessed by the server. The browser automatically appends a cookie in requests sent to the server. Since the browser automatically adds a cookie on each request, tokens are vulnerable to CSRF/XSRF attacks.

Find the right property to fit your requirement by exploring the complete documentation for Syncfusion’s Angular components.

Where can tokens be stored securely in Angular apps?

We can store data in different ways, but we should take proper measures to protect tokens against CSRF and XSRF vulnerabilities. We should store tokens in a place that is not accessible by attackers. Two possible ways of storing tokens to reduce risk of CSRF/XSRF attack are:

  • Local storage: One of the best ways to store data. Local storage is not vulnerable to CSRF attacks.
  • HttpOnly cookie: HttpOnly cookies are not accessible on the client side, i.e. the client cannot read data stored in these cookies.

For additional security, we must consider a few more things on the server side, such as:

  • Token expiration validation.
  • Content security policy.
  • Refresh token mechanism.
  • Anti-forgery token mechanism.

How to create a service to access JWT tokens and storage

Now that we have learned where to store tokens, let’s see how to create an Angular service to decode stored tokens and retrieve values from them in an Angular app.

JWT token service

This service is used for decoding JWT tokens and retrieving values from JWT. Let’s set one up.

First, create an Angular service file for JWT decode and inject it in the application module.

We can use the jwt-decode package for decoding JWT tokens. In this service, functions for getting user claim values like username and email ID have been included. A function has also been added for checking token expiration in this service.

import { Injectable } from '@angular/core';
import * as jwt_decode from 'jwt-decode';

@Injectable()
export class JWTTokenService {

    jwtToken: string;
    decodedToken: { [key: string]: string };

    constructor() {
    }

    setToken(token: string) {
      if (token) {
        this.jwtToken = token;
      }
    }

    decodeToken() {
      if (this.jwtToken) {
      this.decodedToken = jwt_decode(this.jwtToken);
      }
    }

    getDecodeToken() {
      return jwt_decode(this.jwtToken);
    }

    getUser() {
      this.decodeToken();
      return this.decodedToken ? this.decodedToken.displayname : null;
    }

    getEmailId() {
      this.decodeToken();
      return this.decodedToken ? this.decodedToken.email : null;
    }

    getExpiryTime() {
      this.decodeToken();
      return this.decodedToken ? this.decodedToken.exp : null;
    }

    isTokenExpired(): boolean {
      const expiryTime: number = this.getExpiryTime();
      if (expiryTime) {
        return ((1000 * expiryTime) - (new Date()).getTime()) < 5000;
      } else {
        return false;
      }
    }
}

To store the token, you can use either a cookie or local storage service. Code examples for implementing the services are provided below. Depending on where you are storing tokens, cookie or local storage service can be implemented.

Be amazed exploring what kind of application you can develop using Syncfusion Angular components.

Cookie service

  1. Create an Angular service file AppCookieService and inject it in the application module.
  2. Include the get, set, and remove functions to perform cookie operations.
  3. Include the functionality to automatically add the cookie on initialization of the service.
import { Inject, Injectable } from '@angular/core';

@Injectable({
    providedIn: 'root',
  })
export class AppCookieService {
    private cookieStore = {};

    constructor() {
        this.parseCookies(document.cookie);
    }

    public parseCookies(cookies = document.cookie) {
        this.cookieStore = {};
        if (!!cookies === false) { return; }
        const cookiesArr = cookies.split(';');
        for (const cookie of cookiesArr) {
            const cookieArr = cookie.split('=');
            this.cookieStore[cookieArr[0].trim()] = cookieArr[1];
        }
    }

    get(key: string) {
        this.parseCookies();
        return !!this.cookieStore[key] ? this.cookieStore[key] : null;
    }

    remove(key: string) {
      document.cookie = `${key} = ; expires=Thu, 1 jan 1990 12:00:00 UTC; path=/`;
    }

    set(key: string, value: string) {
        document.cookie = key + '=' + (value || '');
    }
}

Local storage service

  1. Create an Angular service file LocalStorageService and inject it in the application module.
  2. In this basic local storage, add get, set, and remove functions for performing the operations.
import { Injectable } from '@angular/core';

@Injectable()
export class LocalStorageService {

    set(key: string, value: string) {
        localStorage.setItem(key, value);
    }

    get(key: string) {
        return localStorage.getItem(key);
    }

    remove(key: string) {
        localStorage.removeItem(key);
    }
}

Syncfusion Angular components are:

  • Lightweight
  • Modular
  • High-performing

How to protect Angular routing with stored JWT tokens

So far, we have learned about JWT tokens, where to store them, and how to access and pass them to the server using Angular services. Now, we are going to see how to use these tokens to protect Angular routing.

Before diving into this topic, we have to understand the routing guard canActivate.

{
    path: 'app',
    component: AppComponent,
    canActivate: [AuthorizeGuard]
}

The above routing configuration is protected by the AuthorizeGuard service.
AuthorizeGuard supports ‘Boolean | Promise | Observable’. If the AuthorizeGuard returns false/Promise reject/Observable error, then routing won’t happen. We need to resolve the error for the routing configuration to be successful.

AuthorizeGuard service

The guard service in the sample code below checks whether the user is logged in. If the user is logged in, then it checks whether the token is expired. If both cases are satisfied, then the routing process will be successful. Otherwise, it will request the login service to get a new token or redirect the user to a custom login or access denied page.

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs'; 
import { JWTTokenService } from './jwt-token.service';
import { LocalStorageService } from './storage.service';
import { LoginService } from './login.service';

@Injectable({
  providedIn: 'root'
})
export class AuthorizeGuard implements CanActivate {
  constructor(private loginService: LoginService,
              private authStorageService: LocalStorageService,
              private jwtService: JWTTokenService) {
  }
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable | Promise | boolean {
      if (this.jwtService.getUser()) {
          if (this.jwtService.isTokenExpired()) {
            // Should Redirect Sig-In Page
          } else {
            return true;
          }
      } else {
        return new Promise((resolve) => {
          this.loginService.signIncallBack().then((e) => {
             resolve(true);
          }).catch((e) => {
            // Should Redirect Sign-In Page
          });
        });
      }
  }
}

Note: In the above code, logInService is used for login resolution purposes only. You can create your own service to resolve logins.

How to pass a JWT token for every API request

In order to have authenticated calls with APIs, we have to send the authorization token in every HTTP request being sent to the server so that the server can verify authentication of the request. It’s difficult to include code to add a token in every place an API call is made—it causes code duplication. To remedy this, Angular has an interceptor service for handling all HTTP requests and responses in a single place. By using this interceptor, we can handle including an authentication token in every API call globally.

How the interceptor works

HttpInterceptor controls all the HTTP requests and responses. Every request and response comes and goes through this service, so we can easily append custom headers containing authorization tokens in a single place.

Override HTTP_INTERCEPTORS as shown in the following app module.

{ provide: HTTP_INTERCEPTORS, useClass: UniversalAppInterceptor, multi: true },

HttpInterceptor service

The interceptor intercepts all Angular HTTP requests and adds authorization headers with the token.

Process of using authorization headers with a token
import { Injectable, Inject, Optional } from ‘@angular/core’;
import { HttpInterceptor, HttpHandler, HttpRequest } from ‘@angular/common/http’;
import { AuthService } from ‘./auth.service’;
@Injectable()
export class UniversalAppInterceptor implements HttpInterceptor {

  constructor( private authService: AuthService) { }

  intercept(req: HttpRequest, next: HttpHandler) {
    const token = this.authService.getJWTToken();
    req = req.clone({
      url:  req.url,
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });
    return next.handle(req);
  }
}

Note: In the above code, AuthService is used for JWT token retrieval purposes only.

See the possibilities for yourself with live demos of Syncfusion Angular components.

Conclusion

In this blog, I have explained the best practices for authentication in Angular apps using JWT tokens and the management of JWT tokens on the client side.

For Angular developers, Syncfusion offers over 65 high-performance, lightweight, modular, and responsive Angular components to speed up development.

For current customers, the latest version of our controls is available for download from the License and Downloads page. If you are not yet a customer, you can try our 30-day free trial to check out all our Angular components have to offer. You can also explore samples in our GitHub repository.

Relate blogs

Pradeep Kumar

Pradeep Kumar works as a product manager at Syncfusion, where he specializes in the development of web components with cutting-edge technologies. He is interested in the latest web technologies and provides solutions for great products.