Securing your Angular application with user authentication and route guarding ensures

In the process of developing an Angular single-page application (SPA) front-end that interacts with a GraphQL endpoint, I encountered a challenge. Upon user login, I store the token in local storage and update the authentication state in my AuthService component. My initial approach was inspired by React's lifecycle methods – specifically, when the App component mounts using ngOnInit, I aimed to send a request for a "me" query that retrieves user data from the token stored in local storage and then set this user data in the AuthService component. However, I ran into an issue where the AuthGuard for the protected dashboard route did not wait for the completion of the App Component's ngOnInit method. As a result, it immediately redirected users to the login page.

import {Component, OnDestroy, OnInit} from '@angular/core';
import {MeGQL, User} from "../generated/graphql";
import {AuthService} from "./auth.service";
import {Router} from "@angular/router";

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html'
})
export class AppComponent implements OnInit {
  title = 'frontend';
  loading: boolean = true
  private meSubs: any;

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

  async ngOnInit() {
    this.loading = true
    console.log("MONTOU APP")
    this.loading = true
    return this.meQuery.fetch({}, {
      fetchPolicy: "network-only",
    }).toPromise()
      .then(({data}) => {
        console.log("ENTROU NO THEN")
        if (data.me) {
          console.log(data.me)
          this.authService.setUser(data.me)
          this.loading = false
        }
      }).catch(e => {
        this.loading = false
        console.log("ERROR: ", e)
      })
  }


}
{{ loading }}
<div *ngIf="loading">Carregando...</div>
<div *ngIf="!loading">
    <router-outlet></router-outlet>
</div>
import { Injectable } from '@angular/core';
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree} from "@angular/router";
import {AuthService} from "../auth.service";
import {Observable} from "rxjs";

@Injectable({
  providedIn: 'root'
})
export class AuthGuardService implements CanActivate{

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

  async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):  Promise<boolean >  {
    console.log("Auth Guard user mount")
    if(!this.authService.isAuthenticated()) {
      console.log("Não autenticado")
      await this.router.navigate(['/login'])
      return false
    }
    return true
  }
}
import {Injectable} from '@angular/core';
import {User, MeQuery, MeDocument, MeQueryVariables} from "../generated/graphql";
import {BehaviorSubject} from "rxjs";
import {Apollo} from "apollo-angular";

export type CurrentUserType = Pick<User, 'id' | 'name' | 'email' | 'active' | 'type'>

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private TOKEN_KEY = "AGENDEI_TOKEN"
  private currentUser: CurrentUserType | null = null
  private _isAuthenticated = new BehaviorSubject(false);
  private authSource = new BehaviorSubject<CurrentUserType | null>(null)


  constructor(private apollo: Apollo) { }

  loginUser(user: CurrentUserType, accessToken: string) {
    localStorage.setItem(this.TOKEN_KEY, accessToken)
    this.setUser(user)
    this._isAuthenticated.next(true)
  }

  setUser(user: CurrentUserType) {
    this.currentUser = user
  }

  async logout() {
    localStorage.removeItem(this.TOKEN_KEY)
    await this.apollo.getClient().resetStore()
    this._isAuthenticated.next(false);
  }

  public isAuthenticated(): Boolean {
    return this._isAuthenticated.value
  }

  public getUserFromMeQuery() {
    return this.apollo.query<MeQuery, MeQueryVariables>({
      query: MeDocument
    }).toPromise()
  }

}

https://i.sstatic.net/b9m1A.png

Answer №1

One potential solution is to modify the canActivate method within your guard service.

The issue may be related to the fact that you are not waiting for the authentication service to establish the user's authentication state.

AuthService

 public isAuthenticated(): Promise<boolean> {
    return this._isAuthenticated.toPromise();
  }

AuthGuardService

async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):  Promise<boolean >  {
    console.log("Auth Guard user mount")
const isAuthenticated = await this.authService.isAuthenticated()
    if(!isAuthenticated) {
      console.log("Not authenticated")
      await this.router.navigate(['/login'])
      return false
    }
    return true
  }


Answer №2

Consider utilizing the await keyword within the ngOnInit method of the AppComponent:

async ngOnInit() {
    this.loading = true
    console.log("MONTOU APP")
    this.loading = true
    let response;
    try {
        response = await this.meQuery.fetch({}, {
                            fetchPolicy: "network-only",
                         }).toPromise()
        let {data} = response;
        if (data.me) {
           console.log(data.me)
           this.authService.setUser(data.me)
        }
        this.loading = false
    } catch (err) {
        this.loading = false
        console.log("ERROR: ", err)
    }
 }

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

React components can be used to dynamically render and display an array of objects through methods like reduce and

Here's the scenario at hand: (https://codesandbox.io/s/8p21n6p09l) I have an array of objects (referred to as modules) structured like this: const modules = [ { thematicArea: "Topic 1", id: 1, name: "Building assertive attitude", d ...

