Nancy form validation not working with AJAX inputs

I am trying to implement an extremely simple splash using Nancy as an alternative to ASP.NET MVC.

It must accept the username (without password) and provide meaningful error messages on one login page without requiring an update. If the login was successful, the response contains the URL for the transition.

The POCO for the response is as follows:

public class LoginResponseModel { public bool IsSuccess { get; set; } public string RedirectUrl { get; set; } public string ErrorMessage { get; set; } } 

JS handler for login request:

 $.ajax({ url: '/login', type: "POST", data: { UserName: username } }).done(function (response) { if (response.IsSuccess) { showSuccess(); document.location.href = response.RedirectUrl; return; } showError(response.ErrorMessage); }).fail(function (msg) { showError("Unable to process login request: " + msg.statusText); }); 

The problem I am facing is Nancy Forms based authentication. I went through half a dozen different tutorials that are more or less doing the same thing, and have also switched to Nancy authentication demos. The only thing everyone has is that they rely on the LoginAndRedirect extension LoginAndRedirect . I do not want to return a redirect. I want to return the result of a login attempt and let the client handle the navigation.

The IUserMapper implementation that I use:

 public class UserMapper : IUserMapper { public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context) { // Don't care who at this point, just want ANY user... return AuthenticatedUser {UserName = "admin"}; } } 

Relevant part of my LoginModule action:

 var result = _userMapper.ValidateUser(input.AccessCode); if (result.Guid != null) this.Login(UserMapper.GUID_ADMIN, expiry); return Response.AsJson(result.Response); 

but for subsequent requests, Context.CurrentUser always null.

If I add the following method to the Nancy.Demo.Authentication.Forms sample, it reproduces the behavior that I see in my own project, which leads me to believe that LoginWithoutRedirect is not working as I expected.

 Get["/login/{name}"] = x => { Guid? userGuid = UserDatabase.ValidateUser(x.Name, "password"); this.LoginWithoutRedirect(userGuid.Value, DateTime.Now.AddYears(2)); return "Logged in as " + x.Name + " now <a href='~/secure'>see if it worked</a>"; }; 
+4
source share
2 answers

The problem is that Context.CurrentUser with FormsAuthentication is dependent on a cookie that is not set unless you return a NancyModule.Login() response.

 var result = _userMapper.ValidateUser(input.AccessCode); if (result.IsSuccess) { this.LoginWithoutRedirect(result.Guid); } return Response.AsJson(result); 

In this example, a call to LoginWithoutRedirect returns a Response object with a set of cookies. To deal with this in an Ajax script, I had to add the AuthToken property to the LoginAjaxResponse class, and then pass the cookie as follows:

 var result = _userMapper.ValidateUser(input.AccessCode); var response = Response.AsJson(result); if (result.IsSuccess) { var authResult = this.LoginWithoutRedirect(result.Guid); result.AuthToken = authResult.Cookies[0].Value; } return Response.AsJson(result); 

On the client, the Ajax response handler changes (assuming jQuery cookie is used :

 $.ajax({ url: '/login', type: "POST", data: { UserName: username } }).done(function (response) { if (response.IsSuccess) { showSuccess(); $.cookie("_ncfa", response.AuthToken); // <-- the magic happens here document.location.href = response.RedirectUrl; return; } showError(response.ErrorMessage); }).fail(function (msg) { showError("Unable to process login request: " + msg.statusText); }); 

AuthToken is a GUID that has been encrypted and encoded in base64. Subsequent requests with this.RequiresAuthentication() enabled will first check this cookie auth token.

  • If there is no cookie "_ncfa", UserMapper GetUserFromIdentifier() never called.

  • If the value in Context.Request.Cookies["_ncfa"] does not produce a valid GUID when base64 is decoded and decrypted, GetUserFromIdentifier() never called.

  • If GetUserFromIdentifier() not called, Context.CurrentUser never set.

If you need a source for a working example, it is on GitHub .

+3
source

LoginAndRedirect is only one option, there are equivalent methods for not redirecting (LoginWithoutRedirect), or one that selects whether it is an AJAX request and processes it accordingly (Login). The same goes for logging.

All this is described in detail in the documentation .

+2
source

All Articles