Forms validation in Nancy not working with AJAX login requests - ajax

I'm trying to implement an extremely simple spike using Nancy as an alternative to ASP.NET MVC.
It should take a username (no password) and provide meaningful error messages on the same login page without requiring a refresh. If login was successful, the response includes the URL to navigate to.
The POCO for the response looks like this:
public class LoginResponseModel
{
public bool IsSuccess { get; set; }
public string RedirectUrl { get; set; }
public string ErrorMessage { get; set; }
}
The JS handler for the 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'm having is with Nancy's Forms-based authentication. I've walked through half a dozen different tutorials which all more or less do the same thing, as well as gone over the Nancy authentication demos. The one thing they all have in common is that they rely on the LoginAndRedirect extension method. I don't want to return a redirect. I want to return a result of the login attempt and let the client handle the navigation.
The IUserMapper implementation I'm using:
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"};
}
}
The 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 is always null.
If I add the following method to the Nancy.Demo.Authentication.Forms sample it reproduces the behaviour I'm seeing in my own project, leading me to believe LoginWithoutRedirect doesn't work how 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>";
};

The problem turns out to be that Context.CurrentUser with FormsAuthentication is dependent upon a cookie which isn't set if you don't return the NancyModule.Login() response.
var result = _userMapper.ValidateUser(input.AccessCode);
if (result.IsSuccess) {
this.LoginWithoutRedirect(result.Guid);
}
return Response.AsJson(result);
In this example, the LoginWithoutRedirect call returns a Response object with the cookie set. To handle this in an Ajax scenario I've had to add a AuthToken property to the LoginAjaxResponse class, then pass the cookie like so:
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 to (assuming use of jQuery cookie plugin:
$.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);
});
The AuthToken is the GUID which has been encrypted and base64-encoded. Subsequent requests with this.RequiresAuthentication() enabled will first check for this auth token cookie.
If no "_ncfa" cookie is present,the UserMapper's GetUserFromIdentifier() is never called.
If the value in Context.Request.Cookies["_ncfa"] does not result in a valid GUID when base64-decoded and decrypted, GetUserFromIdentifier() is never called.
If GetUserFromIdentifier() isn't called, Context.CurrentUser is never set.
If you want the source for a working example it's on GitHub.

LoginAndRedirect is only one option, there are equivalent methods for not redirecting (LoginWithoutRedirect), or one that picks up on whether it's an AJAX request and handles it appropriately (Login). The same applies to logging out.
This is all covered, in detail, in the documentation.

Related

Server-side method does not write to database unless client-side caller is paused/interrupted (via alert)

I am using an Ajax post method to pass a JSON string to a server-side MVC action. The IActionResult method parses the JSON string into an array which is uploaded into SQL Server via Microsoft.Data.SqlClient methods. The IActionResult returns an Ok() result to the caller upon completion.
The anomaly I have observed is that the database upload (server-side) only completes if I pause/interrupt the browser by placing an alert just after the Ajax method (client-side). My code is as follows:
Client-side:
function ExportJSON() {
var myJson = "some JSON stuff goes here";
$.ajax({
type: "POST",
url: "/Dailies/UploadJson/",
dataType: 'json',
data: { jsonString: myJson },
success: function (data) {
console.log(data);
}
});
alert("Your data has been saved.");
}
Server-side action:
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> UploadJson(string jsonString)
{
if (jsonString != null) {
var myJArray = JsonConvert.DeserializeObject<JArray>(jsonString);
TimePunches[] timePunches = myJArray.ToObject<TimePunches[]>();
try
{
string constr = _configuration.GetConnectionString("MYSQLSERVER");
using (SqlConnection sqlConnection = new SqlConnection(constr)) {
await sqlConnection.OpenAsync();
foreach (TimePunches timePunch in timePunches) {
string query = "INSERT INTO TimePunches([Projectid], [CrewLeaderId]) ";
query += "VALUES(#Projectid, #CrewLeaderId) ";
using (SqlCommand cmd = new SqlCommand(query)) {
cmd.Connection = sqlConnection;
cmd.Parameters.AddWithValue("#Projectid", timePunch.Projectid);
cmd.Parameters.AddWithValue("#CrewLeaderId", timePunch.CrewLeaderId);
await cmd.ExecuteNonQueryAsync();
}
}
sqlConnection.Close();
}
}
catch (Exception ex) {
TempData["msg"] = ex.Message;
}
}
return Ok();
}
To reiterate, the server-side action uploads data to the database (as expected) so long as the alert is present in the client-side ExportJSON() method. Conversely, removing the alert causes the database upload to fail.
Any assistance would be greatly appreciated.
I found an answer to this issue. The following link provides an excellent article explaining the nuances of asynchronous JavaScript calls and, in particular, how to manage synchronization issues when using Ajax: https://stackify.com/return-ajax-response-asynchronous-javascript-call/
In my particular case the solution was as simple as adding an async: false qualifier to my Ajax "post" method.

