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()
  }

}

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

Transmit data in the form of a buffer

const response = await client.render(data); const Writable = require('stream').Writable; var buffer = []; const myWritableStream = new Writable({ write(chunk, encoding, callback) { ...

Tips for minimizing the transfer time of large arrays using ajax

https://i.stack.imgur.com/IP0oe.pngDescription I am currently working on transferring a JSON object from the server to the client using PHP and JavaScript via AJAX. The JSON object contains a large array (200x200) of integers. The server is running on lo ...

Tips for executing an npm command within a C# class library

I am currently developing a project in a class library. The main objective of this project is to execute a JavaScript project using an npm command through a method call in C#. The npm command to run the JavaScript project is: npm start The JavaScript ...

What is the method to retrieve the data type of the initial element within an array?

Within my array, there are different types of items: const x = ['y', 2, true]; I am trying to determine the type of the first element (which is a string in this case because 'y' is a string). I experimented with 3 approaches: I rec ...

What is the designated color for highlighting an option in Next.js?

This is my first time working on a Next.js project and I see an unfamiliar option. Which selection should I choose? I plan to use JavaScript for the Next.js project, not TypeScript. Just need to figure out which option is currently selected so I can pro ...

What is the proper way to execute a JavaScript function within a JavaScript file from an HTML file?

I have the following content in an HTML file: <!DOCTYPE html> <!-- --> <html> <head> <script src="java_script.js"></script> <link rel="stylesheet" type="text/css" href="carousel.css"> & ...

One Background Image Serving Multiple Divs

Can you use one image (PNG or SVG) as the background for multiple divs? Take a look at the images below to see how it could work. And if the screen width gets smaller and the divs stack up vertically, is there a way to change the background accordingly? D ...

Using JavaScript to ensure that a div is not hidden on page load if a checkbox is unchecked

Upon inspecting a page, I am implementing a script to check if a checkbox is selected. If not selected, the goal is to hide a specific div element. While troubleshooting this issue, I suspect the problem may be due to the lack of an inline element within t ...

Tips for assigning a personalized value to an MUI Switch when it is in the off position

I am currently utilizing the MUI Switch component to create an On-Off button. I have manually set the value as "on" and it is functioning correctly when the switch is in the true state. However, there doesn't seem to be an option to change the default ...

Is there a callback or event that can be used to ensure that getComputedStyle() returns the actual width and height values?

Currently, I find myself in a situation where I need to wait for an image to load before obtaining its computed height. This information is crucial as it allows me to adjust the yellow color selector accordingly. Question: The process of setting the yello ...

Automatically tally up the pages and showcase the page numbers for seamless printing

I've been tackling a challenge in my Vue.js application, specifically with generating invoices and accurately numbering the pages. An issue arose when printing the invoice – each page was labeled "Page 1 of 20" irrespective of its actual position in ...

Creating a class and initializing it, then implementing it in other classes within an Angular application

Trying to grasp some angular fundamentals by creating a class structure. Unsure if this is the right approach. export class Cars{ Items:Car[]=[]; constructor() { this.Items = [ { id: "1", name: "Honda" ...

How can a JavaScript function be triggered by Flask without relying on any requests from the client-side?

I'm in the process of setting up a GUI server using Flask. The challenge I'm facing is integrating an API that triggers a function whenever there's a change in a specific Sqlite3 database. My goal is to dynamically update a table on the HTML ...

React does not accept objects as valid children. If you want to render a group of children, make sure to use an array instead

I am in the process of developing a system for document verification using ReactJS and solidity smart contract. My goal is to showcase the outcome of the get().call() method from my smart contract on the frontend, either through a popup or simply as text d ...

Using the RxJS iif operator for implementing multiple condition statements

What is the optimal approach for returning Observables based on multiple conditions? This is my current code implementation: iif( () => !this.id, this.service.listStuff$(), this.service.listStuffById$(this.id) ).pipe( switchMap((list: L ...

Disable TS4023 error in TypeScript: Unable to name external module "xyz"

//custom-slice.js import { createCustomSlice } from '@my/data-toolkit'; /* ***********************For Managing all the divisions data****************************** */ export const divisionDataSlice = createCustomSlice({ name: 'divisionda ...

The MUI multiple select feature is experiencing issues following the addition of a new button

I'm having trouble adding buttons below a select dropdown menu with a specific height. When I try to put the menu item inside a div, the multiple select stops working and I have no idea why. Can someone help me figure this out? Check out my CodeSandb ...

Make sure that the webpage does not display any content until the stylesheet has been fully loaded by

I am seeking to utilize ng-href for loading different Themes. One issue I am encountering is that the unstyled content appears before the stylesheet is applied. I have created a Plunker where I made changes to Line 8 in the last 3 Versions for comparison ...

javascript creating unique model instances with mongoose

I've searched through related posts without finding exactly what I need. Currently, I am working on building a backend rest API and conducting tests to collect data. Each test has its own model which is associated with collections in the database. T ...

Using jQuery Ajax to send data and retrieve responses in the Codeigniter framework

I am struggling with passing values in CodeIgniter and I need some guidance. Could you provide an example code snippet using CodeIgniter to send a value from a view to a controller using Ajax and jQuery, and then display the result on the same page? In my ...