I am working on upgrading a project from sitecore 8.1 to 9.3. I have added a new identity provider - AzureB2C to login into sites. It is using OpenIdConnect. I am able to navigate to the login page and get authenticated as well, but upon returning, I get blank error page -
It is redirecting to the redirectUrl configured and sends code, token and state as well. But after that this issue occurs. Below are the code and configs -
Config 1
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<initialize>
<processor type="Project.SC.Feature.Login.Models.Routes.RegisterAuthRoutes, Project.SC.Feature.Login"
patch:before="processor[@type='Sitecore.Mvc.Pipelines.Loader.InitializeRoutes, Sitecore.Mvc']" resolve="true" />
</initialize>
<owin.identityProviders>
<!--This is the custom processor that gets executed when azure AD posts the token to Sitecore-->
<processor type="Project.SC.Feature.Login.Providers.AzureAdB2CIdentityProviderProcessor, Project.SC.Feature.Login" resolve="true" />
</owin.identityProviders>
</pipelines>
<services>
<!-- <configurator type= "Feature.AzureAdB2C.Models.AuthenticationConfigurator, Feature.AzureAdB2C"/>-->
<register serviceType="Sitecore.Abstractions.BaseAuthenticationManager, Sitecore.Kernel"
implementationType="Sitecore.Owin.Authentication.Security.AuthenticationManager, Sitecore.Owin.Authentication"
lifetime="Singleton" />
<register serviceType="Sitecore.Abstractions.BaseTicketManager, Sitecore.Kernel"
implementationType="Sitecore.Owin.Authentication.Security.TicketManager, Sitecore.Owin.Authentication"
lifetime="Singleton" />
<register serviceType="Sitecore.Abstractions.BasePreviewManager, Sitecore.Kernel"
implementationType="Sitecore.Owin.Authentication.Publishing.PreviewManager, Sitecore.Owin.Authentication"
lifetime="Singleton" />
</services>
<federatedAuthentication type="Sitecore.Owin.Authentication.Configuration.FederatedAuthenticationConfiguration, Sitecore.Owin.Authentication">
<!--Provider mappings to sites-->
<identityProvidersPerSites hint="list:AddIdentityProvidersPerSites">
</identityProvidersPerSites>
<!--Definitions of providers-->
<identityProviders hint="list:AddIdentityProvider">
<identityProvider id="AzureAdB2C" type="Sitecore.Owin.Authentication.Configuration.DefaultIdentityProvider, Sitecore.Owin.Authentication">
<param desc="name">AzureAdB2C</param>
<param desc="domainManager" type="Sitecore.Abstractions.BaseDomainManager" resolve="true" />
<caption>AzureAdB2C</caption>
<domain>CustomDomainName</domain>
<transformations hint="list:AddTransformation">
<transformation name="nameClaimTransformation" type="Sitecore.Owin.Authentication.Services.DefaultTransformation, Sitecore.Owin.Authentication">
<sources hint="raw:AddSource">
<claim name="http://schemas.microsoft.com/identity/claims/objectidentifier" />
</sources>
<targets hint="raw:AddTarget">
<claim name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" />
</targets>
<keepSource>true</keepSource>
</transformation>
</transformations>
</identityProvider>
</identityProviders>
<!--List of all shared transformations-->
<sharedTransformations>
</sharedTransformations>
<!--Property mappings initializer-->
<propertyInitializer type="Sitecore.Owin.Authentication.Services.PropertyInitializer, Sitecore.Owin.Authentication">
</propertyInitializer>
</federatedAuthentication>
</sitecore>
</configuration>
Config 2
<configuration>
<sitecore>
<federatedAuthentication>
<identityProvidersPerSites>
<mapEntry name="portal" type="Sitecore.Owin.Authentication.Collections.IdentityProvidersPerSitesMapEntry, Sitecore.Owin.Authentication" resolve="true">
<sites hint="list">
<site>portal</site>
</sites>
<identityProviders hint="list:AddIdentityProvider">
<identityProvider ref="federatedAuthentication/identityProviders/identityProvider[@id='AzureAdB2C']"/>
</identityProviders>
<externalUserBuilder type="Sitecore.Owin.Authentication.Services.DefaultExternalUserBuilder, Sitecore.Owin.Authentication" resolve="true">
<param desc="isPersistentUser">false</param>
</externalUserBuilder>
</mapEntry>
</identityProvidersPerSites>
</federatedAuthentication>
</sitecore>
</configuration>
IdentityProviderProcessor -
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Project.SC.Feature.Login.Configuration;
using Project.SC.Feature.Login.Extensions;
using Project.SC.Feature.Login.Models;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin.Infrastructure;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Notifications;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;
using Sitecore.Abstractions;
using Sitecore.Configuration;
using Sitecore.Diagnostics;
using Sitecore.Owin.Authentication.Configuration;
using Sitecore.Owin.Authentication.Extensions;
using Sitecore.Owin.Authentication.Pipelines.IdentityProviders;
using Sitecore.Owin.Authentication.Services;
using Sitecore.Text;
using Sitecore.Web;
using OpenIdConnectMessage = Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage;
namespace Project.SC.Feature.Login.Providers
{
[CLSCompliant(false)]
public class AzureAdB2CIdentityProviderProcessor : IdentityProvidersProcessor
{
protected override string IdentityProviderName => "AzureAdB2C";
public AzureAdB2CIdentityProviderProcessor(
FederatedAuthenticationConfiguration federatedAuthenticationConfiguration, ICookieManager cookieManager,
BaseSettings settings) : base(federatedAuthenticationConfiguration, cookieManager, settings)
{
}
protected override void ProcessCore(IdentityProvidersArgs args)
{
Assert.ArgumentNotNull(args, nameof(args));
Log.Info("Inside AzureAdB2CIdentityProviderProcessor -> processcore ", typeof(AzureAdB2CIdentityProviderProcessor));
List<SiteInfo> siteInfoList = Factory.GetSiteInfoList();
IEnumerable<OpenIdConnectSiteInfo> sites =
siteInfoList.Select(s => new OpenIdConnectSiteInfo(s)).Where(s => s.UsesOpenIdConnect);
foreach (OpenIdConnectSiteInfo site in sites)
{
Log.Info("Inside foreach site in sites AzureAdB2CIdentityProviderProcessor", typeof(AzureAdB2CIdentityProviderProcessor));
// NOTE [ILs] SXA allows adding multiple hostnames to be matched seperated by |
foreach (string hostname in site.HostName.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries))
{
Log.Info("Inside foreach hostname in site.hostname AzureAdB2CIdentityProviderProcessor", typeof(AzureAdB2CIdentityProviderProcessor));
// args.App.UseOpenIdConnectAuthentication(CreateOptionsFromSiteInfo(site));
args.App.MapWhen(
ctx => ctx.MapDomain(hostname) && ctx.MapFolder(site.VirtualFolder),
app =>
{
CookieAuthentication.ConfigureCookieAuthentication(app);
app.UseOpenIdConnectAuthentication(CreateOptionsFromSiteInfo(site));
});
Log.Info("After MapWhen AzureAdB2CIdentityProviderProcessor", typeof(AzureAdB2CIdentityProviderProcessor));
}
}
}
private OpenIdConnectAuthenticationOptions CreateOptionsFromSiteInfo(OpenIdConnectSiteInfo site)
{
OpenIdConnectAuthenticationOptions options = new OpenIdConnectAuthenticationOptions();
var identityProvider = GetIdentityProvider();
Log.Info("Start AzureAdB2CIdentityProviderProcessor -> CreateOptionsFromSiteInfo ", typeof(AzureAdB2CIdentityProviderProcessor));
// For each policy, give OWIN the policy-specific metadata address, and
// set the authentication type to the id of the policy
options.MetadataAddress = site.Authority;
options.AuthenticationType = GetAuthenticationType();
options.RedirectUri = site.RedirectUri;
options.PostLogoutRedirectUri = site.PostlogoutRedirectUri;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = site.NameClaimType,
SaveSigninToken = true
};
options.Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = context => HandleOpenIdConnectAuthenticationFailed(context, site),
RedirectToIdentityProvider = context => HandleOpenIdConnectRedirectToIdentityProvider(context, site),
//SecurityTokenValidated = notification =>
//{
// notification.AuthenticationTicket.Identity.AddClaim(new Claim("idp", "azureadb2c"));
// // transform all claims
// ClaimsIdentity identity = notification.AuthenticationTicket.Identity;
// notification.AuthenticationTicket.Identity.ApplyClaimsTransformations(new TransformationContext(FederatedAuthenticationConfiguration, identityProvider));
// return Task.CompletedTask;
//}
};
// These are standard OpenID Connect parameters, with values pulled from web.config
options.ClientId = site.ClientId;
//http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
options.Scope = site.Scope;
//http://openid.net/specs/openid-connect-core-1_0.html#Authentication
options.ResponseType = OpenIdConnectResponseTypes.CodeIdToken;
Log.Info("End AzureAdB2CIdentityProviderProcessor -> CreateOptionsFromSiteInfo", typeof(AzureAdB2CIdentityProviderProcessor));
return options;
}
/*
* On each call to Azure AD B2C, check if a policy (e.g. the profile edit or password reset policy) has been specified in the OWIN context.
* If so, use that policy when making the call. Also, don't request a code (since it won't be needed).
*/
private static Task HandleOpenIdConnectRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification, OpenIdConnectSiteInfo site)
{
Log.Info("Start AzureAdB2CIdentityProviderProcessor -> HandleOpenIdConnectRedirectToIdentityProvider", typeof(AzureAdB2CIdentityProviderProcessor));
string policy = notification.OwinContext.Get<string>("Policy");
if (!string.IsNullOrEmpty(policy) && !policy.Equals(site.SignInPolicyId))
{
notification.ProtocolMessage.Scope = OpenIdConnectScopes.OpenId;
notification.ProtocolMessage.ResponseType = OpenIdConnectResponseTypes.IdToken;
notification.ProtocolMessage.IssuerAddress = notification.ProtocolMessage.IssuerAddress.Replace(site.SignInPolicyId, policy);
}
try
{
AddLanguageQueryString(notification);
}
catch (Exception ex)
{
Log.Error(ex.Message, ex, typeof(OpenIdConnectAuthentication));
}
Log.Info("End AzureAdB2CIdentityProviderProcessor -> HandleOpenIdConnectRedirectToIdentityProvider", typeof(AzureAdB2CIdentityProviderProcessor));
return Task.FromResult(0);
}
private static void AddLanguageQueryString(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
Log.Info("Start AzureAdB2CIdentityProviderProcessor -> AddLanguageQueryString", typeof(AzureAdB2CIdentityProviderProcessor));
string portalUrlCookie = "Project_portal";
string langPrefCookieFormat = "Project_{0}#lang";
string language = string.Empty;
if (notification.Request.Cookies[portalUrlCookie] != null)
{
string portalUrl = notification.Request.Cookies[portalUrlCookie];
int pos = portalUrl.LastIndexOf("/") + 1;
string locale = portalUrl.Substring(pos, portalUrl.Length - pos);
language = locale.Split('-')?[0];
}
string refererUrl = notification.Request.Headers["referer"];
string country = refererUrl.Split('/')[3];
string langPrefCookie = string.Format(langPrefCookieFormat, country);
if (notification.Request.Cookies[langPrefCookie] != null)
{
string prefLang = notification.Request.Cookies[langPrefCookie].Split('-')[0];
if (!string.IsNullOrEmpty(prefLang))
{
language = prefLang;
}
else
{
language = refererUrl.Split('/')[4].Split('-')[0];
}
}
if (!string.IsNullOrEmpty(language))
notification.ProtocolMessage.Parameters.Add("ui_locales", language);
Log.Info("End AzureAdB2CIdentityProviderProcessor -> AddLanguageQueryString", typeof(AzureAdB2CIdentityProviderProcessor));
}
private static Task HandleOpenIdConnectAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context, OpenIdConnectSiteInfo site)
{
Log.Info("Start AzureAdB2CIdentityProviderProcessor -> HandleOpenIdConnectAuthenticationFailed", typeof(AzureAdB2CIdentityProviderProcessor));
if (context.Exception.Message.Contains("IDX21323"))
{
context.HandleResponse();
context.OwinContext.Authentication.Challenge();
}
else
{
context.HandleResponse();
Log.Fatal(context.Exception.Message, context.Exception, typeof(OpenIdConnectAuthentication));
UrlString errorUrl = new UrlString(site.ErrorUri);
errorUrl.Add("message", context.Exception.Message);
context.Response.Redirect(errorUrl.ToString());
}
Log.Info("End AzureAdB2CIdentityProviderProcessor -> HandleOpenIdConnectAuthenticationFailed", typeof(AzureAdB2CIdentityProviderProcessor));
return Task.FromResult(0);
}
}
}
Update 1 In the logs I am getting below error -
ERROR Unable to find "idp" claim in the identity. Make sure that "Sitecore.Owin.Authentication.Services.SetIdpClaimTransform" or analogue is used in claim transformations of all identity providers.
Tried adding below transformation in config, but still no luck.
<transformation name="Idp Claim" type="Sitecore.Owin.Authentication.Services.SetIdpClaimTransform, Sitecore.Owin.Authentication" />
ApplyClaimsTransformations
) is commented out, you still get the error in log if you uncomment that?