Long title, I know. It's also a long post. Sorry for that!
tl;dr - when calling signinRedirect
from within the catch()
of a failed promise from signinSilent()
, the wrong parameters are being passed to the identity server.
So maybe I'm doing something unorthodox here, but to me it seemed like a legitimate way to achieve what I want: Upon navigating to a screen in my SPA, check if the user is authenticated. If they are, navigate. If not, attempt to perform a silent signin using an iframe. If the user is logged into identity server already, the silent redirect occurs and navigation completes. If there is no cookie for the auth server (so silent iframe signin fails), redirect the user to the signin screen for the identity server to complete the auth process (then redirect back as per OIDC spec).
So the relevant part of this process' code is here:
// userManager is the oidc-client library's UserManager class.
// data is the field where the OIDC state param goes
var state = {data: navigationInstruction.fragment};
return userManager.loginSilent(state).then(result => {
console.log('signinSilent finished with: ', result);
return next();
}).catch(er =>
{
console.log('signinSilent failed. Doing full page redirect.');
this.userManager.loginRedirect(state).then(result => {
//cancel navigation, redirect will occur.
return next.cancel();
}).catch(er =>
{
console.log('redirect failed:', er);
});
return next.cancel();
});
This worked as expected, mostly. The problem is that I would not be directed to the login page for identity server, but rather presented with an error "login_required". I dug a little bit and discovered this is completely normal if you are using prompt: none
which is rightfully used by the iframe silent signin. So I checked the parameters, and sure enough, that was being passed along with the silent_redirect_uri, instead of the redirect_uri I had set.
So I did a little more digging into the oidc-client source and discovered I could pass those settings in at the time of calling signinRedirect()
I tried and it works:
// userManager is the oidc-client library's UserManager class.
// data is the field where the OIDC state param goes
var state = {data: navigationInstruction.fragment};
return userManager.loginSilent(state).then(result => {
console.log('signinSilent finished with: ', result);
return next();
}).catch(er =>
{
console.log('signinSilent failed. Doing full page redirect.');
// -------------------------- These two lines:
state.prompt = 'consent';
state.redirect_uri = this.config.redirectUri;
// ^^^^^^^^^^^^^^^^^^^
this.userManager.loginRedirect(state).then(result => {
//cancel navigation, redirect will occur.
return next.cancel();
}).catch(er =>
{
console.log('redirect failed:', er);
});
return next.cancel();
});
Firstly, does this workflow make sense? If silent sign-in fails, call redirect sign-in from the catch() of the promise?
Secondly, should it still use the settings it normally would use in this case? (So prompt: undefined
or whatever the default setting is for signinRedirect()
, and use redirect_uri
instead of silent_redirect_uri
? If so, this is a bug.