Error retrieving the latest token in Angular before the component has fully loaded

I am seeking relevant advice to address my specific need:

In my Angular application, I have implemented a jwt-based authentication system. After obtaining a new token and refresh token, I have set up a setTimeout function to ensure the token is refreshed before it expires. However, I also intentionally refresh the token when bootstrapping the app in my AppComponent, which has led to an issue.

The problem arises when retrieving user information from the jwt during app bootstrap. Due to the timing of token storage after component display, the app ends up fetching user details from the previous token instead of the newly generated one.

While considering using resolvers, the drawback is that it would result in triggering two refresh token queries - one in the AppComponent and another inside the resolver, which is not the desired behavior.

So, here is a snippet from my AppComponent:

     ngOnInit() {
        this.refreshToken();
        ...
      }
    
    private refreshToken() {
        this.authSvc.refreshToken().pipe(
          untilDestroyed(this),
          catchError(error => {
            if (error.status === 401) {
              this.router.navigate(['/login']);
            }
            return of();
          })
        ).subscribe();
      }

And here is part of the AuthService:

    get user(): User | null {
      const token = localStorage.getItem('token');
      return token ? this.jwtHelper.decodeToken(token) : null;
    }

    refreshToken(): Observable<LoginResponse> {
        return this.http.post<LoginResponse>(
          `${environment.apiUrl}/token/refresh`,
          {refresh_token: localStorage.getItem('refresh_token')}, this.CONTEXT
        ).pipe(
          tap((res: LoginResponse) => this.storeTokens(res as LoginSuccess))
        );
      }
    
    storeTokens(data: LoginSuccess): void {
        localStorage.setItem('token', data.token);
        localStorage.setItem('refresh_token', data.refresh_token);
        this.scheduleTokenRefresh(data.token);
      }

Furthermore, I have a component where I require user data:

export class HomeComponent implements OnInit {
  user!: User | null;
  constructor(private authSvc: AuthService) {
  }

