Angular - Ensuring completion of refresh token function before proceeding with the request

I am currently developing an authentication system using Angular with a Django backend that utilizes JWT. Within my Angular interceptor, I have implemented a check in each request to validate the access token's status. If the token is deemed invalid, a refreshtoken function is called to refresh the access token.

Below is the code for the interceptor:

constructor(private authService:AuthService) {}

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    let access_token = localStorage.getItem("access_token");
    
    if(access_token){
      // Verify validity of access token and refresh it using the refresh token if needed
      if(this.authService.isLoggedOut()){
        
        this.authService.refreshToken();
        access_token = localStorage.getItem("access_token");
      }
      const cloned = request.clone({
        headers: request.headers.set("Authorization", "Bearer " + access_token)
      });
      return next.handle(cloned);
    } 
    else{
      return next.handle(request);
    }

This process extracts the access_token and validates it through the auth service. However, if it is found to be invalid, the refreshToken() function is invoked:

  refreshToken(){
    let refresh_token = localStorage.getItem("refresh_token");
    console.log("BEFORE REFRESH: " + localStorage.getItem("access_token"));
 
    return this.http.post(`${apiUrl}token/refresh/`, {"refresh" : refresh_token}).subscribe(res => {
      let access_token = res["access"]
      const expiresAt = this.tokenExpiresAt(access_token);
      localStorage.setItem("access_token", access_token);
      localStorage.setItem("expires_at", JSON.stringify(expiresAt.valueOf()));

      console.log("AFTER REFRESH: " + localStorage.getItem("access_token"));
    });
  }

The issue arises when the interceptor fails to wait for the completion of the refreshToken() function, causing errors in the initial request with an invalid token. Subsequent requests are unaffected. How can I modify this to ensure that the refreshToken() function finishes before proceeding?

Thank you for your help!

Answer №1

I consolidated responses from various inquiries and eventually devised this resolution:

Interceptor:

intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    let access_token = localStorage.getItem("access_token");
  
    if (access_token) {
      // Check if access token is no longer valid, if so, refresh it with the refresh token
      if (this.authService.isLoggedOut()) {
        return from(this.authService.refreshToken()).pipe(
          mergeMap((access_token) => {
            localStorage.setItem("access_token", access_token);
            const cloned = request.clone({
              headers: request.headers.set("Authorization", "Bearer " + access_token),
            });
            return next.handle(cloned);
          })
        );
      }
      const cloned = request.clone({
        headers: request.headers.set("Authorization", "Bearer " + access_token),
      });
      return next.handle(cloned);
    } else {
      return next.handle(request);
    }
  }

refreshToken:

async refreshToken(): Promise<string> {
    let refresh_token = localStorage.getItem("refresh_token"); 
    const res$ = this.http
    .post(`${apiUrl}token/refresh/`, { refresh: refresh_token })
    .pipe(map((res) => res["access"]))
    .pipe(first());
    const res = await lastValueFrom(res$);

    const expiresAt = this.tokenExpiresAt(res);
    localStorage.setItem("access_token", res);
    localStorage.setItem("expires_at", JSON.stringify(expiresAt.valueOf()));
    console.log("Refreshed Access Token");
    return res;
  }

Answer №2

you may utilize rxjs operators like switchMap

    intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    let access_token = localStorage.getItem("access_token");
    if (access_token) {
        // Checking the validity of access token and refreshing it with the refresh token if needed
        if (this.authService.isLoggedOut()) {
            return this.authService.refreshToken().pipe(
                switchMap(token => {
                    const cloned = request.clone({
                        headers: request.headers.set("Authorization", "Bearer " + access_token)
                    });
                    return next.handle(cloned);
                })
            )
        }
    } else {
        return next.handle(request);
    }
}

also, within the refreshToken function - ensure it returns an Observable and updates the localStorage inside the pipe

refreshToken(){
let refresh_token = localStorage.getItem("refresh_token");
console.log("BEFORE REFRESH: " + localStorage.getItem("access_token"));

return this.http.post(`${apiUrl}token/refresh/`, {"refresh" : refresh_token}).pipe(tap(res => {
  let access_token = res["access"]
  const expiresAt = this.tokenExpiresAt(access_token);
  localStorage.setItem("access_token", access_token);
  localStorage.setItem("expires_at", JSON.stringify(expiresAt.valueOf()));

  console.log("AFTER REFRESH: " + localStorage.getItem("access_token"));
});
 )
 }

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

Different method of resolving Typescript modules for 'rxjs' within a single file

I've been stuck in a loop for hours, so here's another tricky question: Currently inside a lerna mono repo with two sub projects, namely ProjectA and ProjectB. Both ProjectA and ProjectB have a dependency on rxjs. In addition, ProjectB depends ...

The Angular change detection mechanism is only triggered once when there are consecutive updates to the Ngrx store

I am facing an issue with Angular change detection in a specific scenario while using ngrx-store to manage my application state. Initially, I have state S1. When action A1 is triggered, the state updates to S2 by a reducer. Subsequently, an effect trigge ...

