Angular CanActivate Guard

huangapple go评论63阅读模式
英文:

Angular CanActivate Guard

问题

I understand your request to translate the code portion only. Here's the translated code:

Contractor Guard:

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { AuthService } from '../auth.service';

@Injectable({
  providedIn: 'root'
})
export class ContractorGuard {

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

  public canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {

    return this.authService.isContractorUser().pipe(
      map(isContractor => {

        console.log('contractor is customer? : ' + isContractor);
        if (!isContractor) {
          this.router.navigate(['page-forbidden']);
          return false;
        } else {
          return true;
        }
      })
    );
  }
}

Customer Guard:

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { AuthService } from '../auth.service';

@Injectable({
  providedIn: 'root'
})
export class CustomerGuard {

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

  public canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {

    return this.authService.isContractorUser().pipe(
      map(isContractor => {
        console.log('Customer is contractor : ' + isContractor);
        if (isContractor) {

          this.router.navigate(['page-forbidden']);
          return false;
        } else {
          return true;
        }
      })
    );

  }
}

Login Guard (To guard the login page for logged-in user):

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { AuthService } from '../auth.service';

@Injectable({
  providedIn: 'root'
})
export class LoginGuard {

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

  public canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {

    return this.authService.isAuthenticated().pipe(
      map(isAuthenticated => {
        if (isAuthenticated) {
          this.router.navigate(['page-forbidden']);
          return false;
        } else {
          return true;
        }
      })
    );
  }
}

Function to return the Behavioral subject of 'isContractor' and 'isLoggedIn':

import { Injectable } from '@angular/core';
import { UserService } from './user.service';
import { SnackbarService } from './snackbar.service';
import { Router } from '@angular/router';
import { BehaviorSubject, Observable } from 'rxjs';
import { HttpHeaders } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  constructor(private userService: UserService,
    private snackbarService: SnackbarService,
    private router: Router) { }

  private _loggedIn = new BehaviorSubject<boolean>(false);
  private loggedIn = this._loggedIn.asObservable();
  private _isContractor = new BehaviorSubject<boolean>(false);
  private isContractor = this._isContractor.asObservable();

  public isAuthenticated(): Observable<boolean> {
    return this.loggedIn;
  }

  public isContractorUser(): Observable<boolean> {
    return this.isContractor;
  }
}

Function to persist logging with JWT (This function is placed in ngOnInit of the app.component.ts, so it will be called every time after a hard refresh or revisit the page.):

public autoLogin(): void {
    if (localStorage.getItem("accessToken")) {

      // Persist the login with JWT, Server verify if it is expired. If it is, try to create a new JWT with the refresh JWT (The logic is in the error section).
      this.userService.loginWithJwt(localStorage.getItem('accessToken')).subscribe(
        response => {
          if (response.status == 200) {
            this.userData = response.body.data[0];
            localStorage.setItem('IsLoggedIn', '1');
            this._loggedIn.next(true);

            if (this.userData['isContractor']) {
              this._isContractor.next(true);
            } else {
              this._isContractor.next(false);
            }
          }
        })
  }
}

I hope this helps! If you have any more questions or need further assistance, please feel free to ask.

英文:

I am developing a small project using Angular, Nodejs, Express, MySQL. In the project there is 2 type of user, customer user and contractor user.

I am having difficulty in using CanActivate to guard the route for the 'Contractor Profile Page' and 'User Profile Page'.

The contractor should not access customer user profile and customer user should not be access contractor profile

In MySQL database, I stored a value 'isContractor' which is used to identify if a user is a contractor or not. And I am using JWT to persist my login, each time refresh I will request all data from the server once again including the isContractor using the JWT (If the JWT expired I will use a refresh token to get new JWT, therefore the auto logging might took some time to process).

Here is the problem. When I refreshed on my 'Contractor or Customer User profile page' the boolean value I get is not correctly reflecting the user type of the logged in user(the CanActivate took the default value I set). Therefore the guard is also not working correctly as it is intended to be.

Contractor Guard:

import { Injectable } from &#39;@angular/core&#39;;
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from &#39;@angular/router&#39;;
import { Observable, Subscription } from &#39;rxjs&#39;;
import { map } from &#39;rxjs/operators&#39;;
import { AuthService } from &#39;../auth.service&#39;;

@Injectable({
  providedIn: &#39;root&#39;
})
export class ContractorGuard {

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