Encountering Karma Angular Error: Name 'X' Not Found

After executing Karma Start in my Angular project, I am encountering several errors. All the error messages highlight issues like 'Cannot find name Blob', 'Cannot Find name KeyboardEvent', 'Cannot find name HTMLElement', amon ...

The position of the jQuery VirtualKeyboard is not displaying correctly

I'm currently experiencing an issue with the placement of the keyboard while using the Mottie/Keyboard plugin. The images provided below illustrate my desired outcome and the current behavior: Despite my attempts, the keyboard consistently appears at ...

The Vue.js form is experiencing issues with submission when pressing the "enter" key

TL;DR Question Why is it that having 2 identical input fields in a form prevents the enter button from submitting the form? More detailed question Straight to the point. I'm attempting to use the `enter` button to submit a form when an input elemen ...

The registration form in 'next-auth/react' is not available

Currently, I am in the process of setting up a sign-up page and integrating it with Google using NextAuth. I have successfully integrated signIn with NextAuth and can sign in, but I am facing issues with creating an actual user in the database... The cod ...

The getElementById method in JavaScript can result in a null return value

Why is null returned by the getElementById method in JavaScript? <html> <head> <title>test_elementObject</title> <script language="JavaScript" type="text/javascript"> <!-- var input1 = document.getElementById ( " ...

Instead of logging the JSON file in the console, download it using $.getJson()

Is there a method to download a json file without using jQuery's $.getJSON() and having to log the callback function's argument? I would like to avoid manually typing it from the console.log due to its length. Is there an option to print it, eve ...

Jquery solution for toggling multiple Divs individually

I'm currently working on a small application that has both a sidebar menu and a header menu. My goal is to have all items in these menus toggle the visibility of content in one main window or page. When a button is clicked, I want it to show the corre ...

What is the best way to call a method within a TypeScript class using its name as a string while already inside the class itself?

Currently, I am developing a class that automates the creation of routes for Express and invokes a function in a controller. However, upon trying to execute this[methodName](req, res), I come across an error message stating: 'Element implicitly has an ...

Activating the loader dismiss command will transition the swiper to the current page

Having a swiper and loader makes the scenario very straightforward. The loader is initialized whenever calculations are performed, and after successfully obtaining the result, the loader turns off and swipes to the second slide. <swiper-container #sl ...

Update the controller variable value when there is a change in the isolate scope directive

When using two-way binding, represented by =, in a directive, it allows passing a controller's value into the directive. But how can one pass a change made in the isolated directive back to the controller? For instance, let's say there is a form ...

How can I address multiple buttons with various events using jQuery?

I am new to learning jQuery and I'm currently working on some exercises. However, I've run into an issue with two buttons in the DOM that are supposed to perform different actions. I can't seem to figure out how to assign different functions ...

Ways to update the cart page automatically after quantity changes in Shopify

I have made some updates to the Approach and now I am using JavaScript. I've also updated the script and its logic, which is pasted below. Please take a look and see if you can assist me. I am trying to display information on the top of the cart page ...

VUE JS - My methods are triggering without any explicit invocation from my side

I've encountered a frustrating problem with Vue JS >.<. My methods are being triggered unexpectedly. I have a button that is supposed to execute a specific method, but this method gets executed along with other methods, causing annoyance... Her ...

The Swagger-ui tool condenses objects into query parameters, while the angular client that it generates does not

I'm currently working on a Spring Boot application that includes the following Rest function: @SecuredMaster @GetMapping(path = "/mitarbeiter") @Operation(security = {@SecurityRequirement(name = "jwt")}) public Page<MitarbeiterListRow> getMitar ...

Can we categorize various types by examining the characteristics of an object?

Is it feasible with TypeScript to deduce the result below from the given data: const data = { field1: {values: ['a', 'b', 'c']}, field2: {values: ['c', 'd', 'e'], multiple: true} } const fiel ...

Highlighted option selection in Material UI dropdown using Cypress

Can someone explain how to select Material-UI dropdown options using Cypress? I'm looking for a simple explanation, thanks! ...

The modal template in Angular UI is not displaying properly with Bootstrap styling

I've been working on displaying a modal template when a row is selected on a table. The issue I'm facing is that upon clicking a row, a 2px thick black shadowed line appears, which seems to represent the modal but doesn't display its conten ...

Load and execute a dynamically created script in JavaScript at the same time

I am exploring the option to load and evaluate a script from a third-party synchronously. Here is an example that currently works well: <head> <script src="https://unpkg.com/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfe ...

Can the fluctuations in resolution of webRTC streaming video be detected?

We are currently working on a project involving WebRTC and have specific requirements that involve detecting when the resolution of the streaming video (remote stream) changes in WebRTC. Is there a way for us to achieve this? Any tips or guidance would be ...