简体   繁体   中英

AuthGuard loaded before AuthService

I'm working on a small personal app. I'll explain what I did until now and in the end my problem and my question. I have created a Node server and an Angular app. When the Angular app is booting I'm checking if the user is logged in (via http get request to the server, the request is made in app.component.ts)

      ngOnInit(): void {
    this.authService.checkIfUserSignedIn();
  }

Inside the checkIfUserSignedIn method after that I'm getting the relevant authentication information I notify to the interested components with the auth state.

this.userAuthDetailsSubject.next(this.userAuthDetails);

Additionally, I'm having an AuthGuard that restrict the entry to the "create-list" component only to authenticated users. In the AuthGurad I'm checking the auth state:

const authStatus = this.authService.isAuth();
return authStatus;

In the menu html component I have the following code:

<span routerLink="create-list" *ngIf="userIsAuthenticated"> New List</span>

Which works fine. My problem is when i'm visiting manually localhost:4200/create-list

The AuthGuard is probably loaded before auth state is updated and therefore the user has no access to the "create-list" component, although he is signed in eventually.

I thought about two solutions but I'm not sure if they are good and how to implement them, and would like to hear your opinion.

  1. using localStorage (It may be an overkill solution for this tiny problem)
  2. make the HTTP get request to the server (for the auth state) inside the authGuard or maybe subscribe to an observer in the auth service (if so, how to implement that?)

Any ideas/solutions?


canActivate (AuthGuard):

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | import("@angular/router").UrlTree | import("rxjs").Observable<boolean | import("@angular/router").UrlTree> | Promise<boolean | import("@angular/router").UrlTree> {
    const authStatus = this.authService.isAuth();
    if (authStatus) {
        return true;
    } else {
        this.router.navigate(['/login']);
    }
}

auth.service.ts

@Injectable()
export class AuthService {
    userAuthDetailsSubject = new Subject<UserAuthDetails>();
    userAuthDetails: UserAuthDetails = null;
    private isAuthenticated = false;
    constructor(@Inject(DOCUMENT) private document: Document, private http: HttpClient) {

    };

    public isAuth(): boolean {
        console.log({
            isAuth: this.isAuthenticated
        })
        return this.isAuthenticated;
    }

    signIn() {
        // redirect to signin..
        this.document.location.href = '/auth/google';
    }

    signOut() {
        this.document.location.href = '/auth/logout';
    }

    checkIfUserSignedIn() {
        this.http.get<any>('/auth/current_user').subscribe(res => {
            if (res) {
                this.isAuthenticated = true;
                console.log('assigning true to isAuth')
                this.userAuthDetails = {
                    displayName: res.displayName,
                    email: res.email,
                    uid: res._id
                };
                this.userAuthDetailsSubject.next(this.userAuthDetails);
            } else {
                console.log('User not authenticated')
            }
        })
    }
}

For this particular problem you can make the 'isAuthenticated' field a subject just like 'userAuthDetailsSubject' and update its value when the server responds.

auth.service.ts

    checkIfUserSignedIn() {
    this.http.get<any>('/auth/current_user').subscribe(res => {
        if (res) {
            this.isAuthenticated.next(true);    //update the value
            console.log('assigning true to isAuth')
            this.userAuthDetails = {
                displayName: res.displayName,
                email: res.email,
                uid: res._id
            };
            this.userAuthDetailsSubject.next(this.userAuthDetails);
        } else {
            console.log('User not authenticated')
        }

    })
}

Now change your authguard so it does not return true or false synchronously.

canActivate (AuthGuard):

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): 
         boolean | import("@angular/router").UrlTree | 
          import("rxjs").Observable<boolean | import("@angular/router").UrlTree>| Promise<boolean | import("@angular/router").UrlTree> {


          return this.authService.isAuth().subscribe((logged)=>{
            if (logged) {
              return true;
            } else {
             this.router.navigate(['/login']);
             return false;
            }
          })
       }
         

Off topic:

  1. Why do you use import("@angular/router").UrlTree ? You can use import like import { UrlTree } from '@angular/router';
  2. CanActive support UrlTree return. return this.router.createUrlTree(['/login']); and not create a new async process in your canActivate

On Topic: If you call direct link, you have to resolve authentication. If you call link or F5 reload browser will lost every data from memory. If you use any token to auth it be worth saving into localStore and restore from here. Ofc, After authentication if you open new tab, this new tab will new without auth default like you used F5 on current tab. It lives a separate life for each tabs.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM