I am on Sitecore 9.1 Initial Release and SXA 1.8. Implemented a custom processor for Azure ADB2C Login. I can login perfectly and fetch the claims and stuff. But on redirect to /myaccount page , I need to check if Sitecore.Context.User.IsAuthenticated , but this is set to false. I added a B2CHelper to Create and Login Virtual User , but the Sitecore.Context.User.IsAuthenticated is always false.
1- How do I set the Context on redirect from IDP, so that I can get the details of logged in User.
2- Is the below required in Sitecore 9.1 Sitecore.Security.Authentication.AuthenticationManager.BuildVirtualUser
and Sitecore.Security.Authentication.AuthenticationManager.LoginVirtualUser(virtualUser);
Have raised a Support ticket as well. Below code for Custom Processor
public class AzureAdB2CIdentityProviderProcessor : IdentityProvidersProcessor
{
// App config settings
public static string ClientId = Sitecore.Configuration.Settings.GetSetting("ida:ClientId");
public static string ClientSecret = Sitecore.Configuration.Settings.GetSetting("ida:ClientSecret");
public static string AadInstance = Sitecore.Configuration.Settings.GetSetting("ida:AadInstance");
public static string Tenant = Sitecore.Configuration.Settings.GetSetting("ida:Tenant");
public static string RedirectUri = Sitecore.Configuration.Settings.GetSetting("ida:RedirectUri");
// B2C policy identifiers
public static string SignUpPolicyId = Sitecore.Configuration.Settings.GetSetting("ida:SignUpPolicyId");
public static string SignInPolicyId = Sitecore.Configuration.Settings.GetSetting("ida:SignInPolicyId");
public static string SignUpSignInPolicyId = Sitecore.Configuration.Settings.GetSetting("ida:SignUpSignInPolicyId");
public static string EditProfilePolicyId = Sitecore.Configuration.Settings.GetSetting("ida:EditProfilePolicyId");
public static string ResetPasswordPolicyId = Sitecore.Configuration.Settings.GetSetting("ida:ResetPasswordPolicyId");
public static bool RequireHttps = bool.Parse(Sitecore.Configuration.Settings.GetSetting("RequireHttps"));
public static string DefaultPolicy = SignInPolicyId;
// API Scopes
public static string ApiIdentifier = Sitecore.Configuration.Settings.GetSetting("api:ApiIdentifier");
public static string ReadTasksScope = ApiIdentifier + Sitecore.Configuration.Settings.GetSetting("api:ReadScope");
public static string WriteTasksScope = ApiIdentifier + Sitecore.Configuration.Settings.GetSetting("api:WriteScope");
public static string[] Scopes = new string[] { ReadTasksScope, WriteTasksScope };
// OWIN auth middleware constants
public const string ObjectIdElement = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
// Authorities
public static string Authority = String.Format(AadInstance, Tenant, DefaultPolicy);
public static string WellKnownMetadata = $"{AadInstance}/v2.0/.well-known/openid-configuration";
private static string[] redirectIgnoreUrls = new string[] { };
public static string _defaultScope = String.Concat(ApiIdentifier, "/", Sitecore.Configuration.Settings.GetSetting("api:DefaultScope"));
private static string[] _scopes = new string[] { _defaultScope }; //, ReadTasksScope, WriteTasksScope };
public AzureAdB2CIdentityProviderProcessor(FederatedAuthenticationConfiguration federatedAuthenticationConfiguration, ICookieManager cookieManager, BaseSettings settings)
: base(federatedAuthenticationConfiguration, cookieManager, settings)
{
}
protected override string IdentityProviderName
{
get { return "AzureAdB2C"; }
}
protected override void ProcessCore(IdentityProvidersArgs args)
{
// Required for Azure webapps, as by default they force TLS 1.2 and this project attempts 1.0
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
Assert.ArgumentNotNull(args, nameof(args));
var identityProvider = this.GetIdentityProvider();
var authenticationType = this.GetAuthenticationType();
args.App.UseCookieAuthentication(new CookieAuthenticationOptions());
args.App.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(EditProfilePolicyId));
args.App.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(ResetPasswordPolicyId));
args.App.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(SignUpPolicyId));
args.App.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(SignInPolicyId));
}
private OpenIdConnectAuthenticationOptions CreateOptionsFromPolicy(string policy)
{
var identityProvider = GetIdentityProvider();
return new OpenIdConnectAuthenticationOptions
{
// For each policy, give OWIN the policy-specific metadata address, and
// set the authentication type to the id of the policy
MetadataAddress = String.Format(WellKnownMetadata, Tenant, policy),
//AuthenticationType = GetAuthenticationType(),
AuthenticationType = policy,
AuthenticationMode = AuthenticationMode.Passive,
RequireHttpsMetadata = RequireHttps,
// These are standard OpenID Connect parameters, with values pulled from settings
ClientId = ClientId,
// Scope = "openid profile offline_access",
Scope = $"openid profile offline_access { _defaultScope }",
//ResponseType = "id_token",
RedirectUri = RedirectUri, //SK RedirectIframeUri
PostLogoutRedirectUri = RedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthenticationFailed = OnAuthenticationFailed,
SecurityTokenValidated = OnSecurityTokenValidated,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
},
// This piece is optional - it is used for displaying the user's name in the navigation bar.
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
ValidateIssuer = false
},
};
}
private Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var policy = notification.OwinContext.Get<string>("Policy");
if (!string.IsNullOrEmpty(policy) && !policy.Equals(DefaultPolicy))
{
notification.ProtocolMessage.Scope = OpenIdConnectScope.OpenId;
notification.ProtocolMessage.ResponseType = OpenIdConnectResponseType.IdToken;
notification.ProtocolMessage.IssuerAddress = notification.ProtocolMessage.IssuerAddress.ToLower().Replace(DefaultPolicy.ToLower(), policy.ToLower());
notification.ProtocolMessage.UiLocales = notification.ProtocolMessage.UiLocales ?? string.Empty;
}
return Task.FromResult(0);
}
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
context.HandleResponse();
if (context.ProtocolMessage.ErrorDescription != null && context.ProtocolMessage.ErrorDescription.Contains("AADB2C90118"))
{
// If the user clicked the reset password link, redirect to the reset password route
context.Response.Redirect("/Account/ResetPassword");
}
else if (context.Exception.Message == "access_denied")
{
// If the user canceled the sign in, redirect back to the home page
context.Response.Redirect("/");
}
else if (context.Exception is HttpException httpException)
{
LogExceptionError("Startup", httpException);
if (httpException?.WebEventCode == 404)
{
context.Response.Redirect("/SplashPage");
}
context.Response.Redirect("/Error?ErrorMessage=" + httpException?.Message);
}
else
{
string errorMessage = (!String.IsNullOrEmpty(context.ProtocolMessage.Error)) ? context.ProtocolMessage.ErrorDescription : context.Exception.Message;
LogExceptionError("Startup", new Exception(errorMessage, context.Exception));
context.Response.Redirect("/Error?ErrorMessage=" + errorMessage);
}
return Task.FromResult(0);
}
private Task OnSecurityTokenValidated(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
//Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
notification.AuthenticationTicket.Identity.AddClaim(new System.Security.Claims.Claim("idp", "AzureAdB2C"));
// transform all claims
ClaimsIdentity identity = notification.AuthenticationTicket.Identity;
notification.AuthenticationTicket.Identity.ApplyClaimsTransformations(new TransformationContext(FederatedAuthenticationConfiguration, GetIdentityProvider()));
return Task.FromResult(0);
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
try
{
//Login User Virtually
//We need to get the token and other post data that B2C sent us
var idToken = HttpContext.Current.Request.Form["id_token"];
var idstate = HttpContext.Current.Request.Form["state"];
Dictionary<string, string> tokenClaims = new Dictionary<string, string>();
foreach (var c in notification.AuthenticationTicket.Identity.Claims)
{
tokenClaims.Add(c.Type, c.Value);
}
//bool loggedin = this.CreateAndLoginUser(tokenClaims["given_name"], tokenClaims["given_name"] + " " +tokenClaims["family_name"], tokenClaims);
bool loggedin = B2CHelper.CreateAndLoginVirtualUser(tokenClaims["name"], tokenClaims["name"], tokenClaims, out Sitecore.Security.Accounts.User currentUser);
}
catch (Exception ex)
{
LogExceptionError("AzureAbB2cIdentityProviderProcesssor ", ex);
throw ex;
}
}
private void LogExceptionError(string methodName, Exception ex)
{
string errorMessage = $"Caught internal exception: /n Calling method: {methodName} /n Exception Message: {ex.Message} /n Exception inner details: {ex.InnerException} /n Exception stacktrace: {ex.StackTrace}";
Trace.TraceError(errorMessage);
}
}
//Patch config
<pipelines>
<owin.identityProviders>
<!--This is the custom processor that gets executed when Azure AD posts the token to Sitecore-->
<processor type="Project.Feature.LoginForm.Pipelines.AzureAdB2CIdentityProviderProcessor, Project.Feature.LoginForm" 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>-->
<identityProvidersPerSites hint="list:AddIdentityProvidersPerSites">
<!--To add a providers per sites configuration you can use the custom config file in Include folder - just create new one or use existed.
Under configuration/sitecore/federatedAuthentication/identityProvidersPerSites create a new node with name mapEntry. Fill name, type attributes.
The value of the name attribute should be unique for each entry.
Under created node fill child nodes: sites (list of sites where the provider(s) will work), identityProviders (list of providers), externalUserBuilder.
An example of map entry where the AzureAd provider could be used with shell, admin and website sites:-->
<mapEntry name="0" type="Sitecore.Owin.Authentication.Collections.IdentityProvidersPerSitesMapEntry, Sitecore.Owin.Authentication">
<sites hint="list">
<site>website</site>
</sites>
<identityProviders hint="list:AddIdentityProvider">
<identityProvider ref="federatedAuthentication/identityProviders/identityProvider[@id='AzureAdB2C']" />
</identityProviders>
<externalUserBuilder type="Sitecore.Owin.Authentication.Services.DefaultExternalUserBuilder, Sitecore.Owin.Authentication">
<param desc="isPersistentUser">true</param>
</externalUserBuilder>
</mapEntry>
<!--See below how to add a provider.-->
</identityProvidersPerSites>
<!--Definitions of providers-->
<identityProviders hint="list:AddIdentityProvider">
<identityProvider id="AzureAdB2C" type="Sitecore.Owin.Authentication.Configuration.DefaultIdentityProvider, Sitecore.Owin.Authentication">
<param desc="name">$(id)</param>
<param desc="domainManager" type="Sitecore.Abstractions.BaseDomainManager" resolve="true" />
<caption>AzureAdB2C</caption>
<domain>AzureAdB2C</domain>
<transformations hint="list:AddTransformation">
<transformation name="Name Identifier Claim" 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>