XML Parsing Error: no root element found in ASP.NET Core 2.0 API

I've encountered this problem, and I couldn't figure it out. I'm using ASP.NET Core 2 and Ajax.
This is what the JavaScript debugger says:
XML Parsing Error: no root element found Location:
http://localhost:52617/api/favorites/ Line Number 1, Column 1:
This is my JavaScript code:
$(".js-toggle-fav").click(function (e) {
function sendPost() {
console.log("inside post send");
var button = $(e.target);
$.ajax({
type: 'POST',
url: "http://localhost:52617/api/Favorites/",
data: {"EventId": #Model.Event.EventId},
contentType: "application/json; charset=utf-8"
});
}
$.getJSON("http://localhost:52617/api/favorites/#Model.Event.EventId", function (data) {
if (data == null) {
console.log("fav is null");
sendPost();
fav.addClass(toggling);
fav.text("unfav");
}
else {
console.log("fav is NOT null");
sendPost();
fav.removeClass(toggling);
fav.text("fav");
}
);
});
And my API:
[HttpPost]
public async Task<IActionResult> PostFavorite([FromBody] FavoriteDto favorite)
{
if (!ModelState.IsValid)
{
Console.WriteLine(ModelState.ValidationState.ToString());
return BadRequest(ModelState);
}
var uid = _userManager.GetUserId(HttpContext.User);
var fav = await _context.Favourites.SingleOrDefaultAsync(x => x.EventId == favorite.EventId && x.UserId == uid);
if (fav == null)
{
_context.Favourites.Add(new Favorite { EventId = favorite.EventId, UserId=uid });
}
else
{
_context.Favourites.Remove(fav);
}
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
if (FavoriteExists(favorite.EventId))
{
return new StatusCodeResult(StatusCodes.Status409Conflict);
}
else
{
throw;
}
}
return Ok(favorite);
}
When I do this with Postman or any restclient, everything works like a charm! With Ajax, that's not the case.
NOTES:
In the same .cshtml file, there's more jQuery and JavaScript code which does something like this, and it's just working! All the solutions
I've checked on the internet didn't work, unfortunately.
The Get methods (for returning List, or single element are working!)
The problem is coming from
data: {"EventId": #Model.Event.EventId},
Instead of passing it in directly, use JSON.Stringify
var payload = {EventId: #Model.Event.EventId};
$.ajax({
type: 'POST',
url: "http://localhost:52617/api/Favorites/",
data: JSON.stringify(payload),
contentType: "application/json; charset=utf-8"
});
I'm assuming your FavoriteDto class looks something like this
public class FavoriteDto
{
public int EventId { get; set; }
}
The reason why you were getting an xml error is that the controller action
public async Task<IActionResult> PostFavorite([FromBody] FavoriteDto favorite)
could not parse 'favorite' so it was never initialised and was null. You then return ok(null) which caused a parsing error on your client side ajax when it recieved the response back from your server.
I encountered this error during a DELETE call to an ASP.Net Core 2.1 Web API. I removed the [FromBody] from the model that was expected from the DELETEcall, such as
public async Task<IActionResult> Delete(MyModel body)
{
// Just an example
await DoSomeWork(body);
return StatusCode(200);
}
but that did not solve the problem. However, after I returned some content with StatusCode, in this example just a simple string, the error was gone. So the following code should avoid the error in Firefox:
public async Task<IActionResult> Delete(MyModel body)
{
// Just an example
await DoSomeWork(body);
return StatusCode(200, "Deletion Successfull");
}
To add to the other answers, this error can also happen if jQuery ajax gets an empty string response where it expects valid JSON, or no content at all.
Check the value of the data in the done callback, if it's an empty string "" rather than undefined, you'll have to fix your server to return status code 204 (No Content) instead of an 200 (OK) with no content.
In WebAPI2 this would mean returning StatusCode(HttpStatusCode.NoContent) instead of Ok().

Spring MVC4 Session Management

I am developing a small spring project in which the login page has two type of users admin and staff. on login attempt i want to apply session using spring MVC4 and also wants to open jsp based on the user role(Admin or Staff).
the session has four/five fields like name,id,role,SessionId. i want these information to travel through the jsp pages. But i don't want to do this using url parameter passing.
I don't know how to do this because i am new in spring and this is my first project. Help me Please.
If someone can provide me the sample code and guide me on this then it would be very helpfull.
// Login.jsp code
var MobileNo=$('#mobno').val();
var StaffPwd=$('#pwd').val();
$.ajax
(
{
url: "http://localhost:8080/OnlineStore/kmsg/grocery/Login",
type: "POST",
data: {MobileNo: MobileNo,StaffPwd: StaffPwd},
success: function(data)
{
var vUserRole = data["UserRole"];
var vUserName = data["UserName"];
if(data==="")
{
alert("Login Failed");
}
else
{
if(vUserRole == "Admin")
{
alert("Login Success: " + vUserName);
window.location.href = "http://localhost:8080/OnlineStore/JspPages/City.jsp";
}
if(vUserRole == "CityAdmin")
{
alert("Login Success: " + vUserName);
window.location.href = "http://localhost:8080/OnlineStore/JspPages/Locality.jsp";
}
if(vUserRole == "Staff")
{
alert("Login Success: " + vUserName);
window.location.href = "http://localhost:8080/OnlineStore/JspPages/CustomerOrder.jsp";
}
}
},
error: function(e)
{
alert('Error:' +e)
}
}
);
// this is controller code
#RequestMapping("/Login")
public #ResponseBody UserServiceModel selectStaff(#RequestParam Map<String,String> requestParams) throws Exception
{
String MobileNo = requestParams.get("MobileNo");
String StaffPwd = requestParams.get("StaffPwd");
return staffAdapter.login(MobileNo, StaffPwd);
}
--------------
Just store your data in some serializable object. For e.g.:
public class SessionData implements Serializable {
private String name;
private String id;
private String role;
//etc..
}
then set this object as the session attribute when the user first logs in:
session.setAttribute("sessionData", new SessionData(name, id, role, etc...))
To access this object in a JSP page you can use: ${sessionScope['sessionData']}, to access a specific field:
${sessionScope['sessionData'].id}
${sessionScope['sessionData'].name}
${sessionScope['sessionData'].role}
To access these attributes in JavaScript files just define some page data in your JSP page, for e.g.:
<script type="text/javascript">
pageData = {
id: "${sessionScope['sessionData'].id}",
name: "${sessionScope['sessionData'].name}",
role: "${sessionScope['sessionData'].role}"
}
</script>
and in js file reference them via:
pageData.id
pageData.name
pageData.role
I can see that you trying to create roles based application. You are already using Spring MVC, any reason why not use Spring Security along with it? I would highly advice doing so as it will make your life much easier.

How do I prevent Ajax calls from keeping a session alive?

I'm using cookie authentication in MVC5. My web pages rely heavily on authenticated as well as unauthenticated Ajax calls every 1-5 seconds to keep data updated. Consequently, my users never log out of the site.
My ideal scenario: If a user is actively browsing or conducting actions on my site, keep the session alive. If they have left a page open after 10 minutes, I'd like their session to timeout and I’'ll use the failing Ajax calls to redirect to a login page. I think this would best be accomplished at the controller or action level.
I tried controlling the session state behavior as suggested below but the session still did not time out. After 65 seconds of hitting ReadOnly/Public once per second, I call ReadOnly/Authorized and successfully retrieve data from it.
Here is my CookieAuthentication configuration.
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = TimeSpan.FromMinutes(1),
});
}
My test page:
<div id="public"></div>
<div id="authorized"></div>
#section scripts{
<script>
function poll(times) {
var url = '/ReadOnly/Public';
$.ajax({
url: url,
dataType: 'json',
data: null,
cache: false,
success: function (data) {
$('#public').html(times + ' ' + data.test);
},
error: function (data) {
$('#public').html(times + ' ' + 'failed');
}
});
};
function checkAuth(times) {
var url = '/ReadOnly/Authorized';
$.ajax({
url: url,
dataType: 'json',
data: null,
cache: false,
success: function (data) {
$('#authorized').html(times + ' ' + data.test);
},
error: function (data) {
$('#authorized').html(times + ' ' + 'failed');
}
});
};
$(function () {
var times = 1;
setInterval(function () {
poll(times);
times++;
}, 1000);
setInterval(function () {
checkAuth(times);
}, 65000);
});
</script>
}
and test controller code (tried this with both the disabled and readonly options)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.SessionState;
namespace SessionTest.Controllers
{
[SessionState(SessionStateBehavior.ReadOnly)]
public class ReadOnlyController : Controller
{
[Authorize]
public ActionResult Authorized()
{
return Json(new
{
test = "ReadOnly and Authorized"
}, JsonRequestBehavior.AllowGet);
}
public ActionResult Public()
{
return Json(new
{
test = "ReadOnly and Public"
}, JsonRequestBehavior.AllowGet);
}
}
}
Maybe you need to have 2 separate web apps. One is for serving authenticated requests. Another one is for all public requests.
That's similar to how the Google Analytics script creates and maintains its own Session on Google side about your site without impacting your web application's internal session management. Otherwise, you will get stuck with the default behavior of ASP .NET the way it is handling cookies and keeps session alive.
Good luck.
I wouldn't implement a timeout in this situation. In fact I try to avoid them unless there is a fundamental and key reason why they are necessary, otherwise they just become an annoyance.
However if you do feel you need one, I would implement it in this case, by creating a separate javascript function which has a timer, and that is reset with user input. If the timer completes an ajax call is performed that executes a manual session invalidation on server side.
I would configure the listener method or class to not use session which will prevent it from being extended.
There are attributes available for both methods and controllers that provides different session modes.
More info here:
http://www.dotnet-tricks.com/Tutorial/mvc/906b060113-Controlling-Session-Behavior-in-Asp.Net-MVC4.html
Ajax calls will keep the session alive.
One approach will be to set a timeout on client side to delete cookie after some time.
I'm not sure you have anymore options.
If the calls every 5 sec are only to non-authenticated request, just keep the cookie out of the ajax request.
I think the sliding expiration is set to true by default.
I think perhaps when the call that is made to the action Public, it's made with cookie and thus extending the timeout.
public ActionResult Public()
{
return Json(new
{
test = "ReadOnly and Public"
}, JsonRequestBehavior.AllowGet);
}
If I set this below: (SlidingExpiration = false). I get the failed message.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = TimeSpan.FromMinutes(1.0),
SlidingExpiration = false
//Provider = new CookieAuthenticationProvider
//{
// OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
// validateInterval: TimeSpan.FromMinutes(30),
// regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
//}
});

