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.
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>
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With