angular-auth-oidc-client: checkAuthIncludingServer cannot complete without credentials
Hi, owing to the rewrite of forceRefreshSession (thank you) there are circumstances where the checkAuthIncludingServer and / or forceRefeshSession observables will never return a value. I believe its a race error, but is apparent (for me) if either is subscribed to without any existing credentials in the sessionStorage.
What’s happening -
- The
silentRenewEventHandlercreates the callback to handle the IFrame result viacodeFlowCallbackSilentRenewIframe, receives an error from the IFrame querystring because the refresh request didn’t succeed and immediately throws - silentRenewEventHandler catches the error in its subscription to the callback, and updates
refreshSessionWithIFrameCompletedInternal$to null - all correctly - However, all this happens before
startRefreshSession()actually returns a result - and because the results are expected consecutively, forceRefreshSession will now wait forever for a result fromrefreshSessionWithIFrameCompleted$
I guess it may or may not happen depending on speed of network.
I suggest a fix is to deal with the observable concurrently
return this.startRefreshSession().pipe(
switchMap(() => this.silentRenewService.refreshSessionWithIFrameCompleted$),
take(1),
map((callbackContext) => {
...
})
);
… to …
return forkJoin(
this.startRefreshSession(),
this.silentRenewService.refreshSessionWithIFrameCompleted$.pipe(take(1)))
.pipe(
map(([callbackContext, _]) => {
...
})
);
Thank you!
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 1
- Comments: 20 (4 by maintainers)
@FabianGosebrink @PartyArk @Expelz Thanks for you work. I’ll test this tomorrow, then PR, release, good?
will release in version 11.1.4
Hi guys! As @PartyArk mentioned the problem is occurred here:
It happens because next value into
refreshSessionWithIFrameCompleted$will be sent beforestartRefreshSession()will return observable object.After long investigation why it happens and in what situations, I finally found root cause of the problem:
startRefreshSession()will execute this function: https://github.com/damienbod/angular-auth-oidc-client/blob/0799173fa19ca803109aa2bf8083361c3806bfa9/projects/angular-auth-oidc-client/src/lib/iframe/refresh-session-iframe.service.ts#L26-L41 This code will register handler for'load'event and then redirect iframe to IS authorize endpoint -sessionIframe.src = url;onloadhandler function, which will dispatch event and after will start execution of silent renew event handler function: https://github.com/damienbod/angular-auth-oidc-client/blob/0799173fa19ca803109aa2bf8083361c3806bfa9/projects/angular-auth-oidc-client/src/lib/iframe/silent-renew.service.ts#L100-L128And here is occurred main problem - we have two event handlers for
'load'event for iframe. Event handler from inline script (silent-renew.html) will be executed first. And given that the authorization was unsuccessful (step 2), execution of this'load'event handler will be finished after throw error inside silent renew service: https://github.com/damienbod/angular-auth-oidc-client/blob/0799173fa19ca803109aa2bf8083361c3806bfa9/projects/angular-auth-oidc-client/src/lib/iframe/silent-renew.service.ts#L56-L73Than this error will be handled by subscriber: https://github.com/damienbod/angular-auth-oidc-client/blob/0799173fa19ca803109aa2bf8083361c3806bfa9/projects/angular-auth-oidc-client/src/lib/iframe/silent-renew.service.ts#L117-L127
Finally here we see call to
refreshSessionWithIFrameCompleted$.next(). And now pay attention that all this actions happened beforestartRefreshSession()will return observable object.Reasonable question: “Why doesn’t this happen with successful authorization (silent renew)?” The answer: because after success authorization our iframe will do code exchange (code from IS which we should exchange to get token(s)). And it will be done by
httpClientthrough post request it allows to switch execution context to second'onload'handler (from step 1) which finally will return observable objectstartRefreshSession()before result fromrefreshSessionWithIFrameCompleted$.I agree with @PartyArk
forkJoinwill handle it. If you want reproduce such behavior you need clear not only local or sessions storage but also Cookies (there is located authentication cookie from IS).I’ve had another look at this - I have tried forcing karma to launch a browser with third-party-cookies-disabled but I’m not really sure how. Nor do I know how I would write a test, sorry.
However, the fix is really simple - there’s a glitch in the
forkJoinI proposed and you said you’d tried too. The returned array parameters are in the wrong order. Should be:All tests pass with this in place, and my “manual” testing indicates that the hanging silent-renew bug goes away too. But I think it would be better to have some proper tests in place.