  public canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable&lt;boolean&gt; | Promise&lt;boolean&gt; | boolean {

    return this.authService.isContractorUser().pipe(
      map(isContractor =&gt; {

        console.log(&#39;contractor is csutomer? : &#39; + isContractor);
        if (!isContractor) {
          this.router.navigate([&#39;page-forbidden&#39;]);
          return false;
        } else {
          return true;
        }
      })
    );
  }
}

Customer Guard:

import { Injectable } from &#39;@angular/core&#39;;
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from &#39;@angular/router&#39;;
import { Observable } from &#39;rxjs&#39;;
import { map } from &#39;rxjs/operators&#39;;
import { AuthService } from &#39;../auth.service&#39;;

@Injectable({
  providedIn: &#39;root&#39;
})
export class CustomerGuard {

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

  public canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable&lt;boolean&gt; | Promise&lt;boolean&gt; | boolean {

    return this.authService.isContractorUser().pipe(
      map(isContractor =&gt; {
        console.log(&#39;Customer is contractor : &#39; + isContractor);
        if (isContractor) {

          this.router.navigate([&#39;page-forbidden&#39;]);
          return false;
        } else {
          return true;
        }
      })
    );

  }
}

Login Guard(To guard the login page for logged in user):

import { Injectable } from &#39;@angular/core&#39;;
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from &#39;@angular/router&#39;;
import { Observable } from &#39;rxjs&#39;;
import { map } from &#39;rxjs/operators&#39;;
import { AuthService } from &#39;../auth.service&#39;;

@Injectable({
  providedIn: &#39;root&#39;
})
export class LoginGuard {

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

  public canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable&lt;boolean&gt; | Promise&lt;boolean&gt; | boolean {

    return this.authService.isAuthenticated().pipe(
      map(isAuthenticated =&gt; {
        if (isAuthenticated) {
          this.router.navigate([&#39;page-forbidden&#39;]);
          return false;
        } else {
          return true;
        }
      })
    );
  }
}

Function to return the Behavioral subject of 'isContractor' and 'isLoggedIn':

import { Injectable } from &#39;@angular/core&#39;;
import { UserService } from &#39;./user.service&#39;;
import { SnackbarService } from &#39;./snackbar.service&#39;;
import { Router } from &#39;@angular/router&#39;;
import { BehaviorSubject, Observable } from &#39;rxjs&#39;;
import { HttpHeaders } from &#39;@angular/common/http&#39;;


@Injectable({
  providedIn: &#39;root&#39;
})
export class AuthService {

  constructor(private userService: UserService,
    private snackbarService: SnackbarService,
    private router: Router) { }

  private _loggedIn = new BehaviorSubject&lt;boolean&gt;(false);
  private loggedIn = this._loggedIn.asObservable();
  private _isContractor = new BehaviorSubject&lt;boolean&gt;(false);
  private isContractor = this._isContractor.asObservable();

  public isAuthenticated(): Observable&lt;boolean&gt; {
    return this.loggedIn;
  }

  public isContractorUser(): Observable&lt;boolean&gt; {
    return this.isContractor;
  }
}

Function to persist logging with JWT (This function is placed in ngOnInit of the app.component.ts, so it will be called every time after
the hard refresh or revisit the page.):

public autoLogin(): void {
    if (localStorage.getItem(&quot;accessToken&quot;)) {

      //Persist the login with JWT, Server verify if it is expired. If it is, try create new JWT with the refresh JWT (The logic is in error section).
      this.userService.loginWithJwt(localStorage.getItem(&#39;accessToken&#39;)).subscribe(
        response =&gt; {
          if (response.status == 200) {
            this.userData = response.body.data[0];
            localStorage.setItem(&#39;IsLoggedIn&#39;, &#39;1&#39;);
            this._loggedIn.next(true);

            if (this.userData[&#39;isContractor&#39;]) {
              this._isContractor.next(true);
            } else {
              this._isContractor.next(false);
            }
          }
        })
}

As you can see the value for 'isContractor' and 'isLoggedIn' behavioral subject is updated with the API route call 'loginWithJWT' which is an Observable. Thus there is some asynchronous problem that the value in the Customer Guard or Contractor guard will always use the default value.

Demo video on youtube for the problem:
https://youtu.be/HrjkfMd_YlA

I had tried

1.promise (by using session storage to determine if the value is retrieved from the database before returning a new boolean value, but for some reason, the Guard will just take the default value even with this approach.)

2.BehaviouralSubject (Observable)

I am stuck at how to make the CanActivate not taking the boolean value of isContractor earlier than the value retrieved from the database. It seems like the value is always lagging behind. I believe this is some asynchronous problem that I am not sure how to solve...

答案1

得分: 1

You can turn _isContractor into a plain Subject instead of a BehaviorSubject. 因为你的可观察源是一个 BehaviorSubject(根据定义它创建时有默认值),而你的其他异步逻辑只是使用该值,而不是等待它被找到(在你的情况下是向服务器发送请求)。

当你使用普通的 Subject 时,不会涉及默认值,依赖于此的所有逻辑都将等待主题发出信号;这将在身份验证后,在调用 _isContractor.next 时发生。

还有另一件事情,你可以使用 isLoggedIn 可观察对象来在用户登录后实际检查 isContractor。类似这样:

export class CustomerGuard {

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

  public canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {

    return this.authService.isAuthenticated().pipe(
      filter(isAuthenticated => isAuthenticated),
      switchMap(() => this.authService.isContractorUser())
      map(isContractor => {
        console.log('Customer is contractor: ' + isContractor);
        if (isContractor) {

          this.router.navigate(['page-forbidden']);
          return false;
        } else {
          return true;
        }
      })
    );
  }
}

(Note: I've corrected the code snippet to remove HTML entities and use regular characters.)

英文:

You can turn _isContractor to a plain Subject instead of a BehaviorSubject. Because the fact that your observable source is a BehaviorSubject (which by definition is created with a default value) the rest of your asynchronous logic just uses that value instead of actually waiting for it to be found out (request to server in your case).

When you use a plain Subject, there will be no default value involved, and all of your logic that depends on this, will wait until your subject emits; that will happen after you authenticate, when you call _isContractor.next.

There is also another thing that you could do: use your isLoggedIn observable to actually check isContractor after the user is logged in. Something like this:

export class CustomerGuard {

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

  public canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable&lt;boolean&gt; | Promise&lt;boolean&gt; | boolean {

    return this.authService.isAuthenticated().pipe(
      filter(isAuthenticated =&gt; isAuthenticated),
      switchMap(() =&gt; this.authService.isContractorUser())
      map(isContractor) =&gt; {
        console.log(&#39;Customer is contractor : &#39; + isContractor);
        if (isContractor) {

          this.router.navigate([&#39;page-forbidden&#39;]);
          return false;
        } else {
          return true;
        }
      })
    );
  }
}

huangapple
  • 本文由 发表于 2023年5月22日 01:30:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/76301120.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定