Utilize Angular 2 interceptor to incorporate HTTP requests

Dealing with the 401 response from an interceptor using the HttpClientModule in Angular and JWT authentication can be a bit tricky. When the accessToken expires, it's necessary to use the refreshToken to obtain a new one before making the actual API request. The goal is to pause the request, fetch a new token, and then proceed with the request using the updated accessToken.

What is the most effective way to handle HTTP requests from within the interceptor?

Here is an example of my interceptor:

@Injectable()

export class JwtService implements HttpInterceptor { 

 constructor(public inj: Injector){}

 intercept(req : HttpRequest<any>, next : HttpHandler) : Observable<HttpEvent<any>> {


    if ( this.shouldGenerateNewAccessToken(req.url) ){

        const auth = this.inj.get(AuthService);
        auth.getNewAccessToken().then( token => {

            if (token){

                const headers = { 'Authorization' : token };
                const clone = req.clone( { setHeaders : headers } );
                return next.handle(clone);

            } else { return next.handle(req); }

        }, error => {

             return next.handle(req); 

        });
    }
    else {

        if (APP_CONFIG['accessToken']){

            const headers = { 'Authorization' : APP_CONFIG['accessToken'] };
            const clone = req.clone( { setHeaders : headers });
            return next.handle(clone);

        } else { 

             return next.handle(req);

        }

    }

}


shouldGenerateNewAccessToken(url : string) : Boolean {

    let lastupdate = APP_CONFIG['accessTokenTimestamp'];
    let now = new Date().getTime(); 

    // Let say the token expires after 5s
    if ((now - lastupdate) > 5000 && APP_CONFIG['refreshToken'] && url != APP_CONFIG['apiEndPont']+'getaccesstoken'){
        return true;
    }
    else 
        return false;
}

Authentication logic

getNewAccessToken() : Promise<any>{

    return new Promise( (resolve, reject)=>{

        this.http.post(this.api+ 'getaccesstoken', JSON.stringify({refreshToken: APP_CONFIG['refreshToken'] }), { "headers" : this.headers } ).subscribe( data  => {
            let res : any = data;
            APP_CONFIG['accessToken'] = res.accessToken; 
            APP_CONFIG['accessTokenTimestamp'] = new Date().getTime();
            resolve(APP_CONFIG['accessToken']);

        }, err => {console.log('error'); reject(null); })
    });
}

getuserinfos(){

   return this.http.get(this.api+ 'getuserinfos', { "headers" : this.headers } ).subscribe( data  => {
      console.log('result getUserInfos =>', data);

  }, 
  ( err : HttpErrorResponse ) => { 

     if ( err.error instanceof Error ) { console.log('An error occurred requete login:', err.error.message); }
     else { 
         console.log('error => ', err)
    }

});

I encountered the following error when calling getUserInfos() with an expired token:

error => TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.

Could this issue be related to the described behavior?

On rare occasions, an interceptor may choose to entirely manage the request itself and generate a new event stream rather than invoking next.handle(). While this is fine, keep in mind that subsequent interceptors will be bypassed completely. It is also uncommon but permissible for an interceptor to yield multiple responses on the event stream for a single request. Source

Answer №1

After some experimentation, I successfully accomplished it using a new approach. Below is the code for my interceptor:

@Injectable()

export class JwtService implements HttpInterceptor { 

constructor(public inj: Injector){}

private fixUrl(url: string) {
    if (url.indexOf('http://') >= 0 || url.indexOf('https://') >= 0)
        return url;
    else
        return APP_CONFIG['apiEndPoint'] + url;
}


intercept(req : HttpRequest<any>, next : HttpHandler) : Observable<HttpEvent<any>> {

    // Clone request
    var headers = {}
    if (APP_CONFIG['accessToken']){
         headers = { 'Authorization' : APP_CONFIG['accessToken'] };
    }   
    const cloneRequest = req.clone( { setHeaders : headers });


    return next.handle(cloneRequest).do(data => {

        if (data instanceof HttpResponse) {
          // Additional logic 
        }

    })
    .catch((res)=> {

        if (res.status === 401 || res.status === 403) {

            if (APP_CONFIG['accessToken'])
            {    
                const auth = this.inj.get(AuthService);
                return auth.getUpdatedAccessToken().flatMap( token => {

                    // Clone the previous request
                    let clonedRequestRepeat = req.clone({
                        headers: req.headers.set('Authorization' ,  APP_CONFIG['accessToken'] ),
                        url: this.fixUrl(req.url)
                    });

                    // Resend request 
                    return next.handle(clonedRequestRepeat).do(event => {

                        if (event instanceof HttpResponse) {
                            console.log('Repeated response from server : ', event);
                        }
                    });

                });
            }else { return Observable.throw('Not authenticated'); }

        }
        else { // Not 401 
             return Observable.throw(res);
        }

    })

}


}

AuthService.ts

getUpdatedAccessToken()  : Observable<any>{

 return this.http.post(this.api+ 'getaccesstoken', JSON.stringify({refreshToken: APP_CONFIG['refreshToken'] }), { "headers" : this.headers } )
            .map((response: any) => {
                if (response.code == 0){
                    APP_CONFIG['accessToken'] = response.accessToken; 
                }
                return response
        })
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Making an HTTP request from Angular 6 to a Node.js server

While attempting to send an HTTP request from Angular to a Node.js server, I encountered the following error on the Angular side: "Access to XMLHttpRequest at 'http://localhost:5030/employees/save' from origin 'http://localhost:4200' h ...

Disable the functionality of the next and previous buttons for the Bootstrap carousel when clicking on the outer

Here is the code for the carousel: The next/prev button functions under its respective div, how can I stop it? When I click on the div below the carousel, the carousel continues to move as usual. Below the carousel div, there is another one with a tabbi ...

Calculate the number of parent nodes and their respective child nodes

I am curious about how I can determine the number of children nested within parent-child relationships. For example: const parent = document.querySelectorAll('.parent'); parent.forEach(el => { const ul = el.querySelector('.child3-chi ...

What is the best way to import a data type from another file into a `.d.ts` file without converting it into a module?

Recently encountered a peculiar scenario involving d.ts files and namespaces. I have some d.ts files where I define and merge a namespace called PROJECT. Take a look at how it's declared and automatically merged (across multiple files) below: file1 ...

I would like to know the best approach to utilizing a PHP array or JSON response within JavaScript

Let me present my workflow briefly. While I initiate an Ajax call from the index.phtml file, the Ajax response is as follows: Array ( [Data] => Array ( [0] => Array ( [PlayerId] => 20150 ...

Explore the possibilities of using a unique custom theme with next.js, less, and ant design

Trying to customize the default theme in antdesign has been a challenge for me. I've switched from sass to less, but there seems to be something that just won't work. I've exhaustively searched online for solutions - from official nextjs ex ...

What is the appropriate Typescript return type to use for a $http request that only returns a successful response with no content?

I recently developed a Typescript service: class SettingsService implements ISettingsService { public info = {}; public backupInfo = {}; public userConfig = {}; public isLoaded = false; constructor( private $http: ng.IHttpSer ...

Preview and crop your image before uploading

Currently, I am working on developing a form that will enable administrators to upload an image. The aim is to allow them to preview the image before uploading it, displaying it at a specific size and providing the option to click on the image to open an i ...

Error in parsing JSON: An unexpected token < was encountered at the beginning of the JSON input, causing a SyntaxError at position 0 when parsing with

I need to transfer an array from a PHP file to JavaScript and save it in a JavaScript array. Below is the snippet of JavaScript code: xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange=function(){ if (xmlhttp.readyState==4 ...

"When I use breakpoints and run my application in debugging mode, it performs flawlessly. However, without these tools, it

I have developed an application using the Ionic Framework with Firebase as the backend. When I run the application with breakpoints using the debugger, everything works fine. However, if I run it without the debugger, I notice that values are not being upd ...

Redux's 'connect' function fails to recognize changes in the state array

I've recently implemented redux with a reducer that handles an array of time slots for a specific date. Whenever the date is changed, the reducer successfully updates the state (confirmed through console logs in my mapStateToProps function). However, ...

Stop the loop in cypress

We have a certain situation as outlined below loop through all name elements on the webpage if(name.text() matches expName) { name.click() break out of the loop } else { createName() } How can I achieve this in Cypress? Using return false doesn't se ...

Importing BrowserAnimationsModule in the core module may lead to dysfunctional behavior

When restructuring a larger app, I divided it into modules such as feature modules, core module, and shared module. Utilizing Angular Material required me to import BrowserAnimationsModule, which I initially placed in the Shared Module. Everything function ...

What is the best way to declare strings within a Typescript interface?

I have an array of Projects with multiple strings in the stack property const projects: IProject[] = [ {name: '', description: '', stack: {'php', 'sql'}} ] What is the best approach for defining the interface? ...

Echo a JS variable into PHP and then insert a PHP file into an HTML element - a step-by-step guide!

Greetings to the wonderful community at stackoverflow, I am currently working on a variable code that allows for easy insertion of code from another file, such as a div. I am curious if it is possible to include a PHP file using JavaScript and JS variable ...

conceal a div in Angular when the user is authenticated

One of my tasks involves managing the visibility of a div based on whether the user is logged in. This functionality is achieved by utilizing an authentication service in Angular and tokens from Django. Component.html <a *ngIf="authService.isLoggedIn( ...

Exploring the process of connecting to an additional database with Angular Fire

I came across this code snippet: import { FirebaseApp } from "@angular/fire/compat"; import { AngularFirestore } from "@angular/fire/compat//firestore"; import { initializeFirestore } from "@angular/fire/firestore"; impo ...

Converting Mat-Raised-Button into an HTML link for PDF in Angular 6 using Material Design Library

Greetings, I have a couple of interrelated inquiries: Upon clicking the code snippet below, why does <a mat-raised-button href="../pdfs/test.pdf"></a> change the URL (refer to image 4) instead of opening test.pdf in a new window? If I have a ...

Mongoose Filtering in Rest API: A Comprehensive Guide

Currently, I am utilizing Express to construct an API. Within this API, my objective is to retrieve a list of customers from MongoDB by using Mongoose. The code snippet below showcases the route I have set up (please disregard any paging and limit concerns ...

Ways to display a variable prior to making an axios request

I have a get request that saves the result in a variable named name_student. How can I access this variable in other methods? Or how should I declare it? Here is the code snippet: getStudent(){ axios.get('https://backunizoom.herokuapp.com/student/2 ...