Handling session timeout in mvc - c#

I am trying to implement session timeout in .net core application. Redirecting to login page is working fine in non-ajax request/full postback but not in case of ajax request. The login page is displayed within the layout/current page in ajax request.
I have written a middleware which will call the controller method first in which redirection login is written.Below is my code.
Middleware
app.Use(async (ctx, next) =>
{
if (ctx.GetTenantContext<AppTenant>() == null && !ctx.Request.Path.ToString().Contains("/Home/Redirect"))
{
string redirect = "/Home/Redirect/";
if (ctx.Request.Path.ToString().Contains("Admin"))
{
redirect = "/Home/Redirect/Admin";
}
else
{
redirect = "/Home/Redirect/Trainee";
}
ctx.Response.Redirect(redirect, true);
}
else
{
await next();
}
});
Home Controller
[Route("/Home/Redirect/{AppType?}")]
public async Task<IActionResult> Redirect()
{
string appType = string.Empty;
string clientName = string.Empty;
if (!string.IsNullOrEmpty(Convert.ToString(RouteData.Values["AppType"])))
{
appType = Convert.ToString(RouteData.Values["AppType"]);
}
await _signInManager.SignOutAsync();
HttpContext.Session.Clear();
if (!string.IsNullOrEmpty(appType))
{
if (appType == "Admin")
{
if (HttpContext.Request.Cookies != null)
{
if (HttpContext.Request.Cookies["clientnamebe"] != null)
{
clientName = HttpContext.Request.Cookies["clientnamebe"].ToString();
}
}
return RedirectToRoute(new
{
controller = "Admin",
action = "Login",
clientname = clientName
});
}
else
{
if (HttpContext.Request.Cookies != null)
{
if (HttpContext.Request.Cookies["clientnamefe"] != null)
{
clientName = HttpContext.Request.Cookies["clientnamefe"].ToString();
}
}
return RedirectToRoute(new
{
controller = "Account",
action = "Login",
clientname = clientName
});
}
}
return View();
}
and in Login method I am just returning a view
[Route("Account/Login/{clientname}", Name = ApplicationType.FRONTEND)]
[ResponseCache(Location = ResponseCacheLocation.None, NoStore = true, Duration = 0)]
public async Task<IActionResult> TraineeLogin(string returnUrl)
{
Return View();
}
My ajax request, though I am just loading related action results in div on tab click.
$('#tabstrip a').click(function (e) {
e.preventDefault();
var tabID = $(this).attr("href").substr(1);
localStorage.setItem("ClientCourseTab", '#'+tabID);
$("#" + tabID).html("");
var link = '#Url.Action("-1", "Course")';
link = link.replace("-1", tabID);
$("#" + tabID).load(link); // here actual request made
var appendValue = tabID.replace('_FrontEnd', '');
var appendValue = appendValue.replace('_', '');
window.location.hash = appendValue;
$(this).tab('show');
});
Any help on this appreciated !

The server does return the Redirect response in this case for the ajax request but the user doesn't get redirected to the Login page. Why? The reason is that the HTTP redirect is implicitly processed by the browser and actually never arrives to the ajax success callback. The browser processes the redirect and delivers a 200 code with the content of the redirect's destination (the login page in your case).
This is not as simple as it sounds, there are few workarounds but all of those quite complicate things. Here is one solution that you might try to implement:
How to manage a redirect request after a jQuery Ajax call
Another solution can be to have some javascript code running at a specific interval on each page to check whether the session has expired (by querying the server which complicates things even more). Whenever this javascript code detects that the session has expired, user should be immediately taken to the login page instead of waiting for an ajax request to be triggered.
The problem with querying the server would be that if you have some kind of sliding expiration of auth ticket on the server, the ticket might get renewed and session might never expire.

Related

asp .net core app doesn't set response cookie in IE and EDGE but works well in firefox and chrome

