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

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))
//}
});

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.

How to handle an unauthorized ajax call

I am trying to figure out how to prevent a cors error from showing up in developer tools. The way I get the cors error is when I am using an application but in another tab/window I log out of that application but then go back to the other tab and try to do work. Below is my ajax call.
function RemoveScholarshipRequest(id, name) {
if (confirm("Are you sure you want to delete the scholarship request for " + name + "?")) {
var dataSource = $('#Pending').data('kendoGrid').dataSource;
$.ajax({
type: "POST",
url: '#Url.Action("RemoveRequest", "Admin")',
data: {id: id}
}).done(function (response, data, xhr) {
if (response.success) {
dataSource.read();
alert(response.responseText);
}
else if (!response.success) {
if (response.responseText === "Not Authenticated")
alert(response.responseText);
console.log("error", data.status);
//This shows status message eg. Forbidden
console.log("STATUS: "+JSON.stringify(xhr.status));
}
}).fail(function (response) {
console.log(response);
console.log(JSON.stringify(response));
//window.location.href = "/forms/ScholarshipDisbursement/Admin/PendingRequests";
});
}
}
The controller action that the above ajax method calls is below:
[AllowAnonymous]
[HttpPost]
public ActionResult RemoveRequest(string id)
{
if (!User.Identity.IsAuthenticated)
{
return Json(new { success = false, responseText = "Not Authenticated" }, JsonRequestBehavior.AllowGet);
}
if (User.IsInRole("Developer") || User.IsInRole("BannerAdmin"))
{
new ScholarshipRequestStore().DeleteScholarshipRequest(id);
return Json(new { success = true, responseText = "Successfully deleted" }, JsonRequestBehavior.AllowGet);
}
else
{
return Json(new { success = false, responseText = "You are not an authorized user" }, JsonRequestBehavior.AllowGet);
}
}
One way I get around the cors error is by putting AllowAnonymous on the method and then checking for authentication in the method itself but I don't really like that idea. Is there another way of resolving this issue?
Allow anonymous will not solve this, instead you need to send the allow origin header in your api. You can do this by enabling CORs in the startup class as follows
public void ConfigureServices(IServiceCollection services)
{
// Add Cors
services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
// Add framework services.
services.AddMvc();
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new CorsAuthorizationFilterFactory("MyPolicy"));
});
...
...
...
}
// This method gets called by the runtime. Use this method to configure
//the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
// Enable Cors
app.UseCors("MyPolicy");
//app.UseMvcWithDefaultRoute();
app.UseMvc();
...
...
...
}
and then using the "Enable cors" attribute on your controller
[EnableCors("MyPolicy")]
[AllowAnonymous]
[HttpPost]
public ActionResult RemoveRequest(string id)
read this for better idea https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-2.2
Note: I have allowed any origin to talk to the API, you can specify whatever origin you want like "https://example.com"
AllowAnonymous won't resolve a "cross-origin" request. The issue you are getting is due to tabbed browsing within your browser having a shared store of authenticated sessions. When you log out in tab 1, the session cookie is removed and then tab 2 is no longer authenticated. This is why AllowAnonymous "works" because without a current authenticated session, you're an anonymous user.
CORS, on the other hand, is when you allow calls to http://myservice.com to come from a different host like http://myclient.com. Anonymous access won't have any impact on that.

Spring MVC, Rest Ajax Call and Session Scope Objects

