'Owin app with WsFederation authentication stuck in infinite redirect loop

I have an Owin app that uses Ws-Federation authentication with a SSO application (not ADFS). Whenever my Owin app receives a request, it authenticates the user by first checking if it has the right cookie, and then it builds the claims and authentication ticket from that. If it doesn't have the right cookie, it redirects to the STS, which passes back a SAML token that can be used to complete the authentication.

All of this works except one part. After the token is received and validated, for some reason it redirects back to the STS, thus creating an infinite loop. I am quite sure it is because one or more of my configuration values is wrong since it's not very clear what each property is for and if it's required. I've copied my configuration code below:

public void Configuration(IAppBuilder app)
{
    app.SetDefaultSignInAsAuthenticationType( CookieAuthenticationDefaults.AuthenticationType);

    CookieAuthenticationOptions cookieOptions = new CookieAuthenticationOptions
    {
        AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
        CookieName = "MyCookie",
        CookiePath = "/CookiePath",
        AuthenticationMode = AuthenticationMode.Active
    };
    // Basically the same as saying app.UseCookieAuthentication(app, cookieOptions)
    app.Use(typeof(MyCustomCookieAuthenticationMiddleware), app, cookieOptions);

    // Define properties for WsFederationAuthenticationOptions
    var config = new Microsoft.IdentityModel.Protocols.WsFederationConfiguration
    {
        Issuer = "https://sts-domain.com/STS/",
        TokenEndpoint = this.owinServerUrl // I don't know what this should be. I just made it the same as my owin start url
    };
    Saml2SecurityTokenHandler handler = new Saml2SecurityTokenHandler
    {
        Configuration = new SecurityTokenHandlerConfiguration
        {
            IssuerTokenResolver = new MyCustomSecurityTokenResolver
            {
                Thumbprint = somePublicKeyStr,
                StoreLocation = System.Security.Cryptography.X509Certificates.StoreLocation.LocalMachine,
                StoreName = "My"
            }                
        }
    };
    var handlers = new SecurityTokenHandlerCollection(new List<SecurityTokenHandler>() { handler });
    var wsFedOptions = new WsFederationAuthenticationOptions
    {
        AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType,
        AuthenticationMode = AuthenticationMode.Passive,
        SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType, // I'm not sure what this is exactly, but I think I've seen this used in examples
        Configuration = config,
        Wtrealm = this.owinServerUrl, // I'm guessing what this should be
        Wreply = someString, // I have no idea what this should be -- it hasn't seemed to have any effect so far
        SecurityTokenHandlers = handlers,
        TokenValidationParameters = new TokenValidationParameters
        {
            AuthenticationType = CookieAuthenticationDefaults.AuthenticationType, // I'm not sure whether this should be cookie or ws federation, but I don't think it's relevant to my problem
            ValidIssuer = "https://sts-domain.com/STS/", // same as config.Issuer above
        }
    };        
    app.UseWsFederationAuthentication(wsFedOptions);

    AuthenticateAllRequests(app, WsFederationAuthenticationDefaults.AuthenticationType);

    app.Use<MyCustomMiddleware>();
}

private static void AuthenticateAllRequests(IAppBuilder app, params string[] authenticationTypes)
{
    app.Use((context, continuation) =>
    {
        if (context.Authentication.User?.Identity?.IsAuthenticated ?? false)
        {
            return continuation();
        }
        else
        {
            context.Authentication.Challenge(authenticationTypes);
            return Task.CompletedTask;
        }
    });
}

As I indicate in my code with my comments, there are some properties I am unsure of. Unfortunately, the documentation for WsFederationAuthenticationOptions does not help me out much. For example, I know that Wtrealm and Wreply are important (perhaps Wreply less so), but all the documentation says is "Gets or sets the 'wtrealm'" and "Gets or sets the 'wreply'." I found this thread that has an explanation:

wtrealm is a URI (not necessarily a URL) that identifies the RP. The STS uses this to decide whether to issue a token and what claims to give it.

wreply is the URL that the RP would like to be redirected to with the resulting token. The STS is not bound to comply with this request... sometimes the STS has a predefined address it will redirect to based on the established trust. At the very least, the STS should refuse to redirect to a different domain than the one it associates with the realm. Otherwise, the request could be a vector to send the user to a malicious site.

This makes sense, except for the fact that while I've been testing my owin app, Wreply seems to have no effect on where the STS is redirected to pass back the token; the URL I put for Wtrealm is what determines that.

All I'd like to do is for the STS to pass back the token, authenticate the user, and carry on to the route that the user specified which started all this. I'm not sure if this is relevant, but I also thought that the cookie is supposed to be set when the STS passes back the token. If this were the case, the infinite redirect wouldn't occur because when it comes back to be authenticated, the cookie authentication would find the cookie and the app would proceed as normal.

Update 1

I have changed a few of the values and have gotten different error messages, so I thought I'd share them here in case they help shed light on what could be going on. As you can tell from my post, I don't want to share real info about the app, so bear with me. Let's say the overarching web application (what contains my owin app, along with a bunch of other stuff) has the url http://localhost/app. My owin app has the server url (what I call this.owinServerUrl in the code above) http://localhost/app/owin.

  • When I make WsFederationConfiguration.TokenEndpoint = "http://localhost/app", I get the infinite redirect. When I make it "http://localhost/app/owin", I don't get an infinite redirect, but I do get another error (which error I get depends on other values, which I'll explain now).
  • I was mistaken -- Wreply does seem to have an effect. When I don't set Wreply, I get a 414 error: request URL too long. When I do set it (and as any string, whether it's a URL that I think could make sense or just gibberish), I get a 400 bad request: request too long.


Solution 1:[1]

Isn't it because of your AuthenticateAllRequests function? I think you think it only runs after the user is authenticated and context.Authentication.User?.Identity?.IsAuthenticated is set. However, I think it runs after the user is authenticated and before context.Authentication.User?.Identity?.IsAuthenticated is set. Since is never set, does it just invoke authentication again by calling "context.Authentication.Challenge(authenticationTypes)" ?

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jason Cheng