Asp.net-mvc – WebAPI + Forms Authentication + SimpleMembershipProvider using WindowsIdentity when it shouldn’t

asp.net-mvcasp.net-web-apiauthenticationSecurity

What I've Done

I'm building a REST-ish WebAPI application. I'm trying to implement forms authentication here, so clients can allow users to login/register/logout from their browsers. I'm using simpleMembership, and users are stored in my app's simpleMembership database tables. As a first step, I've implemented a login method in my AccountsController:

    [System.Web.Http.HttpPost]
    public HttpResponseMessage LogIn(LoginModel model)
    {
        if (ModelState.IsValid)
        {
            if (User.Identity.IsAuthenticated)
            {
                return Request.CreateResponse(HttpStatusCode.Conflict, "already logged in.");
            }
            if (WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
            {
                FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
                //TODO: look up by id, not by name
                var userProfile = _service.GetUser(WebSecurity.GetUserId(model.UserName));
                return Request.CreateResponse(HttpStatusCode.OK, userProfile);
            }
            else
            {
                return new HttpResponseMessage(HttpStatusCode.Unauthorized);
            }
        }

        // If we got this far, something failed
        return new HttpResponseMessage(HttpStatusCode.InternalServerError);
    }

The Problem

Here's what's happening:

  1. I set a breakpoint at if (User.Identity.IsAuthenticated).
  2. Using something like POSTMan or Fiddler, I attempt to log in using some bogus credentials.
  3. I hit the breakpoint and get "False" for IsAuthenticated, as expected.
  4. I step over to the next 'if', and an attempt to LogIn fails, as expected.
  5. The code in the else statement is hit, but then once returned, 1) my breakpoint is hit again, and 2) the principal is set to my windows identity. Both are unexpected, and I'm trying to figure out how to correct it.

enter image description here

Expected Behavior

  • The method just returns 401 Unauthorized, since the bogus user does not exist in my app's database.

Configuration

I think this is a configuration issue – the principal is automatically being set to my windows account for some reason upon authentication failure. Some details related to my config:

<authentication mode="Forms">
  <forms loginUrl="~/" timeout="2880" />
</authentication>
  • I haven't enabled windows authentication anywhere in the application. My web.config is above ^
  • Using IISManager I have disabled Anonymous Authentication
  • In my applicationhost.config I have disabled Windows and Anonymous Authentication

Questions

  1. Why is the breakpoint being hit twice, instead of the LogIn method just returning Unauthorized since that last "else" is hit?
  2. Why are my Windows credentials being used on this unexpected and undesired "loopback"?
  3. How can I prevent this from happening? OR:
  4. Do I need to use a MessageHandler to set the principal? If so, how does this impact [Authenticate] and IsAuthenticated? Any examples?

Update 1

I've tried registering the following handler:

public class PrincipalHandler : DelegatingHandler
    {

        protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
                                                                               System.Threading.CancellationToken
                                                                                   cancellationToken)
        {
            HttpContext.Current.User = new GenericPrincipal(new GenericIdentity(string.Empty), null);
            Thread.CurrentPrincipal = HttpContext.Current.User;
            return await base.SendAsync(request, cancellationToken);
        }

    }

And now the principal is never set to my Windows identity. From POSTMan (browser extension), the if statement is hit twice and I get a popup asking for credentials. From Fiddler, I just get 401 Unauthorized, as expected.

New Questions

  1. How can I set the principal according to what's in the response cookie, AND authenticate against my app's simple membership table?

Update 2

This post seems to be getting me in the right direction – using Membership instead of User in my LoginMethod:

ASP.NET MVC – Set custom IIdentity or IPrincipal

Will continue to update with progress, but I'm still open to suggestions/recommendations.

Update 3
I've never been so simultaneously relieved and pissed off – no custom handler was needed here. Windows auth was enabled in my project properties. The working solution for Login and Logout are here, and "just work" with my membership tables:

public HttpResponseMessage LogIn(LoginModel model)
        {
            if (ModelState.IsValid)
            {
                if (User.Identity.IsAuthenticated)
                {
                    return Request.CreateResponse(HttpStatusCode.Conflict, "already logged in.");
                }
                if (WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
                {
                    FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
                    return Request.CreateResponse(HttpStatusCode.OK, "logged in successfully");
                }
                else
                {
                    return new HttpResponseMessage(HttpStatusCode.Unauthorized);
                }
            }

            // If we got this far, something failed
            return new HttpResponseMessage(HttpStatusCode.InternalServerError);
        }

public HttpResponseMessage LogOut()
        {
            if (User.Identity.IsAuthenticated)
            {
                WebSecurity.Logout();
                return Request.CreateResponse(HttpStatusCode.OK, "logged out successfully.");
            }
            return Request.CreateResponse(HttpStatusCode.Conflict, "already done.");
        }

Best Answer

Are you using IIS Express for development? If so, click on your project in Visual Studio and check the properties window. There is an option here for Windows Authentication, which I believe is Enabled by default. Disable it here too.

Related Topic