I have a login controller with a post action which redirects to home page after successful login. I am using following code for redirection, which works well in Chrome and Firefox. but doesn't redirect in IE and EDGE, and response cookie not set
private ActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToRoute("default", new { controller = "", action = "" });
}
}
My Login action
public IActionResult Login(string userName, string password)
{
try
{
if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
throw new InvalidOperationException("Username/Password can not be empty");
var session = CreateSession(userName, password);
var returnUrl = Request.Query["returnUrl"];
return RedirectToLocal(returnUrl);
}
catch (Exception ex)
{
ModelState.AddModelError("Login", ex.Message);
}
return View();
}
I am using my own session management for which I set session cookies like following
CookieOptions option = new CookieOptions();
option.Path = AppPath;
option.Domain = AppHost;
httpContextAccessor.HttpContext.Response.Cookies.Append(AppSessionToken, "SomeSessionId", option);
After searching a lot for exact answer, I found that Internet Explorer (all versions) doesn't allow you to specify a domain of localhost, a local IP address or machine name. When you do, Internet Explorer simply ignores the cookie.
So I removed following line
option.Domain = AppHost;
from my codes and everything start working as expected on both IE and EDGE.
Since you didn't post your route mapping from Startup.cs, I am not sure why it didn't work for you. Maybe you shouldn't override the controller and action parameter by passing new { controller = "", action = "" } into the RedirectToRoute() method?
Instead, have you tried just calling the method with the route name, like
return RedirectToRoute("default");
Or, you can use RedirectToAction()
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
// action, controller and route values
return RedirectToAction("index", "home", new { area = "" });

ASP.Net Session changes between AJAX calls to ASPX WebMethod

I have a single page application that starts off with default.aspx creating the basic HTML layout and then I use AJAX calls to get data from static WebMethods in other aspx pages. I also have a KeepAlive.aspx that reloads every once in a while to keep the session alive.
The login form that appears initially calls the login webmethod which uses an external web service to login and store user information in the session. I use HttpContext.Current.Session to get and set values in the session.
The very next web method that I call is to getuserpreferences from the external web service using identity information obtained at login time.
This is all working fine and dandy on various IIS servers. Recently we deployed this on a new server and it is not working there.
It does successfully login and saves the user values in session, but when the next ajax call to getuserpreferences happens, the getuserpreferences web method tries to get the session values... and it is NOT there any more!
I put in a bunch of logging and saw that session Id is changing! The login session id is different from the session id that I see from get userpreferences, even though both use HttpContext.Current.Session.
On the servers that it does work, sometimes (randomly?) it does behave in a similar way: it loses session values and throws me back to login.
Please provide help/tips to trace this issue and ensure the same session continues across ajax calls. This is not an MVC or WebAPI project; it is a simple aspx application.
UPDATE (Jan 8): I found the difference in behavior between the instances where this works and where it doesn't: ASP.Net_SessionId cookie value is being set where it works, but in this case it is not being set and is therefore not maintaining session state. Now I need to figure out why it is not setting.
Update 2 (Jan 8): When we installed a certificate on the server and implemented https, the session Id cookie started appearing. So although the issue is no longer urgent, I do want to find out why HTTP did not work.
Code:
WebServiceHelper.js
WebServiceHelper.prototype.invokeServiceMethod = function(serviceName, methodName, params, returntype) {
var self = this;
var obj = {};
var def = $.Deferred();
var url = serviceName + "/" + methodName;
$.support.cors = true;
$.ajax({
url: url,
data: ko.toJSON(params),
type: "POST",
dataType: "json",
contentType: "application/json; charset=utf-8",
success: function(mydata) {
if (mydata && mydata.d) {
def.resolve(mydata.d);
}
},
error: function(msg) {
def.reject(msg);
}
});
return def.promise();
};
Data Provider.js:
DataProvider.prototype.loginUser = function(email, password, token) {
var self = this;
var def = $.Deferred();
var webServiceHelper = new WebServiceHelper();
webServiceHelper.invokeServiceMethod("Login.aspx", "LoginUser", { email: email, password: password, token: token }, "json").done(function (retObj) {
self.userObj = retObj.userinfo;
self.loginTime = new Date();
self.loadInitialData();
def.resolve();
}).fail(
function(retObj1) {
def.reject(retObj1);
});
return def.promise();
};
Preference Manager.js
PreferenceManager.prototype.invokeGetPreferences = function (prefKey) {
var self = this;
var def = $.Deferred();
var webServiceHelper = new WebServiceHelper();
webServiceHelper.invokeServiceMethod("WebMethods.aspx", "GetPreferences", { key: prefKey }, "json").done(function (retObj) {
def.resolve(retObj);
}).fail(
function (retObj1) {
def.reject(retObj1);
});
return def.promise();
};
Login.aspx.cs:
[WebMethod(EnableSession=true)]
public static string LoginUser(string email, string password, string token)
{
if (Authenticate(email, password, token))
{
HttpContext.Current.Session["vanalyticsLastLoginDateTime"] = DateTime.Now;
var userjson = GetUserJson();
Logger.Debug("Login - Session Id {0} - Returning user json {1}", HttpContext.Current.Session.SessionID, userjson);
return userjson;
}
return "Error: " + _validationError;
}
private static bool Authenticate(string stremail, string strpassword, string token)
{
var vplug = new vPlugin();
HttpContext.Current.Session.Remove("vUserInfo");
vplug.Authenticate(HttpContext.Current, appsurl, stremail, strpassword, token);
_validationError = vplug.LastException != null ? vplug.LastException.Message : null;
return (HttpContext.Current.Session["vUserInfo"] != null);
}
vPlugin code: (eventually calls setuser)
private void SetUser(HttpContext obj, userInfo user)
{
HttpSessionState session = obj.Session;
session["vUserInfo"] = user;
....
session["vDataToken"] = setting.ContainsKey("token") ? setting["token"] : "0-0";
}
WebMethods.aspx:
[WebMethod(EnableSession=true)]
public static string GetPreferences(string key)
{
var myadr = new ADR.VrtDataService { Timeout = 20000 };
var token = GetTokenFromSession();
try
{
var result = myadr.getPreference(token, key);
myadr.Dispose();
return result;
}
catch (Exception ex)
{
return "Error: " + ex.Message;
}
}
public static string GetTokenFromSession()
{
var token = "";
var val2 = HttpContext.Current.Session["vDataToken"];
if (val2 != null)
{
token = (string) val2;
}
else
{
Logger.Error("Token is blank, session id is " + HttpContext.Current.Session.SessionID);
}
return token;
}
I am getting 'Token is blank' and the session id is different from the Id logged by the login method earlier.
Also, please note that the entire code is in JavaScript and aspx only serves as RESTful API with session.
It's Simple Just Use
HttpContext.Current.Session["user_id"] = ""

