Retrieve the authentication token from the headers prior to rendering any components

As a newcomer to Angular, I am currently working on making modifications to a codebase. The existing code follows the default Angular method of loading all components simultaneously, resulting in multiple components making HTTP calls at the same time.

My Angular application is hosted on an Azure app service with Azure AD authentication set up through the Azure portal.

In order to enhance my application, I aim to implement a process where, before any component is loaded, I retrieve the id_token parameter from the http://host/.auth/me URL to serve as my authentication token. This token will be utilized as an auth header for API calls to the backend. Once obtained, the authentication token will be stored in local or session storage. Only after this process is complete, do I wish to proceed with loading other Angular components.

While familiar with lazy loading of components, I am seeking advice on the best practices for implementing the aforementioned scenario.

Answer №1

There are various methods to achieve this goal

APP_INITIALIZER

You can create an async function that loads a token and inject it into the main module. Components will not initialize until the promise of the function is resolved. This method is straightforward but may result in a white screen for users while the token request is in progress.

@NgModule({
  //...
  providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: () => () => {
        return fetch('https://token.provired.com/getToken')
          .then(x => x.json())
          .then(x => localStorage.setItem("token", x));
      },
      multi: true,
    }
  ]
})
export class AppModule { }

Root component

You can initiate a token request from a root component, typically app.component.ts. In the component's template, you can display some content or a loading spinner while the request is ongoing to inform users about the app's activity. The initialization of other components can be controlled by using *ngIf. See the code snippet below

