12

I am using Owin to configure my ASP.NET MVC 5 (.NET 4.5, IIS 7/8) application to authenticate against a third-party ADFS setup:

app.SetDefaultSignInAsAuthenticationType(WsFederationAuthenticationDefaults.AuthenticationType);

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType
});

app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
    Wtrealm = Settings.Auth.Wtrealm,
    MetadataAddress = Settings.Auth.MetadataAddress
});

I also have a custom authentication filter (used in conjunction with AuthorizeAttribute):

public class OwinAuthenticationAttribute : ActionFilterAttribute, IAuthenticationFilter
{
    public void OnAuthentication(AuthenticationContext filterContext)
    {
        var user = filterContext.RequestContext.HttpContext.User;

        var authenticated = user.Identity.IsAuthenticated;
        if (!authenticated)
        {
            return;
        }

        /* Redirect to profile setup if not already complete */
    }

    public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
    {
    }
}

This works fine half of the time but sometimes, on initial login, a redirect loop will occur between the app and the ADFS login. This seems to be session-specific (does not occur for all users at once) and once the redirect loop occurs it seems to continue occurring until an application pool refresh.

When the redirect loop occurs, I can still see (in Chrome's Network tab) what looks like a valid token being issued by ADFS.

I'm having a hard time isolating the root cause but what I have found is that - when the loop does not occur, user.Identity is of type ClaimsIdentity and IsAuthenticated is true. When it does occur, IsAuthenticated is false but user.Identity is of type WindowsIdentity.

All forms of authentication in IIS - except Anonymous - are disabled. IIS Express is not in use anywhere.

What could be causing this?

Ant P
  • 24,820
  • 5
  • 68
  • 105

1 Answers1

21

Do you use session data, and or TempData? I understand is it related to cookies. I too have the same issue.

Here is some more information and a thorough explanation of the cause. The problem can be worked around by forcing Owin to use System.Web's cookie pipeline (from here):

public class SystemWebCookieManager : ICookieManager
{
    public string GetRequestCookie(IOwinContext context, string key)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
        var cookie = webContext.Request.Cookies[key];
        return cookie == null ? null : cookie.Value;
    }

    public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        if (options == null)
        {
            throw new ArgumentNullException("options");
        }

        var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);

        bool domainHasValue = !string.IsNullOrEmpty(options.Domain);
        bool pathHasValue = !string.IsNullOrEmpty(options.Path);
        bool expiresHasValue = options.Expires.HasValue;

        var cookie = new HttpCookie(key, value);
        if (domainHasValue)
        {
            cookie.Domain = options.Domain;
        }
        if (pathHasValue)
        {
            cookie.Path = options.Path;
        }
        if (expiresHasValue)
        {
            cookie.Expires = options.Expires.Value;
        }
        if (options.Secure)
        {
            cookie.Secure = true;
        }
        if (options.HttpOnly)
        {
            cookie.HttpOnly = true;
        }

        webContext.Response.AppendCookie(cookie);
    }

    public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        if (options == null)
        {
            throw new ArgumentNullException("options");
        }

        AppendResponseCookie(
            context,
            key,
            string.Empty,
            new CookieOptions
            {
                Path = options.Path,
                Domain = options.Domain,
                Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc),
            });
    }
}

And to wire it up:

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    // ...
    CookieManager = new SystemWebCookieManager()
})
Community
  • 1
  • 1
Jamie
  • 736
  • 6
  • 14
  • Thanks for this - you have saved me a lot of headache. Funnily enough we came across your question on this but it wasn't answered! I hope you don't mind but I've extended your answer to include the workaround from the Katana bug so people can still get at it if the link ever goes dead. I was going to add my own answer with this extra info but I think you deserve any credit for it. – Ant P Feb 19 '15 at 09:24
  • I'd forgotten I'd already asked this, I better go and update it. I have also implemented this solution and so far so good. – Jamie Feb 19 '15 at 10:07
  • 3
    In case it saves anyone else time, `CookieAuthenticationOptions.CookieManager` is only available from [Microsoft.Owin.Security.Cookies](https://www.nuget.org/packages/Microsoft.Owin.Security.Cookies) v3.0.0 onwards – Dunc May 10 '16 at 10:51
  • Kudos for this. It's unbelievable this is still an issue today. I've implemented your solution and it's working flawlessly. I've only just recently had to do this as our current implementation stopped working out of the blue. Crazy! – Joshua H May 03 '17 at 05:57
  • So far this seems to work for me, but I will know more in the next half hour since it seems like every 20-30 minutes the AD FS errors show up once again after refreshing the web.config. Thanks for the solution! Cross your fingers. – Jamie Nov 23 '21 at 21:55