I'm working with a multi-site instance running Sitecore 9.2 where I have configured a custom identity provider based on the documentation here https://doc.sitecore.com/developers/92/sitecore-experience-manager/en/configure-federated-authentication.html
The identity provider is only intended to be used by one of the sites in this multi-site instance so I have configured the identityProvidersPerSites
entry accordingly:
<identityProvidersPerSites hint="list:AddIdentityProvidersPerSites">
<mapEntry name="my-site" type="Sitecore.Owin.Authentication.Collections.IdentityProvidersPerSitesMapEntry, Sitecore.Owin.Authentication" resolve="true">
<sites hint="list">
<site>my-site</site>
</sites>
<identityProviders hint="list:AddIdentityProvider">
<identityProvider ref="federatedAuthentication/identityProviders/identityProvider[@id='Okta']" />
</identityProviders>
</mapEntry>
</identityProvidersPerSites>
The identity provider configuration:
<identityProviders hint="list:AddIdentityProvider">
<identityProvider id="Okta" type="Sitecore.Owin.Authentication.Configuration.DefaultIdentityProvider, Sitecore.Owin.Authentication" >
<param desc="name">$(id)</param>
<param desc="domainManager" type="Sitecore.Abstractions.BaseDomainManager" resolve="true" />
<caption>Log in with Okta</caption>
<icon>/assets/okta.png</icon>
<domain>mysite</domain>
<transformations hint="list:AddTransformation">
...
</transformations>
</identityProvider>
</identityProviders>
And the implementation:
namespace Demo
{
public class OktaIdentityProvider : IdentityProvidersProcessor
{
protected override string IdentityProviderName => "Okta";
// OAuth provider setting
private string ClientId => Settings.GetSetting(OktaSettings.ClientId, "");
private string ClientSecret => Settings.GetSetting(OktaSettings.ClientSecret, "");
private string Authority => Settings.GetSetting(OktaSettings.Authority, "");
private string OAuthTokenEndpoint => Settings.GetSetting(OktaSettings.OAuthTokenEndpoint, "");
private string OAuthUserInfoEndpoint => Settings.GetSetting(OktaSettings.OAuthUserInfoEndpoint, "");
private string OAuthRedirectUri => Settings.GetSetting(OktaSettings.OAuthRedirectUri, "");
private readonly string OpenIdScope = OpenIdConnectScope.OpenIdProfile + " email";
private readonly string idToken = "id_token";
private readonly string accessDeniedRelativePath = "/my-site/access-denied";
protected IdentityProvider IdentityProvider { get; set; }
public OktaIdentityProvider(FederatedAuthenticationConfiguration federatedAuthenticationConfiguration, ICookieManager cookieManager, BaseSettings settings)
: base(federatedAuthenticationConfiguration, cookieManager, settings)
{ }
protected override void ProcessCore(IdentityProvidersArgs args)
{
Assert.ArgumentNotNull(args, "args");
args.App.UseKentorOwinCookieSaver();
args.App.UseOpenIdConnectAuthentication(CreateOpenIdConnectAuthenticationOptions(args));
}
private OpenIdConnectAuthenticationOptions CreateOpenIdConnectAuthenticationOptions(IdentityProvidersArgs args)
{
IdentityProvider = this.GetIdentityProvider();
var options = new OpenIdConnectAuthenticationOptions
{
ClientId = ClientId,
ClientSecret = ClientSecret,
Authority = Authority,
RedirectUri = GetCallbackUrl(args),
ResponseType = OpenIdConnectResponseType.CodeIdToken,
Scope = OpenIdScope,
AuthenticationType = IdentityProvider.Name,
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
RedirectToIdentityProvider = notification =>
{
if (notification.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
// If signing out, add the id_token_hint
var idTokenClaim = notification.OwinContext.Authentication.User.FindFirst(idToken);
if (idTokenClaim != null)
notification.ProtocolMessage.IdTokenHint = idTokenClaim.Value;
}
return Task.CompletedTask;
}
}
};
return options;
}
...
}
}
The site (my-site) works as intended and I can log in using Okta. The issue is that adding the provider has the unintended consequence of redirecting 401 responses for all the other sites in this instance to Okta as well. My assumption was that the IdentityProvidersPerSites
entry would ensure that this would not happen but I must be missing something about how this is indented to work.
To demonstrate the behavior I'm referring to I have added a separate site called foobar
. Imagine I have just the to sites configured like this:
<site name="my-site" hostName="*" virtualFolder="/my-site" physicalFolder="/my-site" rootPath="/sitecore/content/My Site/" contentStartItem="/sitecore/content/My Site/Home" startItem="/home" language="en" contentLanguage="en" database="web" domain="mysite" ... />
<site name="foobar" hostName="*" virtualFolder="/foo" physicalFolder="/foo" rootPath="/sitecore/content/Foobar/" contentStartItem="/sitecore/content/Foobar/Home" startItem="/home" language="en" contentLanguage="en" database="web" domain="foo" />
I've then added a controller rendering with the sole purpose of returning a 401 response:
using Sitecore.Mvc.Controllers;
using System.Web.Mvc;
namespace Demo
{
public class FooController : SitecoreController
{
[HttpGet]
public ActionResult SansAccess()
{
return new HttpUnauthorizedResult("no foo for you");
}
}
}
After adding this rendering to the homepage of foobar, my expectation is that when I go to /foo
I will get a default 401 page. Instead I see that the 401 is picked up by the Owin middleware, which then redirects (302) the request to Okta.
Has anyone come across this and can point to what I'm missing?
Update
The Owin middleware appears to intercept all 401 responses inside the ApplyResponseChallengeAsync
method of Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationHandler
. The reason it decides to use the Okta provider I've added is because, by default, the provider is configured with AuthenticationMode
set to Active
.
Changing the mode to Passive
resolves the issue of 401 responses from other sites getting intercepted. I manually redirect login requests for my-site
to /identity/login/my-site/Okta
which sends the request off to Okta.
The issue now is that after I log in to Okta, an exception is generated and trapped by the AuthenticationFailed
method of the provider. The exception is referring to nonce beeing null (IDX21323).
My guess is that there are additional steps that's required when the provider is set to passive but so far I haven't been able to identify what is required.