0

I've reviewed similar posts with solutions that do not work for me.

I have an MVC 5 site hosted in IIS 7.x that serves a web ui - https://www.example.com. Callers can also access api (Webapi 2.2) endpoints to perform certain functions - https://www.example.com/api/x. Some pages/apis are secured while others are not. The mvc/web ui security is managed by owin middleware configured with UseCookieAuthentication and UseWsFederationAuthentication.

The secured pages in the webui are automatically redirected to an ADFS login screen when the user does not have already have a valid SAML token - as desired.

The secured web apis require a separate JWT token passed in the Auth header.

The Webapi is hosted in the same app pool as MVC. The Webapi does NOT have controllers, instead the webapiconfig has routes that leverage a DelegatingHandler to route/pass through the api calls. The Delegating handler is the one that checks to see if the JWT is included in the Auth header and if so allows it to continue to a different internal webapi that validates the JWT. If the JWT is not present then the DelegatingHandler returns a 401.

The 401 return used to work as it shortcircuited a continuation of the request and therefore bypassed any owin pipeline stuff. However, now when the shortcircuit fires the 401 is not returned. Instead the request continues and gets passes onto the Owin auth which then redirects (302) to the ADFS login. I have no idea why. If I change the response status code to something other than 401 then Owin Auth ignores it.

Please see the code below:

Global.asax.cs

public class Global : HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        // Code that runs on application startup
        AreaRegistration.RegisterAllAreas();
        GlobalConfiguration.Configure(WebApiConfig.Register);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);        
    }
}

WebApiConfig.cs

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Routes.MapHttpRoute(
            name: "Apis",
            routeTemplate: "api/{*path}",
            handler: HttpClientFactory.CreatePipeline
            (
                innerHandler: new HttpClientHandler(),
                handlers: new DelegatingHandler[] { new ApiHandler() }
            ),
            defaults: new { path = RouteParameter.Optional },
            constraints: null
        );
    }
}

ApiHandler.cs

internal class ApiHandler : DelegatingHandler
{
    protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);

        try
        {
            // get jwt from header
            var jwt = GetJWTFromHeader(request.Headers);

            if (jwt == null)
            {
                response.ReasonPhrase = "Token required";
                return await Task.FromResult<HttpResponseMessage>(response);
            }
            else if (!IsValidJWT(jwt))
            {
                response.ReasonPhrase = "Invalid token";
                return await Task.FromResult<HttpResponseMessage>(response);
            }

            response = await base.SendAsync(request, cancellationToken);

        }
        catch (Exception ex)
        {
            // log error
            response = new HttpResponseMessage(HttpStatusCode.InternalServerError);
        }

        // return result
        return response;
    }
}

Startup.Auth.cs

public partial class Startup
{
    public void ConfigureAuth(IAppBuilder app)
    {
        ServicePointManager.ServerCertificateValidationCallback += ValidateServerCertificate;

        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(
            new CookieAuthenticationOptions()
            {
                SlidingExpiration = false
            }
        );

        app.UseWsFederationAuthentication(
            new WsFederationAuthenticationOptions
            {
                Wtrealm = ADFS_REALM,
                MetadataAddress = ADFS_METADATA,
                UseTokenLifetime = true,
                TokenValidationParameters = new TokenValidationParameters
                {
                    SaveSigninToken = true
                },

                Notifications = new WsFederationAuthenticationNotifications
                {
                    RedirectToIdentityProvider = async r =>
                    {
                        // do stuff                         
                    },
                    SecurityTokenValidated = async s =>
                    {
                        // if we get here, then UI user has valid saml token
                        // do stuff
                    }
                }
            }
        });

}

I appreciate any help. Please let me know if more details are needed!

Stinky Towel
  • 768
  • 6
  • 26

2 Answers2

1

Looks like you can use: https://msdn.microsoft.com/en-us/library/system.web.http.owinhttpconfigurationextensions.suppressdefaulthostauthentication(v=vs.118).aspx config.SuppressDefaultHostAuthentication();

Finallz
  • 71
  • 4
1

Thanks to Finallz I was able to refine my search and come across an answer - found here. In my case, I don't need any special authentication config since I'm manually inspected the JWT in the apihandler. However, by simply including a map to my api path, it naturally overrides the Owin security:

app.Map("/api", inner =>
{
     // nothing to do here since we don't have any concrete controllers to manage special authorization for
     // we're using apihandlers to pass api traffic through to next stop
});
Community
  • 1
  • 1
Stinky Towel
  • 768
  • 6
  • 26