Open blindstuff opened 5 years ago
Implemented the following solution, will submit a PR if in agreement with strategy. @Awk34
auth.service.ts:
constructor(private http: HttpClient, private userService: UserService) {
this.http = http;
this.UserService = userService;
//Not calling this as it will get done the first time isLoggedIn() is called
//TODO: Delete this comment block. Currently commented for clarity.
/*
if(localStorage.getItem('id_token')) {
this.UserService.get().toPromise()
.then((user: User) => {
this.currentUser = user;
})
.catch(err => {
console.log(err);
localStorage.removeItem('id_token');
});
}
*/
}
isLoggedIn(callback?) {
let is = !!this.currentUser._id;
if(is || !localStorage.getItem('id_token')){
//Got the user locally, or there is no id_token stored
safeCb(callback)(is);
return Promise.resolve(is);
}else{
//User not yet loaded locally, but id_token is present
return this.UserService.get().toPromise()
.then((user: User) => {
this.currentUser = user;
return !!this.currentUser._id;
})
.catch(err => {
console.log(err);
localStorage.removeItem('id_token');
return false;
});
}
}
auth-guard.service.ts:
//Change can activate to return a Promise
canActivate(): Promise<boolean> {
return this.authService.isLoggedIn();
}
Definitely an improvement, this works pretty good for me. Thanks!
Cool solution @blindstuff (https://github.com/angular-fullstack/generator-angular-fullstack/issues/2774#issuecomment-437958115)
How about taking it even a little bit further like:
auth.service.ts:
hasRolePromise(role, callback?) {
let is = !!this.currentUser._id;
if(is || !localStorage.getItem('id_token')) {
//Got the user locally, or there is no id_token stored
safeCb(callback)(is);
return Promise.resolve(is);
} else {
//User not yet loaded locally, but id_token is present
return this.UserService.get().toPromise()
.then((user: User) => {
this.currentUser = user;
return this.hasRole(user.role, role)
})
.catch(err => {
console.log(err);
localStorage.removeItem('id_token');
return false;
});
}
}
auth-guard.service.ts:
static parameters = [Router, AuthService];
constructor(
private router: Router,
private authService: AuthService
) {
this.authService = authService;
this.router = router;
}
canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
return this.authService.hasRolePromise(route.data.role).then(is => {
if (!is) {
this.router.navigate(['/login']);
}
return is;
});
}
and for example in the admin module admin.module.ts:
const adminRoutes: Routes = [{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard],
data: {role: 'admin'}
}];
Good idea @albert-92
I had solved this in my implementation by editing canActivate. Either route seems to do well, I preferred to keep them separate, but am open to either.
canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
// this will be passed from the route config
// on the data property
const expectedRole = route.data.expectedRole;
let promise = new Promise<boolean>((resolve,reject) => {
this.authService.isLoggedIn().then(is => {
resolve(this.authService.hasRole(this.authService.currentUser.role,expectedRole));
});
});
return promise;
}
A user that has sucesfully logged on will not be detected as logged in if the site is refreshed inside an AuthGuarded route.
Steps to reproduce bug:
Cause: Router is evaluating the CanActivate function before the constructor has retrevied the user from the Token
AuthGuard triggered
AuthGuard Cancels Navigation
This part of the constructor finishes running and get the user too late.
Click on another authguarded route after this will work. Bug only occurs while refreshing.