ASP.NET WEB API 2 OWIN Authentication unsuported grant_Type

Hi I am trying to set up OAuth bearrer token authentication in my ASP.NET Web API 2 project.
I have two project one will be the WEB API Project and the other a SPA project.
Here is what I have done so far:
I have created the OWIN Startup class:
[assembly: OwinStartup(typeof(CodeArt.WebApi.App_Start.Startup))]
namespace CodeArt.WebApi.App_Start
{
public class Startup
{
static Startup()
{
PublicClientId = "self";
UserManagerFactory = () => new UserManager<UserModel>(new UserStore<UserModel>());
OAuthOptions = new OAuthAuthorizationServerOptions {
TokenEndpointPath = new PathString("/Token"),
Provider = new OAuthAuthorizatonServer(PublicClientId, UserManagerFactory),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
}
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static Func<UserManager<UserModel>> UserManagerFactory { get; set; }
public static string PublicClientId { get; private set; }
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalBearer);
app.UseOAuthBearerTokens(OAuthOptions);
}
}
I have configured Web API to use only bearer token authentication:
private static void ConfigureBearerTokenAuthentication(HttpConfiguration config)
{
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(Startup.OAuthOptions.AuthenticationType));
}
I have configured WEB API to support CORS:
private static void ConfigureCrossOriginResourseSharing(HttpConfiguration config)
{
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
}
I have created the OAuthAuthorizationServerProvider class.From this class I only managed to make my code call this method:
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
if(context.ClientId == null)
{
context.Validated();
}
return Task.FromResult<object>(null);
}
The if condition inside of it always gets executed.
On my spa project I have the following:
This is my viewModel:
var vm = {
grant_type: "password",
userName: ko.observable(),
password: ko.observable()
};
When the login button gets clicked I call this function:
var http = {
post:function(url, data) {
return $.ajax({
url: url,
data: data,
type: 'POST',
contentType: 'application/json',
dataType: 'jsonp'
});
}
}
function loginClick() {
var model = ko.mapping.toJS(vm.loginModel);
var rez = $.param(model);
http.post("http://localhost:3439/Token", rez)
.done(function (data) {
console.log(data);
})
.fail(function(eror, stuff, otherstuff) {
console.log(eror);
console.log(stuff);
console.log(otherstuff);
});
}
My first attempt I have set the post calls dataType to json and I got this errors:
OPTIONS ...:3439/Token 400 (Bad Request) jquery.js:7845
OPTIONS ...:3439/Token No 'Access-Control-Allow-Origin'
header is present on the requested resource. Origin
'...:3304' is therefore not allowed access.
jquery.js:7845
XMLHttpRequest cannot load ...3439/Token. No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin '...3304' is therefore not allowed
access.
The 3 dots represent http://localhost.
The second time arround I set it datatype to jsonp and I got back an error that stated unsupported "unsupported_grant_type".
Both calls make it to ValidateClientAuthentication that I mentioned above but they are both sent back as a failed request.
Now I am guessing that the problem is more related to how I am sending data instead of the grand_type because the SPA template in Visual Studion set's the grant type to grant_type: "password" like I did.
Also I have read that I have to serialize the data not send it in json in order for this to work here is the exact json serialized data that get's sent:
"grant_type=password&userName=aleczandru&password=happynewYear&moduleId=models%2FappPostModels%2FloginModel"
The model id property get's set to all my object in my SPA template by Durandal Framework.
Can anyone tell me what I am doing wrong I have been trying to figure this out for the last two days?
Add the following line of code to GrantResourceOwnerCredentials, which will add the header to the response.
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
for more information refer to:
web-api-2-0-cors-and-individual-account-identity
Like Robin Karlsson said, you should use:
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
in your Startup configuration.
And make sure it's the only cors statement (don't mix them) and the first statement in your Startup.

Resources