@Component({
  selector: 'app-root',
  template: `
    <div *ngIf="!tokenLoaded">Please wait</div>
    <div *ngIf="tokenLoaded">
      <app-my-component></app-my-component>
    </div>`,
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
  tokenLoaded = false;

  ngOnInit(){
    getToken().then(token => {
      localStorage.setItem("token", token);
      tokenLoaded = true;
    })
  }
}

Guard

As mentioned by @Gytis TG in the comments, another option is to run the token request within a guard. This approach offers the benefit of having a separate piece of code dedicated to loading the token.

Answer №2

I believe there are two potential approaches to handle visibility restrictions for your components.

In our application, we manage login and authentication through our auth.service.ts file.

// auth.service.ts
@Injectable({
  providedIn: 'root'
})
export class AuthService {
private token: string;
private loggedIn = false;

constructor(private http: HttpClient) {}


getLoggedIn() {
  return this.loggedIn;
}

login(email:string, password:string): Promise <string> {
  const promise = new Promise < string > ((resolve, reject) => {
    const authData = { email: email, password: password }
      this.http.post < { token: string } > (environment.backendUrl + '/api/user/login', authData).toPromise()
      .then(response => {
         const token = response.token;
         this.token = token;
         if (token) {
           this.loggedIn = true;
         }
}

As shown in the above snippet, once a successful login occurs, we receive a token in our response body, similar to how tokens are obtained in your application. This token is then assigned to the token variable within our auth.service.ts file. Subsequently, we can access this token by calling the getLoggedIn() function from any part of the application after importing the auth.service.ts file.

To restrict component visibility based on the presence of a token, one approach involves importing the auth.service.ts file into the desired component and initializing it in the constructor as follows:

// .ts file where you want to restrict visibility
export class component {
loggedIn = false;

constructor(private authService: AuthService) {}

ngOnInit() {
   this.loggedIn = this.authService.getLoggedIn();
}

The component checks for a token during the ngOnInit lifecycle hook. By leveraging the loggedIn variable with an *ngIf directive in the corresponding HTML file, we can selectively display certain elements or the entire component.

// .html file
<div *ngIf=loggedIn>
   <h1> title </h1>
   <p> here you be your text </p>
</div>

Alternatively, another method involves implementing a guard mechanism, as previously suggested by Mikhail and Gytis. This guard validates the presence of a token before routing to specific pages, ensuring that only authenticated users can access them. This can be achieved by creating an auth.guard.ts file:

@Injectable()
export class AuthGuard implements CanActivate {

  constructor(private authService: AuthService, private router: Router) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): boolean | Observable < boolean > | Promise < boolean > {
    const loggedIn = this.authService.getLoggedIn();
    if (!loggedIn) {
      this.router.navigate(['/login']);
    }
    return loggedIn;
  }

}

By incorporating this guard, we can enforce token validation in our app-routing.ts configuration:

// app-routing.ts file
const routes: Routes = [
    { path: "", component: HomePageComponent },
    { path: "home", component: HomePageComponent },
    { path: "login", component: LoginPageComponent },
    { path: "signup", component: SignupPageComponent },
    { path: "profile/:id", component: ProfilePageComponent, canActivate: [AuthGuard] },

]

@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule],
    providers: [AuthGuard]
})
export class AppRoutingModule {}

In this setup, the profile page is protected by the AuthGuard, ensuring that only authenticated users can access it. If a user attempts to visit the page without logging in, they will be redirected to the login page. This additional layer of security prompts users to log in before accessing restricted content via manual URL entry.

I am relatively new to Stack Overflow, but I have endeavored to provide a comprehensive response in the hopes of assisting you. Have a wonderful day!

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

The promise of returning a number is not compatible with a standalone number

I am currently working on a function that retrieves a number from a promise. The function getActualId is called from chrome.local.storage and returns a promise: function getActualId(){ return new Promise(function (resolve) { chrome.storage.syn ...

Why is my Angular router displaying the page twice in the browser window?

Angular was initially loading the page on the default port localhost:4200. I wanted it to serve as localhost:4200/specialtyquestions when the app builds, and that is working, but the pages are appearing twice in the browser. Any ideas on what might have be ...

"What is the most efficient way to break up an array into its maximum length and iterate over

Using Firebase to send push notifications, but encountering the following error: { Error: tokens list must not contain more than 500 items at FirebaseMessagingError.FirebaseError [as constructor] (/srv/node_modules/firebase-admin/lib/utils/error.js:42: ...

Unable to loop through using ngFor

I have a component that retrieves data from the back-end and groups it accordingly. Below is the code snippet: getRecruitmentAgencyClientPositions(): void { this._recruitmentAgencyClientsService.getRecruitmentAgencyClientPositions(this.recruitmentAge ...

Leveraging Enums in Angular 8 HTML template for conditional rendering with *ngIf

Is there a way to implement Enums in an Angular 8 template? component.ts import { Component } from '@angular/core'; import { SomeEnum } from './global'; @Component({ selector: 'my-app', templateUrl: './app.componen ...

The error message "NgFor only supports binding to Iterables such as Arrays" is triggered even though the JSON response is formatted as an array

Using TypeScript in CompanyComponent Class export class CompanyComponent { apiService : APIService; data : any; private companyUrl = 'http://localhost:4000/api/company/'; constructor(apiService : APIService) { this.apiService = api ...

IntersectionObserver activates prior to element's entrance into the viewport

I've set up a Vue component with the following structure: <template> <article> <!-- This content spans several viewport heights: you *have* to scroll to get to the bottom --> {{ content }} </article> <span ref ...

Is there a way for me to use TypeScript to infer the type of the value returned by Map.get()?

type FuncType<O extends Object> = (option: O) => boolean export const funcMap: Map<string, Function> = new Map() const func1: FuncType<Object> = () => true const func2: FuncType<{prop: number}> = ({ prop }) => prop !== 0 ...

Generate a fresh JSON object by adjusting JSON data in Angular 6

Here is a sample JSON structure: [ { "Key": "doc/1996-78/ERROR-doc-20200103.xlsx" } }, { "Key": "doc/1996-78/SUCCESS-doc-20200103.xlsx" }, { "Key": "doc/1996-78/PENDING-doc-20200103.xlsx" } ] I need to split the values of the K ...

Retrieve the checked checkbox value within an Angular 2 component

<ul class="show-result"> <li *ngFor="let staff of allStaffs;"> <div> <input type="checkbox" >{{ staff.perscode }} | {{ staff.personName }} </div> </li> </ul> <button class=" ...

Nativescript encountered an issue with ../node_modules/nativescript-plugin-firebase/firebase.js

I've been developing an app using Nativescript Angular and code sharing. Everything was working fine after upgrading to Angular 10 a few days ago. However, today I attempted to integrate Firebase using the nativescript-plugin-firebase plugin and encou ...

Converting JSON data to an Excel file in an Angular application

I'm working on exporting my JSON data to an XLSX file. Despite successfully exporting it, the format in the Excel file isn't quite right. Below is the code I am using: downloadFile() { let Obj = { "data": [12,123], "date": ["2018-10- ...

Angular 9 library compilation issue: Module unused warning detected

After upgrading my project from Angular 8 to Angular 9, I encountered a warning when trying to build the library: The '__read' is imported from external module 'tslib', but never used https://i.stack.imgur.com/Xg4Eu.png This warning ...

Guide to making a sidebar for your chrome extension using Angular, no need for an iframe

I am currently developing a chrome extension using Angular, and it functions similarly to the MaxAI chrome extension. When the user clicks on the widget, it triggers the opening of a sidebar. Although I have followed a few tutorials to create a sidebar, t ...

Comparing the properties of objects in two arrays can be done most effectively by utilizing the most efficient method available

In Angular2, I am looking for a more efficient way to check if an object's property in one array matches a property in another array and return the value. Is there a method similar to using .contains in Swift? doSomething(){ for (let element1 of ...

Next.js Project Encounters Compilation Error Due to Tailwind CSS Custom Class

I am currently working on a Next.js project and incorporating Tailwind CSS. Unfortunately, I have come across a compilation error that I am struggling to resolve. The error specifically pertains to a custom utility class that I defined in my theme.css file ...

Struggling with continuously re-rendering a color background when using useMemo in React?

After every re-render, a new color is generated. Is there a way to store the initial color and reuse it in subsequent renders? const initialColor = generateNewColor(); // some random color const backgroundColor = React.useMemo(() => { return ...

A union type that includes integers and NaN as valid return values

I have a function for comparison that has a union return type. It can return -1, 1, or 0. However, I need to handle a special case when at least one of the items being compared is not defined. While the compiler allows me to include null as a potential ret ...

Error in StoryBook addon-docs: "No props discovered for this particular component" when utilizing TypeScript

Encountering an issue with a TypeScript and StoryBook project: The table displaying component properties is not generated nor visible in the StoryBook "Docs" tab on a TypeScript-based project setup. Instead of the expected table, a message saying "No pro ...

Updating the Mat Table Label Dynamically in Angular

Is there a way to dynamically change the value of a label in an Angular mat table with pagination without recreating the entire table? The goal is to update the header label without having to regenerate the whole table structure. For instance, in the tab ...