Display the results from the API in a div using Vue.js

Currently working on implementing dynamic buttons to fetch data from an API call. Struggling with pushing the data array to the template and div. Here is my VueJS setup: var example = new Vue({ el: '#example', data: function () { ...

Guidelines for Optimizing NPM Packages: Maximizing Efficiency and Providing Multiple Import Routes

I have recently developed an NPM package that utilizes Webpack and Babel for transpiling and bundling. Within my package.json, I have specified the main file as "main": "build/index.js". Additionally, in my Webpack configuration, the entry point is set to ...

Retrieving user input from an HTML form in Python using Flask

Collaborating with a partner on a Flask project poses an interesting challenge as we strive to pass a dynamic JSON object from the HTML file to the app.py file for querying our database. Initially, my partner managed to successfully code this feature using ...

Steps for creating a hyperlink that exclusively opens in Chrome and includes a disable flag

I am looking to create a hyperlink that will launch chrome with the flag --disable-hang-monitor, followed by opening the website https:\\randomwebsite.com. Here is the location for Google Chrome: c:\program Files(x86)\Google\Chrom ...

Compatibility between Angular 5 and Bootstrap 4 improvements

I have exhaustively searched the internet and attempted all suggested solutions, but unfortunately, the issue remains unresolved. Please help me :'( view version displayed problem ...

CSS photo display with magnification feature

I currently have a functioning inner-zoomable image set up with the code provided. I am interested in converting this setup into an image gallery with zoom capabilities for the selected image element, but I'm unsure where to start. My objective is to ...

AngularJS, the innovative tool that combines two separate resources into a single cohesive service

Exploring the AngularJS framework as a beginner, I am currently working on creating a service called String that merges the results of two resource requests. The first request is to string/:stringId.json, and the second is to string-'+language+'/ ...

Solving the puzzle of complex polymorphic object model deserialization in Java Jackson: Facing the JsonMappingException error – Unexpected token (START_OBJECT) instead

I am working with a hierarchy of objects described as follows: A B extends A C extends B D extends B E extends C F extends A and contains a reference to A The annotation for class A is defined as: @JsonTypeInfo(use=JsonTypeInfo.Id.CLASS,include=Jso ...

Error: Trying to assign a value to a null property

I am facing an issue while trying to dynamically create "iframe's textarea" and insert the value of a variable. Unfortunately, I keep encountering an error. Any suggestions on how to resolve this problem? P.S: Using Google Chrome as the browser ...

Revitalize website when submitting in React.js

I need assistance with reloading my React.js page after clicking the submit button. The purpose of this is to update the displayed data by fetching new entries from the database. import React, {useEffect, useState} from 'react'; import axios from ...

Attempting to implement a Context Provider in a React application, encountering an error stating "Invalid element type..."

I am attempting to implement a Context Provider in a React application using TypeScript. The following code snippet illustrates my objective in a simplified manner: import * as React from "react"; public render() { let ctx = React.createContext("tes ...

The peculiar formatting issue with the jQuery datepicker that arises when adding more months

When adjusting the numberOfMonths parameter to a higher value such as 6, I noticed that the datepicker display appears unusual with the background extending further than expected. Take a look at my demonstration on jsfiddle: http://jsfiddle.net/zw3z2vjh/ ...

The retrieval process reaches a timeout limit of 120 seconds

About My Project Currently, I am developing a NodeJS/Express application where the home page triggers a Fetch call to a specific route that sends an API request. The retrieved data from this API request is then displayed in a dropdown menu for the client. ...

Sending information to Bootstrap 4 modal

Can you help me insert the variable into this link? <a href="#edit_task" data-toggle="modal"><i class="fas fa-edit fa-lg fa-fw"></i></a> This is my modal: <div class="modal fade" id=" ...

Implement a jstree in a Rails application by utilizing JSON data and configuring the AJAX option to establish parent-child associations

Here is my model: class Project < ActiveRecord::Base belongs_to :parent, :foreign_key => :project_id, :class_name => "Project" has_many :children, :dependent => :destroy, :class_name => "Project", :foreign_key => :project_id Che ...

Another problem with CORS again?

My rails application uses the host http://myhost.dev to search for music through the vk.com API. The API provides a search method called audio.search. Below is the code snippet from my search.js.erb file which is executed via a remote link to the search ac ...

Upon the initial loading of the React component, I am retrieving undefined values that are being passed from the reducer

Upon the initial loading of the React component, I am encountering an issue where the values originating from the reducer are showing up as undefined. Below is a snippet of my code: const [componentState, dispatchComponentState] = useReducer( versionReduc ...

Creating HTML tables from various JSON data sets

I possess a JSON file. ["plat1","chan1","group1","a","cat1","a","a",26,1000], ["plat1","chan2","group1","a","cat2","a","a",70,14], ["plat1","chan1","group1","a","cat1","a","a",14,1000], ["plat1","chan1","group2","a","cat1","a","a",50,2000], ["plat1","cha ...