I have the federated authentication working in Sitecore 9 with a custom external provider, and I see the ExternalCookie being set. However, when I make another request to a secured area (a controller rendering with [Authorize] attribute over an action) - then I'm being funneled through the authentication again. If I check this on an non-secure controller rendering - I can see my sitecore user is extranet\anonymous while debugging.
However if I get the owin context and manually authenticate - I can retrieve the user and their claims from the result...
var authenticateResult = HttpContext.GetOwinContext().Authentication.AuthenticateAsync("ExternalCookie");
This returns the user which authenticated previously with the correct claims...
Overall (for context) I'm trying to setup this site so that a user can view pages in either anonymous or authenticated - displaying different content for either.
Should I be making the cookie authentication active in the sitecore pipeline? Or is there something else I should be doing to re-populate (or re-authenticate) the User on subsequent requests...
** EDIT - Including Sitecore.Owin.Authentication.config
<?xml version="1.0" encoding="utf-8"?>
<setting name="FederatedAuthentication.IdentityProcessingPathPrefix" value="/identity/" />
<!-- CACHING OWIN TRANSFORMED IDENTITIES CACHE SIZE
Determines the size of the TransformedIdentitiesCacheSize cache, which improves
the performance of the Bearer Token authentication.
Specify the value in bytes or append the value with KB, MB or GB
Default value: 50MB-->
<setting name="Caching.Owin.TransformedIdentitiesCacheSize" value="50MB" />
</settings>
<services>
<configurator type="Sitecore.Owin.Authentication.Infrastructure.ServicesConfigurator, Sitecore.Owin.Authentication" />
<configurator type="Sitecore.Owin.Authentication.Client.Infrastructure.ServicesConfigurator, Sitecore.Owin.Authentication.Client" />
</services>
<pipelines>
<httpRequestBegin>
<processor type="Sitecore.Pipelines.HttpRequest.IgnoreList, Sitecore.Kernel">
<prefixes>
<prefix name="identityProcessing">/identity/</prefix>
</prefixes>
</processor>
</httpRequestBegin>
<owin.initialize>
<processor type="Sitecore.Owin.Authentication.Pipelines.Initialize.DefineOwinAuthenticationState, Sitecore.Owin.Authentication"
resolve="true" patch:after="processor[@type='Sitecore.Owin.Pipelines.Initialize.SetLoggerFactory, Sitecore.Owin']" />
<processor type="Sitecore.Owin.Authentication.Pipelines.Initialize.ValidateFederatedAuthenticationState, Sitecore.Owin.Authentication"
resolve="true" patch:before="processor[@method='Authenticate']" />
<processor type="Sitecore.Owin.Authentication.Pipelines.Initialize.InjectOwinDependencies, Sitecore.Owin.Authentication"
resolve="true" patch:before="processor[@method='Authenticate']" />
<processor type="Sitecore.Owin.Authentication.Pipelines.Initialize.CookieAuthentication, Sitecore.Owin.Authentication"
resolve="true" patch:before="processor[@method='Authenticate']" />
<processor type="Sitecore.Owin.Authentication.Pipelines.Initialize.PreviewCookieAuthentication, Sitecore.Owin.Authentication"
resolve="true" patch:before="processor[@method='Authenticate']" />
<processor type="Sitecore.Owin.Authentication.Pipelines.Initialize.ExternalCookieAuthentication, Sitecore.Owin.Authentication"
resolve="true" patch:before="processor[@method='Authenticate']" />
<processor type="Sitecore.Owin.Authentication.Pipelines.Initialize.AddIdentityProviders, Sitecore.Owin.Authentication"
resolve="true" patch:before="processor[@method='Authenticate']" />
<processor type="Sitecore.Owin.Authentication.Pipelines.Initialize.WindowsAzureActiveDirectoryBearerAuthentication, Sitecore.Owin.Authentication"
resolve="true" patch:before="processor[@method='Authenticate']">
<IdentityProviderName></IdentityProviderName>
<ApplicationId></ApplicationId>
<TenantId></TenantId>
</processor>
<processor type="Sitecore.Owin.Authentication.Pipelines.Initialize.HandleLoginLink, Sitecore.Owin.Authentication" resolve="true" />
</owin.initialize>
<owin.identityProviders help="Processors should derive from Sitecore.Owin.Authentication.Pipelines.IdentityProviders.IdentityProvidersProcessor">
<!--Place here the provider initialize class. You can use the custom config file in Include folder - just create new one or use existed.
For example:
<processor type="Sitecore.Owin.Authentication.Samples.AzureAd.Pipelines.IdentityProviders.AzureAd, Sitecore.Owin.Authentication.Samples.AzureAd" resolve="true" />-->
<processor type="CustomProvider.Authentication.GatewayIdentityProviderProcessor, CustomProvider" resolve="true" />
</owin.identityProviders>
<owin.cookieAuthentication.validateIdentity help="Processors should derive from Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.ValidateIdentity.ValidateIdentityProcessor">
<processor type="Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.ValidateIdentity.ValidateForUnresolvedSiteContext, Sitecore.Owin.Authentication" />
<processor type="Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.ValidateIdentity.CheckIdentityProvider, Sitecore.Owin.Authentication" resolve="true" />
<processor type="Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.ValidateIdentity.ResolveToDefaultAuthenticationSourceOnly, Sitecore.Owin.Authentication">
<paths hint="list">
</paths>
</processor>
<processor type="Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.ValidateIdentity.CheckSiteMode, Sitecore.Owin.Authentication" resolve="true" />
</owin.cookieAuthentication.validateIdentity>
<owin.cookieAuthentication.signIn help="Processors should derive from Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignIn.SignInProcessor">
<processor type="Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignIn.RemoveTicket, Sitecore.Owin.Authentication" resolve="true" />
<processor type="Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignIn.ResolveUser, Sitecore.Owin.Authentication" resolve="true" />
<processor type="Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignIn.ClearCache, Sitecore.Owin.Authentication" resolve="true" />
<processor type="Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignIn.UpdateUserProperties, Sitecore.Owin.Authentication" resolve="true" />
<processor type="Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignIn.CheckClientUser, Sitecore.Owin.Authentication" />
</owin.cookieAuthentication.signIn>
<owin.cookieAuthentication.signedIn help="Processors should derive from Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignedIn.SignedInProcessor">
<processor type="Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignedIn.CleanupUserProfile, Sitecore.Owin.Authentication" />
<processor type="Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignedIn.GetStartUrl, Sitecore.Owin.Authentication" resolve="true" />
<processor type="Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignedIn.CreateTicket, Sitecore.Owin.Authentication" resolve="true" />
<processor type="Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignedIn.ApplyRedirect, Sitecore.Owin.Authentication" resolve="true" />
</owin.cookieAuthentication.signedIn>
<owin.cookieAuthentication.applyRedirect help="Processors should derive from Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.ApplyRedirect.ApplyRedirectProcessor">
<processor type="Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.ApplyRedirect.GoToLoginPage, Sitecore.Owin.Authentication" />
</owin.cookieAuthentication.applyRedirect>
<owin.cookieAuthentication.signOut>
<processor type="Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignOut.CheckUser, Sitecore.Owin.Authentication" />
<processor type="Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignOut.ClearRecentDocuments, Sitecore.Owin.Authentication" />
<processor type="Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignOut.RemoveTicket, Sitecore.Owin.Authentication" resolve="true" />
<processor type="Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignOut.ClearCache, Sitecore.Owin.Authentication" resolve="true" />
</owin.cookieAuthentication.signOut>
<getSignInUrlInfo>
<processor type="Sitecore.Owin.Authentication.Pipelines.GetSignInUrlInfo.GetExternalSignInUrlInfo, Sitecore.Owin.Authentication" resolve="true" />
</getSignInUrlInfo>
</pipelines>
<federatedAuthentication type="Sitecore.Owin.Authentication.Configuration.FederatedAuthenticationConfiguration, Sitecore.Owin.Authentication">
<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>modules_website</site>
<site>website</site>
<site>shell</site>
</sites>
<identityProviders hint="list:AddIdentityProvider">
<identityProvider ref="federatedAuthentication/identityProviders/identityProvider[@id='Custom']" />
</identityProviders>
<externalUserBuilder type="Sitecore.Owin.Authentication.Services.DefaultExternalUserBuilder, Sitecore.Owin.Authentication">
<param desc="isPersistentUser">false</param>
</externalUserBuilder>
</mapEntry>
-->
<!--See below how to add a provider.-->
</identityProvidersPerSites>
<identityProviders hint="list:AddIdentityProvider">
<!--To add a provider you can use the custom config file in Include folder - just create new one or use existed.
Under configuration/sitecore/federatedAuthentication/identityProviders create a new node with name identityProvider. Fill id, type attributes.
Under created node fill child nodes: param, caption, domain, transformations.
About transformations: because of the need to have idp claim it is recommended to apply setIdpClaim transformation and then custom one(s).
An example of provider is placed below:-->
<identityProvider id="Custom" 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 Custom</caption>
<icon>/sitecore/shell/themes/standard/Custom/24x24/msazure.png</icon>
<domain>sitecore</domain>
<transformations hint="list:AddTransformation">
<!-- <transformation name="set idp claim" ref="federatedAuthentication/sharedTransformations/setIdpClaim" /> -->
<transformation type="Sitecore.Owin.Authentication.Services.DefaultTransformation, Sitecore.Owin.Authentication">
<sources hint="raw:AddSource">
<claim name="idp" value="Custom" />
</sources>
<targets hint="raw:AddTarget">
<claim name="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" value="sitecore\Developer" />
</targets>
<keepSource>false</keepSource>
</transformation>
</transformations>
</identityProvider>
</identityProviders>
<!-- List of all shared transformations-->
<sharedTransformations hint="list:AddSharedClaimsTransformation">
<!--Adds idp claim to identity. Every provider should reference to it-->
<setIdpClaim name="setIdpClaim" type="Sitecore.Owin.Authentication.Services.SetIdpClaimTransform, Sitecore.Owin.Authentication"/>
</sharedTransformations>
<propertyInitializer type="Sitecore.Owin.Authentication.Services.PropertyInitializer, Sitecore.Owin.Authentication">
<maps hint="list">
</maps>
</propertyInitializer>
</federatedAuthentication>
</sitecore>
* Edit * Adding IdentityProvidersProcessor
public class CustomProvider : IdentityProvidersProcessor
{
public CustomProvider(FederatedAuthenticationConfiguration federatedAuthenticationConfiguration) : base(federatedAuthenticationConfiguration)
{
}
protected override string IdentityProviderName => "Custom";
protected override void ProcessCore(IdentityProvidersArgs args)
{
Assert.ArgumentNotNull(args, "args");
IdentityProvider identityProvider = this.GetIdentityProvider();
string authenticationType = this.GetAuthenticationType();
var options = new CustomGatewayOptions()
{
AuthenticationType = authenticationType,
BackchannelTimeout = TimeSpan.FromSeconds(60),
CallbackPath = new PathString(@"/auth"),
Endpoints = new CustomGatewayOptions.CustomAuthenticationEndpoints()
{
AuthorizationEndpoint = "https://oauthserver/authorize",
TokenEndpoint = "https://oauthserver/token",
UserInfoEndpoint = "https://info/user/"
},
Provider = new CustomAuthenticationProvider(),
ClientID = "xxx",
ClientSecret = "xxxx",
Scope = new List<string>() { "xxxx" }
};
options.Provider.OnAuthenticated = (context) =>
{
context.Identity.ApplyClaimsTransformations(new TransformationContext(this.FederatedAuthenticationConfiguration, identityProvider));
return Task.CompletedTask;
};
args.App.UseCustomAuthentication(options);
}
}
* EDIT * - Including Provider Callback Reply - heavily copied from Twitch OWIN Provider
private async Task<bool> InvokeReplyPathAsync()
{
if (!Options.CallbackPath.HasValue || Options.CallbackPath != Request.Path)
{
return false;
}
var ticket = await AuthenticateAsync();
if (ticket == null)
{
_logger.WriteWarning("Invalid return state, unable to redirect.");
Response.StatusCode = 500;
return true;
}
var context = new APIGatewayReturnEndpointContext(Context, ticket)
{
SignInAsAuthenticationType = Options.SignInAsAuthenticationType,
RedirectUri = ticket.Properties.RedirectUri
};
await Options.Provider.ReturnEndpoint(context);
if (context.SignInAsAuthenticationType != null && context.Identity != null)
{
var grantIdentity = context.Identity;
if (!string.Equals(grantIdentity.AuthenticationType, context.SignInAsAuthenticationType, StringComparison.Ordinal))
{
grantIdentity = new ClaimsIdentity(grantIdentity.Claims, context.SignInAsAuthenticationType, grantIdentity.NameClaimType, grantIdentity.RoleClaimType);
}
Context.Authentication.SignIn(context.Properties, grantIdentity);
}
if (context.IsRequestCompleted || context.RedirectUri == null) return context.IsRequestCompleted;
var redirectUri = context.RedirectUri;
if (context.Identity == null)
{
// add a redirect hint that sign-in failed in some way
redirectUri = WebUtilities.AddQueryString(redirectUri, "error", "access_denied");
}
Response.Redirect(redirectUri);
context.RequestCompleted();
return context.IsRequestCompleted;
}
** EDIT - Adding picture of final request with external cookie (my Authentication provider redirects to '/' after logging in the user - this request seems to ignore the External Authentication cookie and cause a 401 status to return (which kicks up my authentication into a continuous loop)