I have issue with configuration of OpenID Connect with Sitecore Federated Authentication. On the final step of login process in the call to /identity/externallogincallback the cookies are missing. I do not understand it is caused by my configuration or I missing something. The description is shown below.
Issue: Federated Authentication with OpenID Connect is not working. After typing credentials error showed below occurs:
ERROR Unable to get and an external login info via Microsoft.Owin.Security.AuthenticationManagerExtensions.GetExternalLoginInfoAsync. Most probably the identity does not have a 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier' claim.
The final /identity/externallogincallback request does not contain any cookies required for authentication. Then the authentication returns failure.
Description It looks the login process is working correct to the final step. The method OnSecurityTokenValidated inside OpenIDIdentityProviderProcessor is invoked and run without errors. The claims are loaded correctly and the debuger says that user is authenticated.
From the debugging I see that the login process is correct, then the /identity/signin-openID POST is called (it is set as redirect URI). It is called without any cookies. Then the /identity/externalcallogincallback is set and there is also any cookies into that request. It causes that inside the Sitecore.Owin.Authentication.Pipelines.Initialize.HandleLoginLink.HandleExternalLoginCallbackUrl the code
await context.Authentication.GetExternalLoginInfoWithWhitespacesAsync().ConfigureAwait(false);
is returning null and then the error Error: Unsuccessful login with external provider. is shown on page.
Nuget Packages:
- Sitecore.Owin 9.1.1
- Sitecore.Owin.Authentication 9.1.0
- Microsoft.Owin.Security.OpenIdConnect 4.0.0
The redirectURI is defined as:
<!-- your Sitecore instance Url-->
<setting name="OpenID.RedirectUri" role:require="Standalone or ContentManagement">
<patch:attribute name="value">https://site.sc/signin-openID</patch:attribute>
</setting>
The federated authentication config is shown below. The Identity server is disabled.
<federatedAuthentication>
<identityProviders hint="list:AddIdentityProvider">
<!--<identityProvider id="SitecoreIdentityServer">
<enabled>false</enabled>
</identityProvider>-->
<identityProvider id="openID" type="Sitecore.Owin.Authentication.Configuration.DefaultIdentityProvider, Sitecore.Owin.Authentication">
<param desc="name">$(id)</param>
<param desc="domainManager" type="Sitecore.Abstractions.BaseDomainManager" resolve="true" />
<caption>Open ID</caption>
<domain>ActiveDirectory</domain>
<icon>/sitecore/shell/themes/standard/Images/24x24/msazure.png</icon>
<transformations hint="list:AddTransformation">
<!-- IDP Transformation -->
<transformation name="Idp Claim" type="Sitecore.Owin.Authentication.Services.SetIdpClaimTransform, Sitecore.Owin.Authentication" />
<!-- Name Identifier Claim-->
<transformation name ="Name identifier claim" type="Sitecore.Owin.Authentication.Services.DefaultTransformation, Sitecore.Owin.Authentication">
<sources hint="raw:AddSource">
<claim name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" />
</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>
<identityProvidersPerSites>
<mapEntry name="openID" type="Sitecore.Owin.Authentication.Collections.IdentityProvidersPerSitesMapEntry, Sitecore.Owin.Authentication">
<sites hint="list">
<site>shell</site>
<site>login</site>
<site>admin</site>
<site>service</site>
<site>modules_shell</site>
<site>modules_website</site>
<site>website</site>
<site>scheduler</site>
<site>system</site>
<site>publisher</site>
<site>website</site>
</sites>
<!-- Registered identity providers for above providers -->
<identityProviders hint="list:AddIdentityProvider">
<identityProvider ref="federatedAuthentication/identityProviders/identityProvider[@id='openID']" />
</identityProviders>
<!-- ExternalUserBuilder is what creates a user with customusername in Sitecore and assigns roles based on claim transformation configured above -->
<externalUserBuilder type="Sitecore.Owin.Authentication.Services.DefaultExternalUserBuilder, Sitecore.Owin.Authentication">
<param desc="isPersistentUser">true</param>
</externalUserBuilder>
</mapEntry>
</identityProvidersPerSites>
<!-- Property initializer assigns claim values to sitecore user properties -->
<propertyInitializer type="Sitecore.Owin.Authentication.Services.PropertyInitializer, Sitecore.Owin.Authentication">
<maps hint="list">
<map name="map Roles" type="Sitecore.Owin.Authentication.Services.DefaultClaimToPropertyMapper, Sitecore.Owin.Authentication">
<data hint="raw:AddData">
<source name="http://schemas.microsoft.com/ws/2008/06/identity/claims/role"/>
<target name="Roles"/>
</data>
</map>
<map name="set idp" type="Sitecore.Owin.Authentication.Services.DefaultClaimToPropertyMapper, Sitecore.Owin.Authentication">
<data hint="raw:AddData">
<source name="http://schemas.microsoft.com/identity/claims/objectidentifier"/>
<target name="idp"/>
</data>
</map>
<map name="set email" type="Sitecore.Owin.Authentication.Services.DefaultClaimToPropertyMapper, Sitecore.Owin.Authentication">
<data hint="raw:AddData">
<source name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"/>
<target name="Email"/>
</data>
</map>
<map name="set name" type="Sitecore.Owin.Authentication.Services.DefaultClaimToPropertyMapper, Sitecore.Owin.Authentication">
<data hint="raw:AddData">
<source name="name"/>
<target name="Name"/>
</data>
</map>
<map name="set FullName" type="Sitecore.Owin.Authentication.Services.DefaultClaimToPropertyMapper, Sitecore.Owin.Authentication">
<data hint="raw:AddData">
<source name="name"/>
<target name="FullName"/>
</data>
</map>
</maps>
</propertyInitializer>
</federatedAuthentication>
public class OpenIDIdentityProviderProcessor : IdentityProvidersProcessor
{
private readonly IActiveDirectoryCrudService _activeDirectoryService;
public string AadInstance => Settings.GetSetting("OpenID.AADInstance");
public string Tenant => Settings.GetSetting("OpenID.Tenant");
public string ClientId => Settings.GetSetting("OpenID.ClientId");
public string PostLogoutRedirectUri => Settings.GetSetting("OpenID.PostLogoutRedirectUri");
public string RedirectUri => Settings.GetSetting("OpenID.RedirectUri");
public string Authority => string.Format(CultureInfo.InvariantCulture, AadInstance, Tenant);
Identity provider code:
public class OpenIDIdentityProviderProcessor : IdentityProvidersProcessor
{
private readonly IActiveDirectoryCrudService _activeDirectoryService;
public string AadInstance => Settings.GetSetting("OpenID.AADInstance");
public string Tenant => Settings.GetSetting("OpenID.Tenant");
public string ClientId => Settings.GetSetting("OpenID.ClientId");
public string PostLogoutRedirectUri => Settings.GetSetting("OpenID.PostLogoutRedirectUri");
public string RedirectUri => Settings.GetSetting("OpenID.RedirectUri");
public string Authority => string.Format(CultureInfo.InvariantCulture, AadInstance, Tenant);
public OpenIDIdentityProviderProcessor(
FederatedAuthenticationConfiguration federatedAuthenticationConfiguration,
ICookieManager cookieManager,
BaseSettings settings,
IActiveDirectoryCrudService activeDirectoryCrudService)
: base(federatedAuthenticationConfiguration, cookieManager, settings)
{
_activeDirectoryService = activeDirectoryCrudService;
}
public static string OpenIdIdentityProviderName = "openID";
protected override string IdentityProviderName => OpenIdIdentityProviderName;
protected override void ProcessCore(IdentityProvidersArgs args)
{
Assert.ArgumentNotNull(args, nameof(args));
IdentityProvider identityProvider = GetIdentityProvider();
string authenticationType = GetAuthenticationType();
var options = new OpenIdConnectAuthenticationOptions
{
Authority = Authority,
AuthenticationType = authenticationType,
AuthenticationMode = AuthenticationMode.Passive,
Caption = identityProvider.Caption,
ClientId = ClientId,
CookieManager = CookieManager,
RedirectUri = RedirectUri,
PostLogoutRedirectUri = PostLogoutRedirectUri,
ResponseType = "id_token",
Scope = "openid", // email profile address phone groups offline_access
UseTokenLifetime = true,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
SecurityTokenValidated = OnSecurityTokenValidated
}
};
args.App.CreatePerOwinContext(() => new LoggedInUserProfileService());
args.App.UseOpenIdConnectAuthentication(options);
}
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
Log.Error($"FederatedLogin: AuthenticationFailed {context.Exception.Message}", context.Exception, this);
return Task.CompletedTask;
}
private Task OnSecurityTokenValidated(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
var identityProvider = GetIdentityProvider();
var identity = context.AuthenticationTicket.Identity;
var claimsIdentity = new ClaimsIdentity(identity.Claims, GetAuthenticationType());
// Run Transformations
foreach (var claimsTransformations in identityProvider.Transformations)
{
claimsTransformations.Transform(claimsIdentity, new TransformationContext(FederatedAuthenticationConfiguration, identityProvider));
}
claimsIdentity.AddClaim(new Claim("id_token", context.ProtocolMessage.IdToken));
claimsIdentity.AddClaim(new Claim(ActiveDirectoryClaimTypes.PrivateNameIdentifier, identity.Claims.FirstOrDefault(x => x.Type == ActiveDirectoryClaimTypes.NameIdentifier).Value));
context.AuthenticationTicket = new AuthenticationTicket(claimsIdentity, context.AuthenticationTicket.Properties);
return Task.CompletedTask;
}
}