I have an MVC site that allows logging in using both Forms login and Windows Authentication. I use a custom MembershipProvider that authenticated the users against Active Directory, the System.Web.Helpers AntiForgery class for CSRF protection, and Owin cookie authentication middle-ware.
During login, once a user has passed authentication against Active Directory, I do the following:
IAuthenticationManager authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.SignOut(StringConstants.ApplicationCookie);
var identity = new ClaimsIdentity(StringConstants.ApplicationCookie,
ClaimsIdentity.DefaultNameClaimType,
ClaimsIdentity.DefaultRoleClaimType);
if(HttpContext.Current.User.Identity is WindowsIdentity)
{
identity.AddClaims(((WindowsIdentity)HttpContext.Current.User.Identity).Claims);
}
else
{
identity.AddClaim(new Claim(ClaimTypes.Name, userData.Name));
}
identity.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "Active Directory"));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userData.userGuid));
authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = false }, identity);
My SignOut function looks like this:
IAuthenticationManager authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.SignOut(StringConstants.ApplicationCookie);
Logging in is performed via a jQuery.ajax request. On success, the Window.location
is updated to the site's main page.
Logging in with both Forms and IntegratedWindowsAuthentication
(IWA) works, but I've run into a problem when logging in with IWA. This is what happens:
- The user selects IWA on the login page and hits the submit button. This is sent to the regular login action via an ajax request.
- The site receives the request, sees the "use IWA" option and redirects to the relevant action. 302 response is sent.
- The browser automatically handles the 302 response and calls the redirect target.
- A filter sees that the request is headed to the IWA login action and that User.Identity.IsAuthenticated == false. 401 response is sent.
- The browser automatically handles the 401 response. If the user has not authenticated using IWA in the browser yet, they get a popup to do so (default browser behavior). Once credentials have been received, the browser performs the same request with user credentials.
- The site receives the authenticated request and impersonates the user to perform a check against Active Directory. If the user passes authentication, we finalize SignIn using the code above.
- User is forwarded to the site's main page.
- The site receives the request to load the main page. This is where things sometimes go awry.
TheUser.Identity
at this point is of typeWindowsIdentity
withAuthenticationType
set toNegotiate
, and NOT as I would expect, theClaimsIdentity
created in theSignIn
method above.
The site prepares the main page for the user by calling@AntiForgery.GetHtml()
in the view. This is done to create a new AntiForgery token with the logged in user's details. The token is created with theWindowsIdentity
- As the main page loads, ajax requests made to the server arrive with
ClaimsIdentity
! The firstPOST
request to arrive therefore inevitably causes anAntiForgeryException
where the anti-forgery token it sent is "for a different user".
Refreshing the page causes the main page to load with ClaimsIdentity
and allows POST
requests to function.
Second, related, problem: At any point after the refresh, once things are supposedly working properly, a POST
request may arrive with WindowsIdentity
and not with ClaimsIdentity
, once again throwing an AntiForgeryException
.
- It is not any specific post request,
- it is not after any specific amount of time (may be the first/second request, may be the hundredth),
- it is not necessarily the first time that specific post request got called during that session.
I feel like I'm either missing something regarding the User.Identity
or that I did something wrong in the log-in process... Any ideas?
Note: Setting AntiForgeryConfig.SuppressIdentityHeuristicChecks = true;
allows the AntiForgery.Validate
action to succeed whether WindowsIdentity
or ClaimsIdentity
are received, but as is stated on MSDN:
Use caution when setting this value. Using it improperly can open security vulnerabilities in the application.
With no more explanation than that, I don't know what security vulnerabilities are actually being opened here, and am therefore loathe to use this as a solution.