Search code examples
ionic4angular8

Ionic 4, Angular 8 and HTTP interceptor


I'm building mobile app with Ionic 4 and Angular 8 and can't make my HTTP interceptor working. I reviewed all the examples of interceptors here but none fits my need or simply do not work any more.

The only difference against the regular Angular 8 version is the first line where token is read from the storage. Original Angular 8 code reads such things synchronously and doesn't need subscription thus it works. This one here is Ionic storage which calls local resources in async way.

Here is my code:

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  from(this.storage.get('id_token')).subscribe(res => {
    const idToken = res;
    if (idToken) {
      const cloned = req.clone({ headers: req.headers.set('token', idToken)});
      return next.handle(cloned);
    } else {
      console.log('Unauthorized calls are redirected to login page');
      return next.handle(req).pipe(
        tap(
          event => {
            // logging the http response to browser's console in case of a success
            if (event instanceof HttpResponse) {
              // console.log('api call success :', event);
            }
          },
          error => {
            // logging the http response to browser's console in case of a failure
            if (error instanceof HttpErrorResponse) {
              if (error.status === 401) {
                this.router.navigateByUrl('/');
              }
            }
          }
        )
      );
    }
  });
}

In this shape it compiles but my IDE reports: TS2355 (function has to return a value). What is wrong or missing here ? I can't figure it out.


Solution

  • Ok, so it looks like you're trying to do 2 things in 1 interceptor:

    • add the Bearer token
    • if error code is 401 - redirect to home page

    Also, you'll access the storage at EVERY request which is expensive.

    Here's what I did:

    • Create an auth service and manage the token there
    • In auth service have a BehaviourSubject that stores the token's last value

    And here it goes:

    // JWT interceptor
    import { Injectable } from '@angular/core';
    import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
    import { Observable } from 'rxjs';
    
    import { AuthenticationService } from '../services/authentication.service';
    
    
    @Injectable()
    export class JwtInterceptor implements HttpInterceptor {
        constructor(private authenticationService: AuthenticationService) {}
    
        intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
            // add authorization header with jwt token if available
            const currentAuthToken = this.authenticationService.currentAuthTokenValue;
            if (currentAuthToken && currentAuthToken.token) {
                const headers = {
                    'Authorization': `Bearer ${currentAuthToken.token}`,
                };
                if (request.responseType === 'json') {
                    headers['Content-Type'] = 'application/json';
                }
                request = request.clone({
                    setHeaders: headers
                });
            }
    
            return next.handle(request);
        }
    }
    

    authenticationService.currentAuthTokenValue is just a getter that returns current subject's value

    public get currentAuthTokenValue(): AuthToken {
        return this.currentAuthTokenSubject.value;
    }
    

    Also another interceptor for error:

    // Error interceptor
    import { Injectable } from '@angular/core';
    import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
    import { Observable, throwError } from 'rxjs';
    import { catchError } from 'rxjs/operators';
    
    import { AuthenticationService } from '../services/authentication.service';
    
    @Injectable()
    export class ErrorInterceptor implements HttpInterceptor {
        constructor(private authenticationService: AuthenticationService) {}
    
        intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
            return next.handle(request).pipe(catchError(err => {
                if (err.status === 401) {
                    // auto logout if 401 response returned from api
                    this.authenticationService.logout().then(() => {
                        location.reload();
                    });
                }
    
                const error = err.error.message || err.error.detail || err.statusText;
                return throwError(error);
            }));
        }
    }
    

    Hope it helps.