6

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) Picture

2
  • So after authenticating with the custom external provider do you create a Sitecore virtual user at all?
    – TomT
    Mar 1, 2018 at 1:16
  • I do not - BUT - I believe according to the current sitecore 9 blogs I'm reading that happens automatically (but I could be wrong). On stackoverflow - this answer seems to say it happens automatically in the pipeline - stackoverflow.com/questions/47405473/…
    – Watson
    Mar 1, 2018 at 17:41

1 Answer 1

4
+100

Treat it as a comment. I don't have enough reputation to make a real one.

Have you specified allowed sites for your external identity provider in the corresponding federatedAuthentication/identityProvidersPerSites/mapEntry node? This node must have sites sub node that contains a list of site names for which users will have access if they are authenticated by the providers from the identityProviders subnode node of the mapEntry node.

<sites hint="list">
    <site>website</site>
    <site>shell</site>
</sites>

If it does not help, then it would be much faster if you show your FedAuth-related config and your processor where you configure your external identity provider.

@Watson I do not - BUT - I believe according to the current sitecore 9 blogs I'm reading that happens automatically (but I could be wrong)

You're correct. It happens automatically.


** EDIT New comments

  1. External cookie middleware must be passive. You may forget about external cookies at all. Those cookies are short-lived cookies to temporary store Identity object and some properties from the external providers. They should exists only for one request or so.
  2. I see <keepSource>false</keepSource> when you transform the idp claim. It means that the idp claim will be removed during claim transformation. It must be preserved! Change keepSource to true. Actually, if an Identity does not have an idp claim, Sitecore have to throw an exception. It's hard to say, why exactly you have not seen an exception with your config (look at the Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.ValidateIdentity processor) wihout seeing the whole solution.
  3. Check whether adding sc_site=website url parameter to the URI that you request will make any changes for you. Chances are that your requested path is resolved as non-website site.
  4. Besides idp claim issue, your config is OK (assuming that the Sitecore.Owin.Authentication.Enabler.config.example config file is enabled), so can you add the code of your GatewayIdentityProviderProcessor for further investigation if fix to the keepSource does not help?
20
  • <sites hint="list"> <site>modules_website</site> <site>website</site> <site>shell</site> </sites> Thanks Vyacheslav! I will dump my FedAuth.config into the main post.
    – Watson
    Mar 4, 2018 at 15:07
  • FYI -- Dumped the config into the main question - thanks again. It still feels like the external cookie provider isn't firing - is there a way to set it to be Active in the OWIN pipeline?
    – Watson
    Mar 5, 2018 at 2:56
  • None of these worked -- I can't share the exact code of the provider since it's custom - BUT it's almost an exact duplicate of the Twitch OAuth provider: github.com/TerribleDev/OwinOAuthProviders/tree/master/src/…
    – Watson
    Mar 6, 2018 at 14:27
  • My guess is there is something in the provider callback - I will post my Owin Options for my provider and the code that gets invoked on the "reply" to the callback path sent to the OAuth server.
    – Watson
    Mar 6, 2018 at 14:44
  • 1
    > "then my middleware handler redirects them back to the original secured page" - bingo! That's wrong. Show me the login url that you are using. If you use the 'getSignInUrlInfo' pipeline, then the return url must be similar to this "/identity/externallogincallback?returnurl=your_secured_page...." (I don't have a running Sitecore instance right now to check), so you must be redirected to the middleware that serves the "/identity/externallogincallback" path (jfyi: this processor: Sitecore.Owin.Authentication.Pipelines.Initialize.HandleLoginLink, method: HandleExternalLoginCallbackUrl) Mar 8, 2018 at 22:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.