Home > database >  Angular's AuthGuard allways return false on page refresh, but I am authenticated
Angular's AuthGuard allways return false on page refresh, but I am authenticated

Time:07-11

I am creating login system in my Angular application. Everything works fine, I have just one problem with my AuthGuard. When I refresh the page in which implement 'canActivate: [AuthGuard]' it redirects me to login and i get isAuth = false. But from there I can go back to my guarded page without login.

AuthGuard

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(
    private userAuthService: UserAuthService,
    private router: Router
  ) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ):
    | boolean
    | UrlTree
    | Observable<boolean | UrlTree>
    | Promise<boolean | UrlTree> {
    const isAuth = this.userAuthService.getIsAuth();
    console.log(isAuth   ' at auth.guard.ts');

    if (!isAuth) {
      this.router.navigate(['/login']);
    }
    return isAuth;
  }
}

And my UserAuthService file:

  private authStatusListener = new Subject<boolean>();
  isAuthenticated: boolean = false;
  private token: string | null = '';

 getIsAuth() {
    return this.isAuthenticated;
  }

  getAuthStatusListener() {
    return this.authStatusListener.asObservable();
  }

  login(user: UserLogin) {
    const url = environment.apiUrl   '/auth/login/';

    this.http.post<{ token: string }>(url, user).subscribe(
      (res: any) => {
        const token = res.token;
        this.token = token;
        if (this.token) {
          this.isAuthenticated = true;
          this.authStatusListener.next(true);
          this.storeJwtToken(this.token);
          this.router.navigate(['/']);
        } else {
          this.isAuthenticated = false;
          this.authStatusListener.next(false);
        }
      },
      (error) => {
        this.authStatusListener.next(false);
      }
    );
  }

  autoAuthUser() {
    const token = this.getJwtToken();
    if (token) {
      this.token = token;
      this.isAuthenticated = true;
      this.authStatusListener.next(true);
    } else {
      return;
    }
  }

  storeJwtToken(token: string) {
    localStorage.setItem('token', token);
  }

  getJwtToken(): any {
    const token: any = localStorage.getItem('token');
    return token;
  }

And in my app.component.ts file I am just doing this:

  ngOnInit(): void {
    this.userAuthService.autoAuthUser();
  }

CodePudding user response:

You can create a promise (async) function in your UserAuthService and provide that function in app.module.ts as dependency. So, it will wait until resolves the promise of your async function before starting the app. With this you will be already logged in when your app starts or refresh. UserAuthService:

  async init(): Promise<any>{
    this.autoAuthUser();
  }

app.module.ts:

@NgModule({
  declarations: [...],
  imports: [...],
  providers: [
    UserAuthService,
    {
      //With this your app will wait to resolve the promise of init() of your UserAuthService.
      provide: APP_INITIALIZER, 
      useFactory: (service: UserAuthService) => function() { return service.init();},
      deps: [UserAuthService],
      multi: true
    }],
  bootstrap: [AppComponent]
})
export class AppModule { }

Another way

You can simply redirect from your autoAuthUser() function.

AuthGuard:

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(
    private userAuthService: UserAuthService,
    private router: Router,
    state: RouterStateSnapshot
  ) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ){
    const isAuth = this.userAuthService.getIsAuth();
    console.log(isAuth   ' at auth.guard.ts');
    if (!isAuth) {
      //Here you pass queryParams with last url from state.url.
      this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
    }
    return isAuth;
  }
}

UserAuthService :

constructor(
    private route: ActivatedRoute,
    private router: Router){}

autoAuthUser() {
    const token = this.getJwtToken();
    if (token) {
      this.token = token;
      this.isAuthenticated = true;
      this.authStatusListener.next(true);
      // Here you redirect to the last URL or '/'.
      this.router.navigateByUrl(this.route.snapshot.queryParams['returnUrl'] || '/');
    } else {
      return;
    }
  }

CodePudding user response:

You can return an Observable in canActivate (docs)

You can just use the getAuthStatusListener in the guard instead of that boolean.

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(
    private userAuthService: UserAuthService,
    private router: Router
  ) {}

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

    return this.userAuthService.getAuthStatusListener().pipe(
      take(1),
      map(isAuthenticated => {
        if (!isAuthenticated) {
          this.router.navigate(['/login']);
        }

        return isAuthenticated;
      })
    )
  }
}

Also, in your service, you need to emit false on the else of autoAuthUser

autoAuthUser() {
  const token = this.getJwtToken();
    
  this.token = token;
  this.isAuthenticated = !!token;
  this.authStatusListener.next(!!token);
}

CodePudding user response:

  1. Can you add your routing File detail then it will more clear.I want know where you applied your guard.
  • Related