{
"typ": "JWT",
"alg": "RS256",
"kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
"exp": 1646850084,
"nbf": 1646846484,
"ver": "1.0",
"iss": "https://azureb2cmydemo.b2clogin.com/d227dc46-1f9c-4a46-97a2-d3b83ed89a3a/v2.0/",
"sub": "b5bb6799-77f9-43a6-b6bf-15eaff761600",
"aud": "d1400d7d-a389-4c38-a36f-327e8e949017",
"nonce": "defaultNonce",
"iat": 1646846484,
"auth_time": 1646846484,
"oid": "b5bb6799-77f9-43a6-b6bf-15eaff761600",
"name": "Test user",
"country": "US",
"given_name": "John",
"family_name": "Smith",
"extension_Roles": "Administrator, Developer",
"emails": [
"useremail@gmail.com"
],
"tfp": "B2C_1_SignUpAndSignIn1"
}.[Signature]
namespace SitecoreAzureB2CDemo.Pipelines
{
public class SignUpAndSignInPipeline : IdentityProvidersProcessor
{
private readonly string _tenant = "azureb2cmydemo.onmicrosoft.com";
private readonly string _aadInstance;
private readonly string _metaAddress;
private readonly string _redirectUri = "https://sitecoreazureb2cdemosc.dev.local/azureb2c";
private readonly string _postLogoutRedirectUri = "https://sitecoreazureb2cdemosc.dev.local/azureb2c";
private readonly string _clientId = "xxx"/>;
private readonly string _clientSecret = "xxx"/>;
private readonly string _scope = "https://azureb2cmydemo.onmicrosoft.com/tasks-api/tasks.read";
protected override string IdentityProviderName => IdentityProviderNames.SignUpAndSignIn;
protected virtual string Policy => "B2C_1_SignUpAndSignIn1";
private readonly HttpClient _client = new HttpClient();
private IdentityProvider IdentityProvider => GetIdentityProvider();
public SignUpAndSignInPipeline(FederatedAuthenticationConfiguration federatedAuthenticationConfiguration,
ICookieManager cookieManager, BaseSettings settings) : base(federatedAuthenticationConfiguration,
cookieManager, settings)
{
var aadInstanceTemplate = "https://azureb2cmydemo.b2clogin.com/{0}/{1}";
_aadInstance = string.Format(aadInstanceTemplate, _tenant, Policy);
_metaAddress = $"{_aadInstance}/v2.0/.well-known/openid-configuration";
}
protected override void ProcessCore(IdentityProvidersArgs args)
{
Assert.ArgumentNotNull(args, nameof(args));
var authenticationType = GetAuthenticationType();
var options = new OpenIdConnectAuthenticationOptions(authenticationType)
{
Caption = IdentityProvider.Caption,
AuthenticationMode = AuthenticationMode.Passive,
RedirectUri = _redirectUri,
PostLogoutRedirectUri = _postLogoutRedirectUri,
ClientId = _clientId,
Authority = _aadInstance,
MetadataAddress = _metaAddress,
UseTokenLifetime = true,
TokenValidationParameters = new TokenValidationParameters { NameClaimType = Claims.Name },
CookieManager = CookieManager,
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
SecurityTokenValidated = OnSecurityTokenValidated,
}
};
args.App.UseOpenIdConnectAuthentication(options);
}
private async Task OnSecurityTokenValidated(
SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> arg)
{
var identity = arg.AuthenticationTicket.Identity;
identity.AddClaim(new Claim(Claims.IdToken, arg.ProtocolMessage.IdToken));
//apply Sitecore claims tranformations
arg.AuthenticationTicket.Identity.ApplyClaimsTransformations(
new TransformationContext(FederatedAuthenticationConfiguration, IdentityProvider));
arg.AuthenticationTicket = new AuthenticationTicket(identity, arg.AuthenticationTicket.Properties);
}
private Task OnRedirectToIdentityProvider(
RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> arg)
{
var owinContext = arg.OwinContext;
var protocolMessage = arg.ProtocolMessage;
if (protocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
{
protocolMessage.Prompt = "login";
}
else if (protocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
protocolMessage.PostLogoutRedirectUri = BuildPostLogoutRedirectUri(owinContext);
}
return Task.CompletedTask;
}
}
}
<appSettings configBuilders="SitecoreAppSettingsBuilder">
<add key="AzureB2C.Tenant" value="azureb2cmydemo.onmicrosoft.com"/>
<add key="AzureB2C.SignUpAndSignInPolicy" value="B2C_1_SignUpAndSignIn1"/>
<add key="AzureB2C.PasswordResetPolicy" value="B2C_1_PasswordReset1"/>
<add key="AzureB2C.ProfileEditingPolicy" value="B2C_1_ProfileEditing1"/>
<add key="AzureB2C.ClientId" value="xxx"/>
<add key="AzureB2C.ClientSecret" value="xxx"/>
<add key="AzureB2C.RedirectUri" value="https://sitecoreazureb2cdemosc.dev.local/azureb2c"/>
<add key="AzureB2C.PostLogoutRedirectUri" value="https://sitecoreazureb2cdemosc.dev.local/azureb2c"/>
<add key="AzureB2C.AzureADInstance" value="https://azureb2cmydemo.b2clogin.com/{0}/{1}"/>
<add key="AzureB2C.AccessTokenUri" value="https://azureb2cmydemo.b2clogin.com/azureb2cmydemo.onmicrosoft.com/{0}/oauth2/v2.0/token"/>
<add key="AzureB2C.Scope" value="https://azureb2cmydemo.onmicrosoft.com/tasks-api/tasks.read" />
</appSettings>
private async Task OnSecurityTokenValidated(
SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> arg)
{
var identity = arg.AuthenticationTicket.Identity;
var result = await GetToken(arg.ProtocolMessage.Code);
identity.AddClaim(new Claim(Claims.IdToken, arg.ProtocolMessage.IdToken));
identity.AddClaim(new Claim(Claims.AccessToken, result.AccessToken));
identity.AddClaim(new Claim(Claims.RefreshToken, result.RefreshToken));
//apply Sitecore claims tranformations
arg.AuthenticationTicket.Identity.ApplyClaimsTransformations(
new TransformationContext(FederatedAuthenticationConfiguration, IdentityProvider));
arg.AuthenticationTicket = new AuthenticationTicket(identity, arg.AuthenticationTicket.Properties);
}
private async Task GetToken(string code)
{
var getTokenUrl = string.Format(AzureB2CConfiguration.AccessTokenUri, Policy);
var dict = new Dictionary<string, string>
{
{ "grant_type", "authorization_code" },
{ "client_id", _clientId },
{ "client_secret", _clientSecret },
{ "scope", $"{_scope} offline_access" },
{ "code", code },
{ "redirect_uri", AzureB2CConfiguration.RedirectUri},
};
var requestBody = new FormUrlEncodedContent(dict);
var response = await _client.PostAsync(getTokenUrl, requestBody);
response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadAsStringAsync();
var responseDto = JsonConvert.DeserializeObject(responseBody);
return responseDto;
}
<sharedTransformations>
<transformation type="Sitecore.Owin.Authentication.Services.DefaultTransformation, Sitecore.Owin.Authentication">
<sources hint="raw:AddSource">
<claim name="emails" />
</sources>
<targets hint="raw:AddTarget">
<claim name="email" />
</targets>
<keepSource>false</keepSource>
</transformation>
</sharedTransformations>
<sharedTransformations>
<transformation name="map roles from Sitecore"
type="SitecoreAzureB2CDemo.Transformations.ClaimsToRolesTransformation, SitecoreAzureB2CDemo"
patch:after="transformation[@type='Sitecore.Owin.Authentication.IdentityServer.Transformations.ApplyAdditionalClaims, Sitecore.Owin.Authentication.IdentityServer']"
resolve="true" />
</sharedTransformations>
public class ClaimsToRolesTransformation : Transformation
{
public override void Transform(ClaimsIdentity identity, TransformationContext context)
{
var claimValue = identity.Claims.GetClaimValue(Claims.Permissions);
if (string.IsNullOrEmpty(claimValue))
{
return;
}
var userPermissions = claimValue.Split(',');
foreach (var userPermission in userPermissions)
{
identity.AddClaim(new Claim(Claims.Role, userPermission));
}
}
}
<propertyInitializer
type="Sitecore.Owin.Authentication.Services.PropertyInitializer, Sitecore.Owin.Authentication">
<maps hint="list">
<map name="emailClaim"
type="Sitecore.Owin.Authentication.Services.DefaultClaimToPropertyMapper, Sitecore.Owin.Authentication"
resolve="true">
<data hint="raw:AddData">
<source name="email" />
<target name="Email" />
</data>
</map>
</propertyInitializer>
public class ExternalDomainUserBuilder : DefaultExternalUserBuilder
{
public ExternalDomainUserBuilder(ApplicationUserFactory applicationUserFactory, IHashEncryption hashEncryption)
: base(applicationUserFactory, hashEncryption)
{
}
public override ApplicationUser BuildUser(UserManager userManager,
ExternalLoginInfo externalLoginInfo)
{
var appUser = base.BuildUser(userManager, externalLoginInfo);
appUser.InnerUser.Profile.Save();
return appUser;
}
private static void MapClaimToCustomProperty(ExternalLoginInfo source, ApplicationUser target, string claim, string propertyName)
{
var property = source.GetClaimValue(claim);
if (!string.IsNullOrEmpty(property))
{
target.InnerUser.Profile.SetCustomProperty(propertyName, property);
}
}
protected override string CreateUniqueUserName(UserManager userManager,
ExternalLoginInfo externalLoginInfo)
{
if (userManager == null)
{
throw new ArgumentNullException(nameof(userManager));
}
if (externalLoginInfo == null)
{
throw new ArgumentNullException(nameof(externalLoginInfo));
}
var identityProvider =
this.FederatedAuthenticationConfiguration.GetIdentityProvider(externalLoginInfo.ExternalIdentity);
if (identityProvider == null)
{
throw new InvalidOperationException("Unable to retrieve identity provider for given identity");
}
var domain = identityProvider.Domain;
var name = externalLoginInfo.GetClaimValue(Claims.Email);
if (string.IsNullOrEmpty(name))
{
return GetDomainUserName(domain, externalLoginInfo.DefaultUserName);
}
return GetDomainUserName(domain, name.Replace(",", ""));
}
private string GetDomainUserName(string domain, string userName)
{
Sitecore.Diagnostics.Log.Info("Azure login user " + userName, this);
return $"{domain}\\{userName}";
}
}
<externalUserBuilder
type="SitecoreAzureB2CDemo.Helpers.ExternalDomainUserBuilder, SitecoreAzureB2CDemo" resolve="true">
<IsPersistentUser>false</IsPersistentUser>
</externalUserBuilder>
private string GetSignInUrl(string identityProviderName, string returnUrl)
{
if (string.IsNullOrEmpty(identityProviderName))
{
throw new ArgumentNullException(nameof(identityProviderName));
}
var pipelineManager = (BaseCorePipelineManager)ServiceLocator.ServiceProvider.GetService(typeof(BaseCorePipelineManager));
var args = new Sitecore.Pipelines.GetSignInUrlInfo.GetSignInUrlInfoArgs(Sitecore.Context.Site.Name, returnUrl);
Sitecore.Pipelines.GetSignInUrlInfo.GetSignInUrlInfoPipeline.Run(pipelineManager, args);
var url = args.Result.FirstOrDefault(x => x.IdentityProvider == identityProviderName);
return url?.Href;
}
/identity/externallogin?authenticationType=SignUpAndSignIn&ReturnUrl=%2fidentity%2fexternallogincallback%3fReturnUrl%3d%252fazureb2c%26sc_site%3dwebsite%26authenticationSource%3dDefault&sc_site=website
<html>
<body onload='sessionStorage.clear(); document.forms[""form""].submit()'>
<form name='form' action='@Model.SignInUrl' method='post'>
<input type="submit" value="Login">
</form>
</body>
</html>
public class PasswordResetPipeline : SignUpAndSignInPipeline
{
public PasswordResetPipeline(FederatedAuthenticationConfiguration federatedAuthenticationConfiguration,
ICookieManager cookieManager, BaseSettings settings) : base(federatedAuthenticationConfiguration,
cookieManager, settings)
{
}
protected override string IdentityProviderName => IdentityProviderNames.PasswordReset;
protected override string Policy => "B2C_1_PasswordReset1";
}
<pipelines>
<owin.identityProviders>
<processor type="SitecoreAzureB2CDemo.Pipelines.SignUpAndSignInPipeline, SitecoreAzureB2CDemo" resolve="true" />
<processor type="SitecoreAzureB2CDemo.Pipelines.PasswordResetPipeline, SitecoreAzureB2CDemo" resolve="true" />
</owin.identityProviders>
</pipelines>
<federatedAuthentication
type="Sitecore.Owin.Authentication.Configuration.FederatedAuthenticationConfiguration, Sitecore.Owin.Authentication">
<identityProvidersPerSites hint="list:AddIdentityProvidersPerSites">
<mapEntry name="Azure AD B2C for website"
type="Sitecore.Owin.Authentication.Collections.IdentityProvidersPerSitesMapEntry, Sitecore.Owin.Authentication" resolve="true">
<sites hint="list">
<site>website</site>
</sites>
<identityProviders hint="list:AddIdentityProvider">
<identityProvider ref="federatedAuthentication/identityProviders/identityProvider[@id='SignUpAndSignIn']" />
<identityProvider ref="federatedAuthentication/identityProviders/identityProvider[@id='PasswordReset']" />
</identityProviders>
<externalUserBuilder
type="SitecoreAzureB2CDemo.Helpers.ExternalDomainUserBuilder, SitecoreAzureB2CDemo" resolve="true">
<IsPersistentUser>false</IsPersistentUser>
</externalUserBuilder>
</mapEntry>
</identityProvidersPerSites>
<identityProviders hint="list:AddIdentityProvider">
<identityProvider id="SignUpAndSignIn" type="Sitecore.Owin.Authentication.Configuration.DefaultIdentityProvider, Sitecore.Owin.Authentication">
<param desc="name">SignUpAndSignIn</param>
<param desc="domainManager" type="Sitecore.Abstractions.BaseDomainManager" resolve="true" />
<caption>SignUpAndSignIn</caption>
<domain>extranet</domain>
<enabled>true</enabled>
<triggerExternalSignOut>true</triggerExternalSignOut>
<transformations hint="list:AddTransformation">
</transformations>
</identityProvider>
<identityProvider id="PasswordReset" type="Sitecore.Owin.Authentication.Configuration.DefaultIdentityProvider, Sitecore.Owin.Authentication">
<param desc="name">PasswordReset</param>
<param desc="domainManager" type="Sitecore.Abstractions.BaseDomainManager" resolve="true" />
<caption>PasswordReset</caption>
<domain>extranet</domain>
<enabled>true</enabled>
<triggerExternalSignOut>true</triggerExternalSignOut>
<transformations hint="list:AddTransformation">
</transformations>
</identityProvider>
</identityProviders>
private string GetSignInUrl(string identityProviderName, string returnUrl)
{
if (string.IsNullOrEmpty(identityProviderName))
{
throw new ArgumentNullException(nameof(identityProviderName));
}
var pipelineManager = (BaseCorePipelineManager)ServiceLocator.ServiceProvider.GetService(typeof(BaseCorePipelineManager));
var args = new Sitecore.Pipelines.GetSignInUrlInfo.GetSignInUrlInfoArgs(Sitecore.Context.Site.Name, returnUrl);
Sitecore.Pipelines.GetSignInUrlInfo.GetSignInUrlInfoPipeline.Run(pipelineManager, args);
var url = args.Result.FirstOrDefault(x => x.IdentityProvider == identityProviderName);
return url?.Href;
}