Session Available or not

I am calling a void function using jquery ajax in mvc3. In that function when the Session is out then also it will come to success function of ajax. I need to know whether the Session is available or not before sending the request or inside the success function of ajax.
controller Action:
protected override void Save(Query query, string queryTitle)
{
}
Why not catch the expiry of the session on the server, return an HTTP 401 Unauthorized, then check for this response in jquery and pop up a "Your session has expired, please log in again" page?
Detecting Session expiry on ASP.NET MVC
How to set HTTP status code from ASP.NET MVC 3?
How do I get the HTTP status code with jQuery?
The code you need on the initial server call is:
protected void Save(Query query, string queryTitle)
{
// would probably be better to refactor this bit out into its own method
string sCookieHeader = Request.Headers["Cookie"];
if (Context.Session != null
&& Context.Session.IsNewSession
&& sCookieHeader != null
&& sCookieHeader.IndexOf("ASP.NET_SessionId") >= 0)
{
// session has expired
if (Request.IsAuthenticated)
{
FormsAuthentication.SignOut();
}
Response.StatusCode = 401
}
else
{
// we're authenticated, so do the save
}
}
and on the client:
$.ajax(serverUrl, {
data: dataToSave,
statusCode: {
200: function(response) {
// all good, continue
401: function (response) {
// session expired!
// show login box
// make ajax call to reauthenticate
// call save method again
},
});
Your reauthentication call would look something like this:
public ActionResult Reauthenticate(username, password)
{
if (IsValidUser(username, password))
{
// sometimes used to persist user roles
string userData = string.Join("|",GetCustomUserRoles());
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // ticket version
username, // authenticated username
DateTime.Now, // issueDate
DateTime.Now.AddMinutes(30), // expiryDate
isPersistent, // true to persist across browser sessions
userData, // can be used to store additional user data
FormsAuthentication.FormsCookiePath); // the path for the cookie
// Encrypt the ticket using the machine key
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
// Add the cookie to the request to save it
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
cookie.HttpOnly = true;
Response.Cookies.Add(cookie);
}
}
(Adapted from How to set HTTP status code from ASP.NET MVC 3?)
Why not try this ?
public void Save()
{
if (Session.IsNewSession)
{
throw new Exception("This session was just created.");
}
//Go on with save matter...
}
This should return a status 500 on your AJAX function and should cause the response to fall in the fail method you defined.
Another way is to setInterval() on the client that continually sends a dummy request to the server to keep the session alive, at least when the user is editing. This might be the best way to prevent them user from losing work. You could also use this to detect loss connectivity.
When the page is loaded first time , pass the current SessionId to the client side and assign to the local javascript variable.Next have a method which will return the current SessionId before making the Save method call from the AJAX , compare the local variable against the Session Id you have received.
public string GetCurrentSessionId(){
return HttpContext.Current.Session.SessionId;
}
Javascript function
$.ajax({
Url : 'GetCurrentSessionId',
success : function(result){
if(result === "LOCALSESSIONID")
CALL THE SAVE METHOD
else
alert('Session is expired');
}
});

Fixing the action back to an account page in asp.net mvc application

I have an asp.net mvc4 application, in which i have to logout from an account :
if (_fonction == "User")
{
if (_is_admin == true) return RedirectToAction("Index");
else
{
Session["user"] = _u;
return RedirectToAction("Index", "User");
}
}
in the controller User
public ActionResult Index()
{
if (Session["user"] == null) return RedirectToAction("Index", "Home");
return View(Session["user"]);
}
the action Logout
public ActionResult Logout()
{
if (_is_admin) { Session["user"] = null; return RedirectToRoute("Administration"); }
else { Session["user"] = null; return RedirectToAction("Index", "Home"); }
}
i do this : i log in to a user account then i disconnect so i'am in the home page , then i click into the back button of the browser i got the page of the account. When i refresh i returned to the home page. i think that the problem is in the cache and i don't think that make it null is a good idea .
So how can i fix this problem?
You can try to add [OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")] as an attribute to your User controller's Index action to force non-cached results.
But I would strongly suggest to just use AuthorizeAttribute because this will prevent unauthorized web requests done on a specific view. The benefit of using this is that you still give the users the liberty to cache your views and be secured at the same time.
if you do not want to clean your cache then below is the javascript which helps you to hard refresh your page on click of the browser back button
if (window.name != "") { // will be '' if page not prev loaded
window.name = ""; // reset to prevent infinite loop
window.location.reload(true);
}
window.name = new Date().getTime();
put the above "javascript" code on your page. so, it will hard refresh your page.

When attempt logoff, The provided anti-forgery token was meant for user "XXXX", but the current user is ""

I have an MVC 4 app and having issues when the forms session expires and then the user tries to logoff.
Ex.
timeout is set to 5 min.
User logs in.
User does nothing for 10 min.
User clicks the LogOff link.
User gets error: "The provided anti-forgery token was meant for user "XXXX", but the current user is ""."
The user then had to go through some gymnastics to get around this so they can re-login then re-logout (logout is being used to close their timecard for the day).
I think I understand why this happens...but not sure how to fix it.
EDIT:
Why I think this is happening is because originally when the page is loaded, the AntiForgery token is generated for the currently logged in user. But then when the session expires and they try to navigate to the logoff page, the current user is "" instead of the actual user. As such there is a mismatch and the error is rendered.
Actually you can handle it with IExceptionFilter, that will redirect to the /Account/Login
public class HandleAntiForgeryError : ActionFilterAttribute, IExceptionFilter
{
#region IExceptionFilter Members
public void OnException(ExceptionContext filterContext)
{
var exception = filterContext.Exception as HttpAntiForgeryException;
if (exception != null)
{
var routeValues = new RouteValueDictionary();
routeValues["controller"] = "Account";
routeValues["action"] = "Login";
filterContext.Result = new RedirectToRouteResult(routeValues);
filterContext.ExceptionHandled = true;
}
}
#endregion
}
[HandleAntiForgeryError]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
}
Also you can use [HandleError(ExceptionType=typeof(HttpAntiForgeryException)...] but it requires customErrors On.
The answer of #cem was really helpful for me and I added a small change to include the scenario of ajax calls with antiforgerytoken and expired session.
public void OnException(ExceptionContext filterContext)
{
var exception = filterContext.Exception as HttpAntiForgeryException;
if (exception == null) return;
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = 403;
filterContext.ExceptionHandled = true;
}
else
{
var routeValues = new RouteValueDictionary
{
["controller"] = "Account",
["action"] = "Login"
};
filterContext.Result = new RedirectToRouteResult(routeValues);
filterContext.ExceptionHandled = true;
}
}
... and on the client side you can add a global ajax error handler to redirect to the login screen...
$.ajaxSetup({
error: function (x) {
if (x.status === 403) {
window.location = "/Account/Login";
}
}
});

Categories

Resources