Open TimSwerts opened 5 years ago
@TimSwerts did you find a solution to this? I'm experiencing the same. Even if I call validateToken, the currentUserData is still undefined...
I think I had a similar problem. I did two things, not sure which solved it.
1) I put an ngIf on the root component when a user is logged in to wait for currentUserData
to be available.
2) I have a much more complicated AuthGuard that does some throttling and returns Observable
The important thing in the code below is 1) your guard should return an Observable 2) Check if current user data is undefined, if it is, validate the token before continuing:
if (this.authService.currentUserData === undefined) {
return this.authService.validateToken();
}
Full code:
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
subject: ReplaySubject<boolean>;
validationObservable: Observable<boolean>;
// TODO: https://github.com/neroniaky/angular2-token/issues/253
private authServiceValidation$ = this.authService.validateToken()
.pipe(
map<any, boolean>((res: any) => res.success as boolean),
catchError(res => {
const responseIs4xx = getSafe(() => res.status.toString(), '').match(/4\d\d/);
// User probably lost their internet connection
// we can assume that the token is valid until we get a 400 or
// invalid token response
if (navigator.onLine === false || !responseIs4xx) {
this.toastrService.warning('Please check your internet connection.');
// Assume the current user is logged in
return observableOf(true);
} else {
return observableOf(res.success as boolean);
}
}),
switchMap((loggedIn) => {
return setAppStateForLoggedInUser(this.appStateService, this.apollo).pipe(
switchMap(() => observableOf(loggedIn)),
catchError(() => observableOf(loggedIn))
);
}),
tap((loggedIn) => {
if (!loggedIn) {
this.router.navigate(['/login']);
}
}
)
);
constructor(private authService: AngularTokenService,
private router: Router,
private apollo: Apollo,
private appStateService: AppStateService,
private toastrService: ToastrService
) {
this.subject = new ReplaySubject<any>();
// resetValidationObservable method
// sets / resets the validation observable.
// It is called here to set the validation observable.
// But, it also needs to be called on the login page
// if a user has been logged out due to a bad token.
// Issue is: if token expires or becomes invalid
// then user is redirected to login page, (good)
// but when they sign back in they are then brought
// back to the login page
// This does not happen when a user clicks "sign out"
// only when token expires or is invalidated outside
// of the angular session.
// More investigation into why this is happening
// is needed, but for now this solves the problem.
this.resetValidationObservable();
}
/**
* Used to set or reset the replay observable.
* This is important that it is reset or set
* on the initial login.
*
* If it is not set, then it is possible
* that the user cannot not login without
* refreshing the page because the previous
* value of false will be returned before
* the updated credentials are returned by the
* observable.
**/
resetValidationObservable() {
this.validationObservable = this.subject
.pipe(
throttleTime(3000),
switchMap(() => {
return this.authServiceValidation$;
}),
shareReplay<boolean>()
);
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
// Triggers `validationObservable` observable to be fired.
// since validationObservable listens to `subject`
// and provides a throttle which prevents multiple
// requests to the server
this.subject.next(null);
// Logic
return this.validationObservable
.pipe(
switchMap(tokenValid => {
// tokenValid is the previous result we had
// (in the last 3 seconds) for validating the token
// Regardless of if the token is valid or not,
// check if the auth service has been called.
// there are times when a refresh will happen
// and becuase the request is throttled and cached
// auth service might not be initialized
// https://github.com/neroniaky/angular2-token/issues/253
if (this.authService.currentUserData === undefined) {
return this.authService.validateToken();
}
return observableOf(tokenValid);
}),
switchMap((tokenValid) => {
if (tokenValid === false) {
// user needs to log back in
return observableOf(false);
} else {
const roleOk = this.checkRole(route); // user is logged in, check permissions
// User shouldn't be here
if (!roleOk) {
// so redirect to the landing page if we arne't already there
// prevents an infinite loop
if (state.url.toLowerCase() !== 'select_campaign') {
this.router.navigateByUrl('select_campaign');
}
return observableOf(false);
}
// Looks good user is signed in and has permissions.
return observableOf(true);
}
})
);
}
/**
* Check if the current user has the correct role to view the route
*
* @param route
*/
checkRole(route: ActivatedRouteSnapshot) {
const currentUserData = this.authService.currentUserData;
const canActivateRoles: Array<string> = route.data.canActivateRoles;
if (isNil(route.data.canActivateRoles)) {
return true;
}
// Roles of user is included in the array of roles
// that can activate this route
if (canActivateRoles.includes(currentUserData['role'])) {
return true;
}
console.warn('User does not have permission to access component.');
return false;
}
}
I'm submitting a...
Current behavior
I want to reopen an issue. I have kind of the same problem as in the issue #253. I tried to reload the .currentUserData twice and it's not working for me. I think it has something to do with the way I am confronted with this problem.
I am making a AuthGuard or let's say I made it and it works all the time when I am logged in an when I am browsing trough my application. Now I was testing the basic security of my application and I found out that when I use the .currentUserData in my logic of canActivate in my guard and then type in a guarded link, the .currentUserData will be empty so my guard isn't functioning.
I've tried to validate the Token but that doesn't work iether. I think the problem is when I force the browser to go to a certain page the authGaurd works before the component gets loaded in that way the .currentUserData will not be available.
If there is any solution for this problem or if you have extra questions, feel free to respond.
Expected behavior
Getting the currentUserData before the logic of the authGaurd works.
Environment
Angular-Token version: X.Y.Z Angular version: X.Y.Z
Bundler
Browser:
Others: Guard:
Router with guard implementation: