SSO with multisite

Vote:
 

I implemented SSO to get access to the CMS, it is using Federated security with WS-Federation protocol.

It was working fine until a new client requirement came: they need a new microsite.

What I did, on my local, is to create a new page under the root item and set it as the home page for the new site. 

When I try to edit the new home page, it tries to redirect to another hostname but it seems it’s conflicting with the wtrealm defined and it provokes an infinite loop.

Is there a way we could edit the new site but stay in the same hostname (my local in this case) and avoid those redirects?

Here is a recording of what’s happening: https://share.getcloudapp.com/12uKRZxD

Please, if you can help me and provide some guidance will be much appreciated.

Thanks in advance.

#246026
Dec 24, 2020 16:11
Vote:
 

Hi Santiago

Do you have code to change the Redirect URL before sending users to the Federation endpoint?

Look at this documentation page (it is for OpenID, but the concept is the same). Copy the method HandleMultiSiteReturnUrl and use it in the RedirectToIdentityProvider delegate method.

This will tell the Federation endpoint to send the user back to Episerver on the site the user attempted to visit (not default to the primary site).

#246068
Dec 26, 2020 7:28
Vote:
 

Hi Stefan,

Thank you for your reply! Here is the code I'm using with WS-Federation, the sample you are pointed out is using OpenId and I don't know what is the alternative for WS-Federation

public class Startup
    {
        private static readonly Lazy<ConfigurationService> _configurationService =
            new Lazy<ConfigurationService>(() => ServiceLocator.Current.GetInstance<ConfigurationService>());

        const string _logoutUrl = "/util/logout.aspx";

        public void Configuration(IAppBuilder app)
        {
            //Enable cookie authentication, used to store the claims between requests
            app.SetDefaultSignInAsAuthenticationType(WsFederationAuthenticationDefaults.AuthenticationType);
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType
            });

            //Enable federated authentication
            app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions()
            {
                //Trusted URL to federation server meta data
                MetadataAddress = _configurationService.Value.GetAppSetting("SSO:MetadataAddress"),
                //Value of Wtreal must *exactly* match what is configured in the federation server
                Wtrealm = _configurationService.Value.GetAppSetting("SSO:WTRealm"),

                Notifications = new WsFederationAuthenticationNotifications()
                {
                    RedirectToIdentityProvider = ctx =>
                    {
                        
                        //To avoid a redirect loop to the federation server send 403 when user is authenticated but does not have access
                        if (ctx.OwinContext.Response.StatusCode == 401 && ctx.OwinContext.Authentication.User.Identity.IsAuthenticated)
                        {
                            ctx.OwinContext.Response.StatusCode = 403;
                            ctx.HandleResponse();
                        }
                        //XHR requests cannot handle redirects to a login screen, return 401
                        if (ctx.OwinContext.Response.StatusCode == 401 && IsXhrRequest(ctx.OwinContext.Request))
                        {
                            ctx.HandleResponse();
                        }
                        return Task.FromResult(0);
                    },
                    SecurityTokenValidated = (ctx) =>
                    {
                        //Increases the timeout
                        ctx.AuthenticationTicket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(1);
                        ctx.AuthenticationTicket.Properties.IsPersistent = true;

                        //Ignore scheme/host name in redirect Uri to make sure a redirect to HTTPS does not redirect back to HTTP
                        var redirectUri = new Uri(ctx.AuthenticationTicket.Properties.RedirectUri, UriKind.RelativeOrAbsolute);
                        if (redirectUri.IsAbsoluteUri)
                        {
                            ctx.AuthenticationTicket.Properties.RedirectUri = redirectUri.PathAndQuery;
                        }

                        if (ctx.AuthenticationTicket.Identity.HasClaim(Global.Claims.Group, Global.MembershipConstants.ADFSsAdmins))
                        {
                            ctx.AuthenticationTicket.Identity.AddClaim(new Claim(ClaimTypes.Role, Global.MembershipConstants.WebAdmins));
                        }
                        else if (ctx.AuthenticationTicket.Identity.HasClaim(Global.Claims.Group, Global.MembershipConstants.ADFSUsers))
                        {
                            ctx.AuthenticationTicket.Identity.AddClaim(new Claim(ClaimTypes.Role, Global.MembershipConstants.WebEditors));
                        }

                        if (!ctx.AuthenticationTicket.Identity.HasClaim(x => x.Type == ClaimTypes.Name))
                        {
                            var username = ctx.AuthenticationTicket.Identity.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
                            if (username != null)
                            {
                                ctx.AuthenticationTicket.Identity.AddClaim(new Claim(ClaimTypes.Name, username.Value));
                            }
                        }

                        ctx.OwinContext.Authentication.SignIn(ctx.AuthenticationTicket.Identity);

                        //Sync user and the roles to EPiServer in the background
                        ServiceLocator.Current.GetInstance<ISynchronizingUserService>().SynchronizeAsync(ctx.AuthenticationTicket.Identity);
                        return Task.FromResult(0);
                    }
                }
            });

            //Add stage marker to make sure WsFederation runs on Authenticate (before URL Authorization and virtual roles)
            app.UseStageMarker(PipelineStage.Authenticate);

            //Remap logout to a federated logout
            app.Map(_logoutUrl, map =>
            {
                map.Run(ctx =>
                {
                    ctx.Authentication.SignOut();
                    return Task.FromResult(0);
                });
            });

            //Tell antiforgery to use the name claim
            AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Name;
        }

        private static bool IsXhrRequest(IOwinRequest request)
        {
            const string xRequestedWith = "X-Requested-With";

            var query = request.Query;
            if ((query != null) && (query[xRequestedWith] == "XMLHttpRequest"))
            {
                return true;
            }

            var headers = request.Headers;
            return (headers != null) && (headers[xRequestedWith] == "XMLHttpRequest");
        }

       
    }

