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 -
silentRenewEventHandler creates the callback to handle the IFrame result via codeFlowCallbackSilentRenewIframe , receives an error from the IFrame querystring because the refresh request didn't succeed and immediately throwsrefreshSessionWithIFrameCompletedInternal$ to null - all correctlystartRefreshSession() actually returns a result - and because the results are expected consecutively, forceRefreshSession will now wait forever for a result from refreshSessionWithIFrameCompleted$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!
Hi @PartyArk
We used this initially but the refreshSession ws returning early and so we decided to change it. Is they a way to reproduce this?
Greetings Damien
Hi @damienbod!
I've been faced with the same problem. Steps for reproduce:

Hope this help you.
Thank you!
Thanks @Expelz - yes this is reasonably easy to reproduce -
angular-auth-oidc-client-master and startsample-code-flow, and make sure localhost:4200 is logged inconsole.warn('app authenticated', isAuthenticated) is never hit because checkAuthIncludingServer never completesIt's a bit of an insidious error, because the way the test-server is setup, it's not obvious that there is a problem at all. However, if you're using the result of checkAuthIncludingServer in a route guard (e.g. canLoad or canActivate) then you're in real trouble!
Thanks again.
Hey @PartyArk , just tried it out with your steps and I could not reproduce. Am I doing something wrong?

Well I've just tried again and yours isn't the same as mine, so there must be some difference!
I'm opening up the code in VS Code on Windows, doing npm install and start. Login in, delete session state, and here's the console on the next reload.

As expected app authenticated is never hit and the subscription never completes.
It looks quite different to yours (bit hard to read because of the animation) but clearly your authentication process is getting much further. I think I'm the same as @Expelz - the login_required error is where it all goes wrong.
Ok, so I've taken another look and it looks like this isn't happening in Edge (Chrome engine) or Firefox. So it's something local. After digging around there is this setting here - I _think_ it's on by default in Chrome (could be wrong) -

.... and when I load the page now I get this little icon top right

... which tells me I'm blocking third-party cookies. If I allow third party cookies the oidc process works. So, I think this is the key.
EDIT - Thinking about it, blocking all third party cookies completely is always going to cause silent refresh to fail (unless your sts and client are on the same domain). It's quite a common corporate-enforced setting in my experience.
And that's ok, because silent-refresh is a cookie based system. The _bug_ is that it causes the oidc authentication to effectively hang permanently if you're using it as part of a route guard, and at least never to return a yes / no.
Thanks for taking the time to look at this.
Hey, I just changed something, can you try out this branch? Thanks. https://github.com/damienbod/angular-auth-oidc-client/tree/fabiangosebrink/fixing-missing-catch-error
Hi, I'm afraid a simple catch isn't going to work - the issue is one of timing on the subscription, where the error (and hence completion) of refreshSessionWithIFrameCompletedInternal happens _before_ silentRenewEventHandler completes, and so expecting them to happen consecutively causes the method to hang.
I set out in the original post in more detail why it's not completing. Hopefully you can reproduce your end. Thanks!
Thanks for the answer. We tested the code you wrote above (funnily we have had that code without looking at it :-) ) but during testing it did not work for us. How about this: We are accepting PRs. So you can fix it as you think it should be fixed and we will run your code against our test as well. If all is good we are happy to merge. Will look into it further as well.
Ok I will try that, would be interested to know what didn't work.
As a general point, you way want to run more tests against blocked third party cookies, might throw something else up.
Yeah that'd be really interesting. Would be interested as well. Thanks, yeah, will def try that!
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 forkJoin I proposed and you said you'd tried too. _The returned array parameters are in the wrong order._ Should be:
return forkJoin(
this.startRefreshSession(),
this.silentRenewService.refreshSessionWithIFrameCompleted$.pipe(take(1)))
.pipe(
map(([_, callbackContext]) => {
....
})
);
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.
Hey @PartyArk, thanks for the fix. We have not been far away then, thanks! Does it also work not only one but multiple times? Cheers
Should do, yes.
Having thought about it some more, I suggest the 3rd party cookies and clearing the session state is a bit of a distraction. The essence of the bug is simply this - if silent renew comes back with in an error state, then the process can't complete.
There's a thousand combinations of blocked cookies, SameSite settings, latency, broken connections and all the other things that might go wrong with the request to the sts server. If you can simulate an sts error state in your tests, then you should be covered for these scenarios and this bug.
Hi guys!
As @PartyArk mentioned the problem is occurred here:
return this.startRefreshSession().pipe(
switchMap(() => this.silentRenewService.refreshSessionWithIFrameCompleted$),
take(1),
map((callbackContext) => {
...
})
);
It happens because next value into refreshSessionWithIFrameCompleted$ will be sent before startRefreshSession() 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:'load' event and then redirect iframe to IS authorize endpoint - sessionIframe.src = url;onload handler function, which will dispatch event and after will start execution of silent renew event handler function:And 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-L73
Than 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 before startRefreshSession() 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 httpClient through post request it allows to switch execution context to second 'onload' handler (from step 1) which finally will return observable object startRefreshSession() before result from refreshSessionWithIFrameCompleted$.
I agree with @PartyArk forkJoin will 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).
Hey all, first of all: Thanks, you guys are amazing. This is what makes OSS. Thanks. I already pushed @PartyArk s solution into the branch I created. @damienbod could you run the tests, then we can merge and fix this issue. Thanks to all!
@FabianGosebrink @PartyArk @Expelz Thanks for you work. I'll test this tomorrow, then PR, release, good?
Hey @damienbod , any update on this issue? I ran into the same problem :-(. Thanks in advance and stay healthy.
@FabianGosebrink @PartyArk @Expelz
testing this now (with a slight delay, sorry) see the PR
At the moment it looks good.
will release in version 11.1.4
i have 11.1.4 and this still never completes:
3093 checkAuthIncludingServer() ... isAuthenticated is false ...
2984 return forkJoin([this.startRefreshSession(), this.silentRenewService.refreshSessionWithIFrameCompleted$.pipe
are u still working on this issue ?
let me know if i should prepare stackblitz/sample ...
Most helpful comment
@FabianGosebrink @PartyArk @Expelz Thanks for you work. I'll test this tomorrow, then PR, release, good?