Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent XmlHttpRequest redirect response in .Net MVC WS-Federation Site

I'm using WS Federated (Claims Aware) authentication on an MVC 3 site and am having trouble keeping some of my API controllers that send JSON from returning a redirect when the authentication fails. I have an Area called API with several controllers that just return JSON, these controllers all inherit from the same base class. I want to send down legitimate 401 error responses instead of 302 redirects that are happening by default.

I followed some directions I found for creating a custom WSFederationAuthenticationModule in concert with a filter I put on my API controller actions:

public class WSFederationServiceAuthenticationModule : WSFederationAuthenticationModule
{
    private static Log4NetLoggingService logger = new Log4NetLoggingService();

    public const string IsServiceIndicator = "ROIP.IsService";

    protected override void OnAuthorizationFailed(AuthorizationFailedEventArgs e)
    {
        base.OnAuthorizationFailed(e);            

        var isService = HttpContext.Current.Items[IsServiceIndicator];

        if (isService != null)
        {
            logger.Info("WSFedService: Found IsService");
            e.RedirectToIdentityProvider = false;
        }
        else
        {
            logger.Info("WSFedService: Did not find IsService");
        }
    }
}

public class WSFederationServiceAuthAttribute : ActionFilterAttribute
{
    private static Log4NetLoggingService logger = new Log4NetLoggingService();

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);

        // Set an item that indicates this is a service request, do not redirect.
        logger.Info("WSFedService: Setting IsService");
        HttpContext.Current.Items[WSFederationServiceAuthenticationModule.IsServiceIndicator] = 1;
    }
}

But my logging shows that I am never finding the IsService item in the Items:

{INFO}02/29 03:39:21 - WSFedService: Setting IsService
{INFO}02/29 03:39:32 - WSFedService: Setting IsService
{INFO}02/29 03:39:32 - WSFedService: Setting IsService
{INFO}02/29 03:50:39 - WSFedService: Did not find IsService
{INFO}02/29 03:53:16 - WSFedService: Did not find IsService
{INFO}02/29 03:53:29 - WSFedService: Did not find IsService

I think this may be a problem with the HttpContext.Current not being the same between the filter and the module, but I'm not sure.

Another option I tried was to subscribe to the FederatedAuthentication.WSFederationAuthenticationModule.RedirectingToIdentityProvider event in the Application_Start event of my Global.asax.cs, but the WSFederationAuthenticationModule is null at that time.

private void ConfigureWSFederationAuthentication()
{
    bool hasFederatedAuthentication = false;
    try
    {
        if (FederatedAuthentication.WSFederationAuthenticationModule != null)
        {
            hasFederatedAuthentication = true;
        }
    }
    catch
    {
        hasFederatedAuthentication = false;
    }

    if (hasFederatedAuthentication)
    {
        Logger.Info("WSFederation: Registering for Event Handler");
        FederatedAuthentication.WSFederationAuthenticationModule.RedirectingToIdentityProvider += (s, e) =>
            {
                var msg = string.Empty;
                try
                {
                    if (HttpContext.Current.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
                    {
                        e.Cancel = true;
                        msg = "Found XMLHttpRequest header";
                    }
                    else
                    {
                        msg = "Did not find XMLHttpRequest header";
                    }
                }
                catch (Exception ex)
                {
                    msg = "WSFederation: Event Handler Error: " + ex.Message;
                }

                Logger.Info("WSFederation: Redirecting from Event Handler: " + msg);
            };
    }
    else
    {
        Logger.Info("WSFederation: Null WSFederationAuthenticationModule");
    }
}

I'd like to know either how to get the first option working, or where I should subscribe to the RedirectingToIdentityProvider event.

like image 576
Jacob Avatar asked Feb 28 '12 22:02

Jacob


2 Answers

I think I've found an answer to this problem and want to circle back and leave an answer for anyone else in the world that might encounter this.

My problem was that the HttpContext.Current.Items wasn't matching up between my ActionFilterAttribute and the WSFederationAuthenticationModule so I ended up inspecting the context and adding some checks similar to Phil Haacks Forms Redirect Suppress Example

Here is what my updated custom WSFederationAuthenticationModule looks like:

public class WSFederationServiceAuthenticationModule : WSFederationAuthenticationModule
{
    private static Log4NetLoggingService logger = new Log4NetLoggingService();

    protected override void OnAuthorizationFailed(AuthorizationFailedEventArgs e)
    {
        base.OnAuthorizationFailed(e);            

        var context = HttpContext.Current;
        var req = context.Request;
        var resp = context.Response;

        if (req == null || resp == null)
        {
            logger.Info("WSFedService: Did not find Request or Response");
            return;
        }

        if ((resp.StatusCode == 302 || resp.StatusCode == 401) && req.Headers["X-Requested-With"] == "XMLHttpRequest")
        {
            logger.Info("WSFedService: Found Redirect and Header");
            e.RedirectToIdentityProvider = false;
        }
        else
        {
            logger.Info(string.Format("WSFedService: Did not find redirect status code or XMLHttpRequest Header: {0}", resp.StatusCode));
        }

    }
}

And of course, you'll need to add this to your web.config in place of the default authentication module:

<system.web>
    <httpModules>
        <!-- Old and Busted...
        <add name="WSFederationAuthenticationModule"
           type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
        -->

        <!-- New Hotness... -->
        <add name="WSFederationAuthenticationModule"
           type="MyApp.Web.Authentication.WSFederationServiceAuthenticationModule, MyApp.Web" />
    </httpModules>
</system.web>

<system.webServer>
    <modules>
        <!-- Old and Busted...
        <add name="WSFederationAuthenticationModule"
           type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
           preCondition="managedHandler"/>
        -->

        <!-- New Hotness... -->
        <add name="WSFederationAuthenticationModule"
           type="MyApp.Web.Authentication.WSFederationServiceAuthenticationModule, MyApp.Web"
           preCondition="managedHandler"/>

    </modules>
</system.webServer>
like image 52
Jacob Avatar answered Nov 17 '22 18:11

Jacob


I realize this thread is ancient, but I came across it while trying to solve the same problem (I have web apis that I want to use with active clients, and I want to send down a 401 if authentication goes south instead of redirecting.)

..And, depending on your situation, it might be easier/less involved to handle the authorization failed event in Global.asax and do your checking there.

This link is what I've been following: http://msdn.microsoft.com/en-us/library/system.identitymodel.services.wsfederationauthenticationmodule.authorizationfailed.aspx

..And it seems straightforward.

like image 3
William Scalf Avatar answered Nov 17 '22 16:11

William Scalf