  ngOnInit() {
    this.user = this.authSvc.user;
  }

The challenge lies in Home component being displayed before storeTokens method is called, resulting in potential mismatch between user data on the backend and the token used by HomeComponent. I have explored using a resolver but it necessitates calling refreshToken again, which does not align with my requirements of keeping the refreshToken logic within the AppComponent without duplicate calls.

Is there a more suitable solution for this scenario?

Answer №1

If you're looking for a solid solution, my top recommendation would be to utilize the APP_INITIALIZER provider. This tool allows you to handle tasks before your application fully boots up, giving you the opportunity to fetch necessary data or tokens preemptively. It's a much cleaner approach than managing everything within the AppComponent.

To implement this in your project, simply add the APP_INITIALIZER provider to your AppModule and define the desired functionality using a factory function. This setup ensures that critical operations are completed before any rendering takes place.

// Sample AppModule setup
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { AuthService } from 'somewhere/in/you/project';

@NgModule({
  declarations: [
    AppComponent, AboutUsComponent, HomeComponent, ContactUsComponent
  ],
  imports: [
    HttpClientModule,
    BrowserModule,
    AppRoutingModule,
  ],
  providers: [ 
    {
      provide: APP_INITIALIZER,
      useFactory: appInit,
      deps: [AuthService]
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

// For standalone applications, include it in bootstrapApplication instead of AppModule
bootstrapApplication(AppComponent, {
  providers: [
    // ...
    {
      provide: APP_INITIALIZER,
      useFactory: appInit,
      multi: true,
      deps: [AuthService],
    },
  ],
}).catch(err => console.error(err));

Don't forget to create the factory function either as a separate file or above the NgModule declaration. By structuring it properly, you can ensure a smooth execution flow and quicker loading times despite potential delays during startup. Many authentication libraries like KeyCloak and Azure benefit greatly from this method, enhancing overall user experience.

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

Enhancing Security and Privacy of User Information with JWT Tokens and NgRx Integration in Angular Application

I'm facing a security concern with my Angular application. Currently, I store user details like isAdmin, isLoggedIn, email, and more in local storage. However, I'm worried about the risks of unauthorized updates to this data, especially since my ...

Exploring Angular's ability to utilize the Map and Subscribe functions within an

I could use some assistance with RxJS. I have a piece of code that is supposed to fetch an API and then, for each returned element, retrieve the type of that element from another API. The code is functioning properly, but it returns an Observable for the ...

What is the best way to prevent a font awesome icon from appearing in a span during data loading

I am currently working on an Angular 11 application where I have implemented an icon to trigger the loading of a graph. However, I have noticed that there is a delay in loading the graph when the icon is clicked. To prevent users from triggering the icon m ...

Adding a URL link to a mentioned user from angular2-mentions within an Angular 4 application can be achieved in the following way:

Need help with adding a URL RouterLink to mention a user using angular2-mentions. This is the code snippet I currently have: <div class="col-sm-12"> <input type="text" [mention]="contactNames" [mentionConfig]="{triggerChar:'@',maxI ...

Tips for utilizing ngIf based on the value of a variable

Here is the code from my file.html: <button ion-button item-right> <ion-icon name="md-add-circle" (click)="save();"></ion-icon> </button> The content of file.ts is: editmode = false; I am trying to achieve the foll ...

Looking forward to the completion of DOM rendering in my Angular/Jasmine unit test!

I recently created an Angular pie chart component using VegaEmbed, which relies on Vega and D3 for its graphics. The chart is generated by providing a title and some (key, value) pairs. I managed to isolate this component and made modifications to main.ts ...

Encountering issues when integrating an Angular library project into the main project

Currently, I am facing an issue with a library project (let's call it X) that contains 2 projects within it (X-core, X-core-members). My goal is to utilize this library in another angular project called ABC. I have made the necessary links in the tsco ...

Triggering the JWT callback manually to refresh an access token: The upcoming authorization process

While signing into my NextJS 13 application with Next Auth using the credentials provider and JWT as the strategy, an access token is obtained from my custom Node.js backend which is refreshed server-side within [...nextauth].ts as outlined in the Next Aut ...

Exploring the magic of Angular 4's FormBuilder: creating dynamic FormControls within a

My application enables users to choose from a dropdown menu of events, each with its own unique properties and selectable property values. This is achieved by creating a FormGroup for each event, with a FormControl for each property. Upon initialization, ...

Angular QueryList produces varying types compared to what is obtained from a click event listener

I'm experiencing an issue when comparing the content of a list of QueryList<ElementRef>. I need to differentiate certain elements in order to create a closing logic for a menu. In my toolbar, I have buttons that are Angular Material buttons of ...

Merge a pair of observables to create a single combined output

Hey there, I'm currently diving into the world of RxJS and reactive programming. One challenge I'm facing is merging two observables. The first observable contains an array of objects called DefectImages[], while the second observable holds an ar ...

Strategies for reducing the number of ngIf statements in Angular's template

I'm seeking advice on how to avoid using multiple *ngIf in templates. For instance, in a component's template, depending on the route, I need to display various elements like so: <div *ngIf="route == 'page1'">Title for page 1< ...

Encountering the error message "Unable to connect to this site" while attempting to run Angular 8 using Docker Compose

After successfully running npm start for my angular UI application, I encountered an issue when moving API and UI into docker. Every time I tried to access the site, it displayed "This site can’t be reached". Can someone please assist me in identifying w ...

When employing GraphQL Apollo refetch with React, the update will extend to various other components as well

My current setup involves using react along with Apollo. I have implemented refetch in the ProgressBar component, which updates every 3 seconds. Interestingly, another component named MemoBox also utilizes refetch to update the screen at the same int ...

Transforming dynamic class based on state value from React to TypeScript

I'm trying to implement this React function in TypeScript, but I'm encountering errors. const ListItem = ({ text }) => { let [showMore, setShowMore] = useState(false); return ( <div className="item"> ...

Guide to leveraging a JWT token for API access in a server-side component with Next.js version 14

I am currently working on a login component for clients. Once the user logs in, the backend (built separately in NestJS) provides a jwt_token. I am then displaying all users on a server-side rendered page. How can I properly store this token and include it ...

Issues encountered while developing a ReactJS application using TypeScript

While attempting to create a React app using the command npx create-react-app client-app --use-npm --typescript, I expected to generate a project with TypeScript files, but instead ended up with index.js and app.js rather than index.tsx and app.tsx. Could ...

Enhance the MUI palette by incorporating TypeScript, allowing for seamless indexing through the palette

When utilizing the Material UI Palette with Typescript, I am encountering a significant issue due to limited documentation on MUI v5.0 in this area. Deep understanding of typescript is also required. The objective is to iterate through the palette and vir ...

Flattening an array of Map in Typescript involves combining all the

I am working with an array containing entries of type Map<string, number> Is there a way to flatten this array into a single map? Map<string, number>[] converted to Map<string, number> Appreciate any help on this matter. ...

What makes TypeScript code run successfully using `node` instead of `ts-node` by mistake?

I have created a basic node.js project using TypeScript. Here is the content of my package.json file, which lists all the packages I have installed (without including ts-node): { "name": "mydemo", "version": "1.0.0", "description": "", "main": "ind ...