Could you provide some guidance on what should I do? Thanks

#246109
Dec 28, 2020 2:14
Vote:
 

Hi Santiago

You can still add the HandleMultiSiteReturnUrl method from the page I linked to. But instead of setting context.ProtocolMessage.RedirectUri in the method, you can try setting context.ProtocolMessage.Wreply property to the current URL.

#246123
Dec 28, 2020 11:27
Vote:
 

Hi Stefan, thanks for your quick reply!

I added the Wreply:

private void HandleMultiSiteReturnUrl(
            RedirectToIdentityProviderNotification<WsFederationMessage, WsFederationAuthenticationOptions> context)
        {
            // here you change the context.ProtocolMessage.RedirectUri to corresponding siteurl
            // this is a sample of how to change redirecturi in the multi-tenant environment
            if (context.ProtocolMessage.Wreply == null)
            {
                var currentUrl = HttpContext.Current.Request.Url;
                context.ProtocolMessage.Wreply = new UriBuilder(
                    currentUrl.Scheme,
                    currentUrl.Host,
                    currentUrl.Port).ToString();
            }
        }

But the OwinResponse is throwing 401:

and I'm getting the same infinite loop until I get the error thrown by the provider:

The scenario is this one. I have a main site which is https://www.mainsite.edu. Then, the client added a new requirement where they want a microsite with a domain like this https://microsite.mainsite.edu. When I clicked on the home page of the new site, Episerver redirects to the other domain and I think the SSO tries to login against that new domain but it's not recognized since the only relaying party defined is www.mainsite.edu. I assume their IT team should add that new domain as a relaying party too? or how can we skip that validation since microsite.mainsite.edu is a "subdomain" of www.mainsite.edu? 

It would be great if Epi doesn't redirect to another domain when you are editing another site. 

#246157
Dec 29, 2020 1:45
Johan Kronberg - Jan 11, 2021 15:37
Yes, you need the new domain set up in the WS-fed product IT are using. Most federated signin options require things to be set up exactly the same in both ends.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.