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:
if (User.Identity.IsAuthenticated)
.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.Expected Behavior
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>
Questions
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
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.");
}
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.
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