Kentor Auth Services choose programmaticaly entityId based on page url

814 Views Asked by At

I'm integrating our asp.net MVC application with SAML2 Authentication. And using Kentor.AuthServices as module as described at kentor.AuthServices Configuration

Everithing works fine. But next step is to add usage of second service provider (which configured to use another auth mechanisms on server side) only for specified range of pages.

First, how to configure it via web.config to add second SP (not the second IdP in scope of first SP) with different entityId.

And Second, how to switch programmatically to second SP? I assume that it should happend in global.asax file in method Application_BeginRequest, but how?

2

There are 2 best solutions below

2
On

Using two different SP instances in the same application is a quite rare scenario. But if you are really sure you need it, it can be achieved.

You will have to use the Kentor.AuthServices.Owin package and do the configuration in code - web.config won't do. Register two instances of the middleware. Each one will have their own configuration, including their own SP EntityID. Also make sure to change the ModulePath of at least one of them so that they get different endpoint addresses.

To challenge an authentication from either one, set the right authentication scheme in the challenge (typically in a ChallengeResult returned from a controller)

0
On

Self-answering.

Here is a workaround for multiple SP for MVC or HttpModule package, switching is based on specified range of URLs. In my case different SP realize different amount of security factors.

First, implementing custom IOptions and CookieHandler, with ability to switch to correct instance. In the web.config file, two kentor.authServices sections must be defined. In my case only "entityId" attribute differs.

    public class CustomOptions : IOptions
    {
        private IOptions options1Factor;
        private IOptions options2Factor;
        private Func<bool> _checkIsSecure;

        public CustomOptions(Func<bool> checkIsSecure)
        {
            _checkIsSecure = checkIsSecure;

            AddOption(out options2Factor, "kentor.authServices1");
            AddOption(out options1Factor, "kentor.authServices");
        }

        private void AddOption(out IOptions options, string sectionName)
        {
            var sp = new SPOptions((KentorAuthServicesSection)ConfigurationManager.GetSection(sectionName));
            options = new Options(sp);
            KentorAuthServicesSection.Current.IdentityProviders.RegisterIdentityProviders(options);
            KentorAuthServicesSection.Current.Federations.RegisterFederations(options);
        }

        public SPOptions SPOptions
        {
            get
            {
                if (_checkIsSecure())
                    return options2Factor.SPOptions;

                return options1Factor.SPOptions;
            }
        }

        public IdentityProviderDictionary IdentityProviders
        {
            get
            {
                if (_checkIsSecure())
                    return options2Factor.IdentityProviders;

                return options1Factor.IdentityProviders;
            }

        }

        public KentorAuthServicesNotifications Notifications
        {
            get
            {
                if (_checkIsSecure())
                    return options2Factor.Notifications;

                return options1Factor.Notifications;
            }
        }
    }

    public class CustomCookieHandler : CookieHandler
    {
        private Func<bool> _checkIsSecure;
        private CookieHandler _originalCookieHandler1Factor;
        private CookieHandler _originalCookieHandler2Factor;

        public CustomCookieHandler(Func<bool> checkIsSecure)
        {
            _checkIsSecure = checkIsSecure;
            _originalCookieHandler1Factor = new ChunkedCookieHandler()
            {
                Name = "commonAuth",
                RequireSsl = false
            };

            _originalCookieHandler2Factor = new ChunkedCookieHandler()
            {
                Name = "securedAuth",
                RequireSsl = false
            };
        }

        public override string MatchCookiePath(Uri baseUri, Uri targetUri)
        {
            if (_checkIsSecure())
                return _originalCookieHandler2Factor.MatchCookiePath(baseUri, targetUri);

            return _originalCookieHandler1Factor.MatchCookiePath(baseUri, targetUri);
        }

        protected override void DeleteCore(string name, string path, string domain, HttpContext context)
        {
            if (_checkIsSecure())
                _originalCookieHandler2Factor.Delete();
            else
                _originalCookieHandler1Factor.Delete();
        }

        protected override byte[] ReadCore(string name, HttpContext context)
        {
            if (_checkIsSecure())
                return _originalCookieHandler2Factor.Read();

            return _originalCookieHandler1Factor.Read();
        }

        protected override void WriteCore(byte[] value, string name, string path, string domain, DateTime expirationTime, bool secure, bool httpOnly, HttpContext context)
        {
            if (_checkIsSecure())
                _originalCookieHandler2Factor.Write(value, true, expirationTime); 
            else
                _originalCookieHandler1Factor.Write(value, true, expirationTime);
        }
    }

In Global.asax file setting static properties to custom implementations. No more modifications needed.

    protected void Application_Start()
    {
        FederatedAuthentication.FederationConfiguration.CookieHandler = new CustomCookieHandler(CheckIsSecure);
        Kentor.AuthServices.Mvc.AuthServicesController.Options = new CustomOptions(CheckIsSecure);
    }

    private bool CheckIsSecure()
    {
        if (HttpContext.Current == null)
            return false;

        var mainHost = "http://host.local"; // host url 
        var sp = new [] { "/Home/Secure" }; // array of URLs which must be secured with other SP
        var request = HttpContext.Current.Request;
        var isSecured = sp.Any(x => x.Equals(request.Path, StringComparison.InvariantCultureIgnoreCase));

        if (!isSecured && request.Path.Equals("/AuthServices/SignIn", StringComparison.InvariantCultureIgnoreCase))
        {
            var returnUrl = request.QueryString["ReturnUrl"];
            isSecured = !string.IsNullOrEmpty(returnUrl) &&
                        sp.Any(x => x.Equals(returnUrl, StringComparison.InvariantCultureIgnoreCase));
        }

        if (!isSecured && request.Path.Equals("/AuthServices/Acs", StringComparison.InvariantCultureIgnoreCase))
        {
            var _r = new HttpRequestWrapper(request).ToHttpRequestData();
            isSecured = _r != null && _r.StoredRequestState != null && _r.StoredRequestState.ReturnUrl != null
                        && sp.Any(x => x.Equals(_r.StoredRequestState.ReturnUrl.ToString(),
                            StringComparison.InvariantCultureIgnoreCase));
        }

        if (!isSecured && !string.IsNullOrEmpty(request.Headers["Referer"]))
        {
            var referer = request.Headers["Referer"];
            isSecured = sp
                .Select(x => string.Format("{0}/{1}", mainHost.TrimEnd('/'), x.TrimStart('/')))
                .Any(x => x.Equals(referer, StringComparison.InvariantCultureIgnoreCase));
        }

        return isSecured;
    }