angular-auth-oidc-client icon indicating copy to clipboard operation
angular-auth-oidc-client copied to clipboard

Cannot distinguish between not authenticated and initial loading state

Open seabass223 opened this issue 4 years ago • 6 comments

Describe the bug Cannot distinguish between not authenticated and initial loading state.

isAuthenticated$ emits DEFAULT_AUTHRESULT on post login redirect, and a few frames later emits a successful AuthenticatedResult (isAuthenticated is true). Because of this, if I place a call to .authorize() when isAuthenticated$ is fired with DEFAULT_AUTHRESULT on startup, I will get a login loop with the IDP. Need a way to distinguish between DEFAULT_AUTHRESULT which is the initial loading state, and actual token not present or token expired.

To Reproduce Steps to reproduce the behavior:

  1. login to IDP
  2. IDP redirects to angular app
  3. app.component ctor runs, the remaining steps happen in the ctor
  4. this.oidcSecurityService.isAuthenticated$.subscribe(result => console.log(result.isAuthenticated)); //first time always as false after post login redirect
  5. This is the problem point. The authenticatedInternal$ BehaviorSubject emits its inital value which is the same as token not present/token expired. Here my logic would think the user is not logged an and redirect to IDP login page. If I remove this logic then we can continue on to step 6.
  6. this.oidcSecurityService.isAuthenticated$.subscribe(result => console.log(result.isAuthenticated)); //fires as true a few frames later
  7. this.oidcSecurityService.checkAuth().subscribe((loginResponse: LoginResponse) => { console.log(loginResponse.isAuthenticated)}); //fires as true
  8. If F5 to refresh browser then this.oidcSecurityService.isAuthenticated$.subscribe(result => console.log(result.isAuthenticated)); // fires as true
  9. and a few frames later this.oidcSecurityService.isAuthenticated$.subscribe(result => console.log(result.isAuthenticated)); //fires as true

Expected behavior the default value of this.authenticatedInternal$ BehaviorSubject in (looking at compiled output)angular-auth-oidc-client.js would ideally be something other than what is emitted when (there is no token present, or when the users current token expires) so that we can ignore the initial loading state and thus handle token not exists/token expired scenarios appropriately.

Desktop (please complete the following information):

  • OS: Windows 10 Enterprise build 19043.1237
  • Browser: Chrome
  • Version 94.0.4606.61 (64-bit)

seabass223 avatar Sep 27 '21 19:09 seabass223

this works as temporary fix (this.oidcSecurityService as any).authStateService.authenticatedInternal$.next(null);

seabass223 avatar Sep 28 '21 13:09 seabass223

same thing for userService.userDataInternal$

seabass223 avatar Sep 29 '21 16:09 seabass223

Hey, thanks for this. Would you expect null as initial value as a better choice here?

FabianGosebrink avatar Oct 01 '21 06:10 FabianGosebrink

Yes, because rxjs BehaviorSubjects emit their default value, using null as the initial value instead of DEFAULT_AUTHRESULT and DEFAULT_USERRESULT, would allow us to differentiate from the initial BehaviorSubjects value vs a not logged in/expired value.

using this at the top of app.componet.ts ctor as a temporary fix works great.


(this.oidcSecurityService as any).authStateService.authenticatedInternal$.next(null);
(this.oidcSecurityService as any).userService.userDataInternal$.next(null);

if null is not preferred, then perhaps something that relays more intent, but is still different from a not logged in/expired value.

seabass223 avatar Oct 01 '21 13:10 seabass223

This is just a suggestion but usually in the rxjs world, such an observable would not emit with a default initial value but instead emit only once a first value is emitted. That way, subscribers get notified only when there is an actual result and don't have to have knowledge of this placeholder initial value and handling skipping that value.

This is can be archieved by using a ReplaySubject of size 1 (new ReplaySubject(1)), which would have the same behavior as a BehaviorSubject except for the first placeholder value.

Samuel-B-D avatar Jan 07 '22 21:01 Samuel-B-D

Hey @Samuel-B-D , thanks for the explanation and I totally agree with you. We had that implemented initially like this, but a user wanted to have a default value for any reason, so we had this changed long time ago.

FabianGosebrink avatar Jan 08 '22 12:01 FabianGosebrink

Closing as there hasn't been any response from the issue author. Please re-open if you are still seeing problems here.

FabianGosebrink avatar Dec 08 '22 12:12 FabianGosebrink

I have experienced the same problem.

In my case, this was happening because there was an AuthGuard on redirectUrl.

Possible fix:

checkAuth(): Observable<boolean | UrlTree> { return this.eventService.registerForEvents().pipe( filter((event) => event.type != EventTypes.CheckingAuth), switchMap(() => this.oidcSecurityService.isAuthenticated$)).pipe( map(({ isAuthenticated }) => { if (isAuthenticated) { return true; } return this.router.parseUrl('/unauthorized'); }) ); }

kcalisan avatar Dec 21 '22 01:12 kcalisan