I want to solve following issue. I have a Spring-MVC Application with Thymeleaf, with a post request (sent by a form) I trigger a simulation task, what could take several minutes. The task process big number of data and we would like to have a progress bar via JavaScript. If there are two sessions, the simulation should be triggered independently and each browser shows its progress status.
Currently we have a solution, what is not really working well all the time.
The MVC Controller gets the Post request:
#Autowired SimulatorView view; // SESSION SCOPE
#PostMapping("/view")
public String run(#ModelAttribute(CHECKS) ChecksDto checksWrapper, Model model) throws InterruptedException, ExecutionException {
view.setStatisticDto(simulate(checksWrapper)); // Can take several minutes
return "simulation/result :: simulated";
}
When I trigger the simulation on my WebGUI, a progress bar has been displayed and via JavaScript I am calling Rest Methods frequently to ask for the status of the progress.
RestController
#RequestMapping("simulation/api")
public class SimulatorApi {
#Autowired SimulatorView view; // SESSION SCOPE
#RequestMapping("/progressStream")
public double progressStream() {
return view.getProgress().progressStream();
}
#RequestMapping("/progressInvoice")
public double progressInvoice() {
return view.getProgress().progressInvoice();
}
}
My JavaScript code snippet looks like:
function registerSimulationRunEvent() {
// this is the id of the form
$("#simulatorForm").submit(function(e) {
handleSimulationStarted();
var url = location.protocol + "//" + location.host + "/fdsclient/simulation/view";
$.ajax({
type: "POST",
url: url,
data: $("#simulatorForm").serialize(), // serializes the form's elements.
success: function(data) { handleSimulationFinished(); },
error: function(xhr, error) { handleSimulationError(); }
});
e.preventDefault(); // avoid to execute the actual submit of the form.
});
}
function handleSimulationStarted() {
replaceResultPanelRunning(); // THYMELEAF FRAGMENT EXCHANGE
}
function handleSimulationFinished() {
stopResultPanelAnimation(); // STOP PROGRESS BAR ANIMATION
replaceResultPanelSimulated(); // EXCHANGE THYMELEAF FRAGMENT
}
function handleSimulationError() {
stopResultPanelAnimation();
replaceResultPanelError();
}
function replaceResultPanelRunning() {
var url = // URL;
$("#resultDiv").load(url);
startResultPanelAnimation();
}
// ANIMATION
var animationInterval = null;
function startResultPanelAnimation() {
animationInterval = setInterval(animateResultPanel,4000);
}
function stopResultPanelAnimation() {
clearInterval(animationInterval); // stop the interval
}
function animateResultPanel() {
$("#simulatorProgressLabel").animate({opacity: '0.4'}, "slow");
$("#simulatorProgressLabel").animate({opacity: '1.0'}, "slow");
}
I know using session scope for rest services is a bad thing, but I didn`t know yet what is a good and easy solution. On the other hand currently different browser can simulate independently, but not always the progress bar works (especially when trigger first time mostly doesnt work). The IE11 only works when the Developer Tools are activated. When deactivating the tool while progress, the progress bar stops to grow.
What I would like to know is, how a good solution looks like when using template engine with Spring-MVC and Thymeleaf for triggering the process and displaying the status of progress via Javascript (as JQUery). Thank you in advance.
I have done a similar thing using Jquery AJAX POST submission. You can do something like this. This will submit POST request as a JSON format to the controller and wait for a response. A progress UI component can be shown during this waiting period.
//Start Progress display
function setStatistic(){
var data = JSON.stringify(//build your ChecksDto)
if (data) {
$.ajax({
url : '/view',
headers : {
'Content-Type' : 'application/json'
},
method : 'POST',
dataType : 'json',
data : data,
success : function(data) {
if (data.status == 200) {
// Stop Progress display
// Handle success status
}
},
error : function(xhr, status, error) {
// Stop Progress display
// Handle errors here
}
});
}
}
You also need to change Controller method to retrieve ajax requests as follows,
#ResponseBody
#PostMapping("/view")
public String run(#RequestBody ChecksDto checksWrapper, Model model) throws InterruptedException, ExecutionException
At least I found the solution in another Stackoverflow Page. The magic word is setting ajax cache to false.
$.ajaxSetup ({
// Disable caching of AJAX responses */
cache: false
});

Pending Requests When Posting Data to ASP.NET web API Controller from ASP.NET MVC Controller

Im working on a SOA project using, ASP.NET MVC and ASP.NET WEB API. Both projects are inside different solutions, so we have a mvc site and a web api site and the mvc site calls the web api site using http methods operations.
With that cenario in mind, im facing a strange behaviour when POSTING some data to the web API from my mvc controller. When I do GET operations everything works fine. Here's what Im doing:
Inside my cshtml view, i have a js code that do an ajax call to my MVC controller:
> JS Code inside Create.cshtml:
var create = (function () {
var _init = function (container, starter, url) {
$(starter).autocomplete({
minLength: 3,
focus: function () {
return false;
},
select: function (event, ui) {
var terms = this.value.replace(" ", "").split(',');
terms.pop();
terms.push(ui.item.label);
terms.push("");
this.value = terms.join(",");
return false;
},
search: function (event, ui) {
if (_extractLast(this.value).length < 3)
return false;
return true;
},
source: function (request, response) {
_execute(request, response, url);
}
});
};
var _extractLast = function (term) {
var termArray = term.split(',');
var lastTerm = termArray[termArray.length - 1];
return lastTerm.replace(' ', '');
};
var _execute = function (request, response, url) {
var responseCallback = response;
$.ajax({
url: '\Comunication\Contacts',
async: true,
type: 'POST',
dataType: 'json',
data: { termo: _extractLast(request.term) },
success: function (data) {
responseCallback($.map(data, function (item) {
return { label: item.Name, value: item.Id }
}));
}
});
}
return { init: _init };})();
And from inside my controller I call a method from a specialized Service layer to do a POST request to my WEB API SITE doing the SOA thing, as you can see bellow:
> The MVC CONTROLLER CODE:
public class ComunicationController : BaseController{
[HttpPost]
public async Task<JsonResult> Contacts(string termo)
{
var contacts = new ContactServices.ConsumeAsync(new ContactParameter{Term: termo});
return Json(contacts, JsonRequestBehavior.AllowGet);
}
}
> The Layer SERVICE CODE:
public class ContactServices{
public async Task<List<ContactsDTO>> ConsumeAsync(ContactParameter parameter)
{
using (System.Net.Http.HttpClient httpClient = new System.Net.Http.HttpClient())
{
httpClient.BaseAddress = new Uri("http://localhost:123456/api/comunication/contacts");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await httpClient.PostAsJsonAsync(new Uri("http://localhost:123456/api/comunication/contacts"), parameter);
return JsonConvert.DeserializeObjectAsync<ResponseProxy<List<ContactsDTO>>>(response.Content.ReadAsStringAsync().Result).Result.data;
}
}
}
My WEB API SITE accept the request, process the request like expected, no concerns here. But when I return the HttpResponseMessage from my WEB API CONTROLLER to the MVC Site under a POST operation, the request response hangs on forever pending status according to my network monitor....
So basically my request is hanging, the Protocol status is Pending and I never get the request response, heres is a pic:
Pending Status from My Post Request
As I said, when I do GET requests to my web api, everything works fine but when I do POST requests this happens...
Your problem lies in this line:
return JsonConvert.DeserializeObjectAsync<ResponseProxy<List<ContactsDTO>>>(response.Content.ReadAsStringAsync().Result).Result.data;
The use of the Result property of your Task generates a deadlock on you MVC controller. If an API is asynchronous, then always await the task.
var content = await response.Content.ReadAsStringAsync();
var deserialized = await JsonConvert.DeserializeObjectAsync<ResponseProxy<List<ContactsDTO>>>(content);
return deserialized.data;
To learn more about why you should never block on async methods check this great post:
Don't Block on Async Code

Forms validation in Nancy not working with AJAX login requests

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.

Resources