Security is an important aspect of a system. Authentication belong to one of them.

Authentication can be implemented in web applications in multiple ways like session-based, cookie-based, token-based, etc. Our main focus would be looking at token-based authentication.

In token-based authentication, basically user login in into the system and server send acces_token and refresh_token with its expiry if the user is validated.

This part is simple, a user enters into a web application. After a user gets logged in, each API call has to be validated to maintain the data security of an application. We have to send access_token with each request.

On server-side API, it extracts access_token from the request, verifies (inside Identity Server) if it is valid or not based on its expiry. And return a data if a token is not expired, or return 401 status if access_token is expired.

We created services dedicated to making ajax calls. For authorization purposes, we’ve to ensure Auth Header is getting passed with each request.

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { map } from 'rxjs/operators';

@Injectable()
export class AppService {

  constructor(private http: HttpClient) { }

  getCountries() {
    const headers = new HttpHeaders();
    headers.append('Authorization', `Bearer ${token}`);
    return this.http.get('/api/country', { headers });
  }
}

for every Service (where we need Authorization), we will have to repeat the logic : It mean , ensure that Auth Header is getting passed with each request.  This would violate the DRY (Don’t Repeat Yourself). We should find a better way to do this, maybe something that could help us to intercept the http request before/after it makes a call to Server.

HttpInterceptoris useful here to solve this problem. Basically it allows us to intercept http request / response and give access to manipulate it. Hence our generic logic to add header in the request can be kept here. Basic TokenInterceptor code may look like below.

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor
} from '@angular/common/http';
import { AuthService } from './auth/auth.service';
import { Observable } from 'rxjs';

@Injectable()
export class AppHttpInterceptor implements HttpInterceptor {
  // AuthService is holding 
  constructor(public auth: AuthService) {}
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = window.localStorage.get('token');
    request = request.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });
    return next.handle(request);
  }
}

In the above snippet, we have implements AppHttpInterceptorService class from HttpInterceptor a method. Then implemented an intercept method that provides control to modify the request/response. Thus we have kept adding a token to header part in there.

Extending HTTP_INTECEPTORS provider is needed to take AppHttpInterceptorService in action. This can be registered inside your app.module.ts

import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AppHttpInterceptor } from './AppHttpInterceptor';
@NgModule({
  bootstrap: [AppComponent],
  imports: [...],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AppHttpInterceptor,
      multi: true
    }
  ]
})
export class AppModule {}

Ideally AppHttpInterceptor should take care of the below things :

  1. Add Authorization the header on each request
  2. Allow requests which don’t require token logic at all.
  3. If the token expires, then it automatically refreshes the token.

Generally access_token have 60 minutes expiry for security reasons. After 60 minutes token access_tokenbecome invalid and API could return a response with 401 status. To handle this case we’ve to make a new token using refresh_token and use that token for further API calls.

import { AuthService } from './auth.service';

import { Injectable } from '@angular/core';
import {
  HttpRequest, HttpHandler, HttpInterceptor, HttpSentEvent,
  HttpHeaderResponse, HttpProgressEvent, HttpResponse, HttpUserEvent
} from '@angular/common/http';
import { Observable, BehaviorSubject, throwError} from 'rxjs';
import { catchError, switchMap, finalize, filter, take, map } from 'rxjs/operators';
import { Router } from '@angular/router';
import { JwtHelperService } from '@auth0/angular-jwt';

const helper = new JwtHelperService();
// You can add URL which don't need AUTH header
const whiteListUrls = ['login', 'refreshToken'];

@Injectable()
export class CustomHttpInterceptorService implements HttpInterceptor {
  constructor(
    private auth: AuthService,
    private router: Router) {}

    // Check expiry of token, first decode token
    // extract data, and verify expiry timing wrt currentTime
    // should be less that currentTime
    private isTokenExpired(token): boolean {
      const decoded = token && helper.decodeToken(token);
      const date = new Date().getTime();
      return decoded && new Date(decoded.exp * 1000).getTime() <= date;
    }

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent |
      HttpResponse<any> | HttpUserEvent<any> | any> {
      const token = localStorage.get('access_token');
      const refresh_token = localStorage.get('access_token');
      const isExpired = this.isTokenExpired(token);
      // Directly allowed whitelisted URL's
      if (whiteListUrls.find(w => request.url.includes(w)) || !isExpired) {
        return next.handle(request);
      }
      // If accessToken is expired
      if (isExpired) {
        // Retreive new refresh_token
        const refreshToken = this.auth.refreshToken(refresh_token).pipe(
          // Applying catchError only to refreshToken call
          catchError(_ => {
            // Logout if refresh call fails
            return this.logout() as any;
          }),
          switchMap((user: any) => {
            // when new user token retrieved
            if (user) {
              // Update token locally
              this.auth.updateTokens(user);
              // Make the ajax call after by passing `accessToken`
              return next.handle(this.addTokenToRequest(request, user.accessToken));
            }
            // Log out if there is no user
            return this.logout() as any;
          })
        );
        return refreshToken as any;
      } else {
        // Make normal ajax call just by passing token
        return next.handle(this.addTokenToRequest(request, token));
      }
    }

    private addTokenToRequest(request: HttpRequest<any>, token: string): HttpRequest<any> {
      // Token appended in the request header.
      return request.clone({ setHeaders: { Authorization: `Bearer ${token}`}});
    }

    private logout() {
      this.auth.logout();
      this.router.navigate(['login']);
    }
}

By Shabazz

Software Engineer, MCSD, Web developer & Angular specialist

Leave a Reply

Your email address will not be published. Required fields are marked *