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');
}
});
Related
I'm building a web api and I have a method in my controller which gives the user a cookie. I can see it in the browser it is set, everything is fine.
[HttpGet]
[Route("[controller]/cookie")]
public IActionResult Cookie()
{
string cookieName = "av225461";
string key = $"blahblah";
HttpContext.Response.Cookies.Append(
cookieName, key,
new CookieOptions() { SameSite = SameSiteMode.Unspecified, HttpOnly = true, Expires =
DateTime.UtcNow.AddMinutes(15)/*, Secure = true*/ });
return Ok("");
}
But I am not able to read it in my Get method, if I am sending a request to my controller. The Cookies.Count is 0. Even if the cookie is set in browser and inthe requestheader of Firefox.
if (Request.Cookies.Count > 0)
{
//some code here
}
With postman sometimes it worked, and sometimes not. Someone an idea?
I am having a strange issue with my login web page, where user's login session gets lost occasionally after successful login.
When the user accesses the next page after login, it would be redirected back to the login page because the login session doesn't exist.
It happens about 10% of the time randomly (seems so) among many users, and usually the same user who experienced this would get in after the second try. It doesn't seem to me that the system was busy when this happens.
Here's the login process:
User enters the username and password on the login page (login.asp),
then the login credentials are sent from the login page (login.asp)
to a Asp.Net login handler (LoginHandler.ashx) via an javascript
Ajax call.
The Login Handler then validates the credential and set a login
session (Session["CC_VendorInfo"]) which includes user's information.
Then the Login Handler sends back an OK response to the Ajax call.
Upon receipt of the OK response, the javascript on Login.asp would
send the user to the next page.
When user requests the next page, the server tries to retrieve info
from the Login session (Session["CC_VendorInfo"]). If the session is null then it would
redirect the user back to the login page.
I have some debug log on the server to print out the login session contents which proves that the session is successfully set in Step 2. And I also have log showing that in Step 4, the login session sometimes is NULL.
More Notes:
The application is deployed on a standard Windows virtual machine in Azure, so the session management limitations caused by load balancing don't seem to apply to this problem.
What would be the possible cause of this issue? Any answer is much appreciated!
Part of the code in Login.asp:
function Login() {
$.ajax({
type: "POST",
url: "../Home/LoginHandler.ashx",
data: {
user: $("#UserName").val(),
pswd: $("#PassWord").val()
},
success: function (response) {
ProcessLogin(response);
},
error: function (xhr, status) {
alert("Failed to login.");
}
})
return false;
}
function ProcessLogin(response) {
// ... ...
if (response == 'OK') {
window.location.href = strNextUrl;
} else {
alert("Failed to login.");
}
}
Part of the code in LoginHandler.ashx:
if (CheckCredential(context, sUserName, sPassword))
{
ClsVendorInfo oVendorInfo = new ClsVendorInfo();
oVendorInfo.iVendorID = iVendorID;
oVendorInfo.sUserName = sUserName;
// set the login session here
ClsCommonUI.SetVendorInfoSession(oVendorInfo);
sResp = "0|OK";
}
A function in an utility class to set the login session:
static class ClsCommonUI
{
// ... ...
public static bool SetVendorInfoSession(ClsVendorInfo oVendorInfo)
{
ClsVendorInfo oSessVendorInfo = HttpContext.Current.Session["CC_VendorInfo"] as ClsVendorInfo;
if (oSessVendorInfo != null &&
(oSessVendorInfo.iVendorID != oVendorInfo.iVendorID ||
oSessVendorInfo.iUserID != oVendorInfo.iUserID))
{
DebugLog(oSessVendorInfo,
string.Format("Login Session Changed [{0}] (before): {1}",
HttpContext.Current.Session.SessionID,
oSessVendorInfo.Print()));
}
// update session
HttpContext.Current.Session.Remove("CC_VendorInfo");
HttpContext.Current.Session["CC_VendorInfo"] = oVendorInfo.Clone();
oSessVendorInfo = HttpContext.Current.Session["CC_VendorInfo"] as ClsVendorInfo;
// I can see the session content being print out in the debug log
DebugLog(oSessVendorInfo,
string.Format("SetVendorInfoSession [{0}]: {1}",
HttpContext.Current.Session.SessionID,
oSessVendorInfo.Print()));
// ... ...
return true;
}
// ... ...
}
When the user requests the next page, here's the code to check the Login session:
public static int GetVendorInfo(HttpRequest oReq,
ClsDBAccess oDBAccess,
out ClsVendorInfo oVendorInfo,
[CallerFilePath] string sCallerFileName = "")
{
HttpContext context = HttpContext.Current;
ClsVendorInfo oSessionVendorInfo = context.Session["CC_VendorInfo"] as ClsVendorInfo;
if (oSessionVendorInfo != null &&
oSessionVendorInfo.iVendorID != 0)
{
// continue processing
//... ...
}
else
{
// No Session - go back to login
RedirectToLogin(HttpContext.Current);
}
}
If this isn't your issue, I can delete this answer.
Chrome's new SameSite change for cookies is about to drop or already did. If you don't have SameSite attribute on a cookie, chrome will just drop the cookie outright. If it is set to Lax, then all cross-domain iframes will drop the cookie. If this works fine in IE, then this is probably your issue. If you use SameSite=None, cross domain iframe works, but older browsers may stop working, too...
I am currently trying to replace our company wide user authentication that we use for all our internal web apps and what not as our current one was made in 2006 and fails on the regular. I was told to make it as simple as possible to implement on all existing projects. It is a .NET class library. It's .dll will be added as a reference to existing projects.
I am having an issue where I can log in exactly one time after all cookies have been cleared. Once I logout and log back in I get System.ArgumentException: Invalid value for 'encryptedTicket' parameter. I found some posts suggesting the cookie may be null, or I'm not trying to decrypt the name and not the value, but that wasn't the case. This happens on chrome and edge.
The user is authenticated every time though, assuming the correct username and password is used as I get redirected to the success page.
After authentication I add a cookie and then redirect.
private void AddCookie(int compID, bool persist, HttpContext httpContext)
{
httpContext.Request.Cookies.Add(SetUpSession(compID, persist));
FormsAuthentication.RedirectFromLoginPage(compID.ToString(), persist);
}
My method for creating the cookie
private HttpCookie SetUpSession(int companyID, bool persist)
{
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // ticket version
companyID.ToString(), // authenticated username
DateTime.Now, // issueDate
DateTime.Now.AddMinutes(30), // expiryDate
persist, // true to persist across browser sessions
FormsAuthentication.FormsCookiePath); // the path for the cookie
String encTick = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie("Cookie", encTick);
cookie.HttpOnly = true;
return cookie;
}
After I redirect to the success page there is a snipped of code that checks to see if the user is logged in. This is where the error happens
public dynamic isLoggedIn(HttpContext httpContext)
{
AuthenticationUtilities authUtil = new AuthenticationUtilities();
if (httpContext.Response.Cookies["Cookie"] != null)
{
companyID = authUtil.Authenticate(httpContext.Request.Cookies["Cookie"]);//the error occurs here
authUtil = new AuthenticationUtilities(companyID);
return authUtil;
}
else
{
httpContext.Response.Redirect("~/login.aspx");
return null;
}
}
The method that decrypts the cookie
public int Authenticate(HttpCookie cookie)
{
FormsAuthenticationTicket authTick = FormsAuthentication.Decrypt(cookie.Value);
return int.Parse(authTick.Name);
}
this method is called on any page that requires the user to be logged in, like this.
LMFJAuth.AuthenticationUtilities auth = _LMFJAuth.isLoggedIn(HttpContext.Current);//if the cookie is null it redirects to login.
This is the logout method
public void LogOut(HttpContext httpContext)
{
FormsAuthentication.SignOut();
HttpCookie cookie = new HttpCookie("Cookie");
cookie.Expires = DateTime.Now.AddMinutes(-1);
httpContext.Session.Clear();
httpContext.Response.Cookies.Add(cookie);
httpContext.Response.Redirect(FormsAuthentication.LoginUrl);
}
Can somone help explain what may be going on in which the value for the encrypted ticked is coming up as invalid after the first successful login/logout?
For me it was that the encrypted value of cookie.Value was coming up as greater than the maximum value of 4096, being 4200 in my case. I had just added some role strings to the user data.
I found it help to look up the source code of Microsoft classes when I'm stuck, in this case I used:
http://www.dotnetframework.org/default.aspx/DotNET/DotNET/8#0/untmp/whidbey/REDBITS/ndp/fx/src/xsp/System/Web/Security/FormsAuthentication#cs/1/FormsAuthentication#cs.
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.
I have some proof concept code for a HTTP module. The code checks to see if a cookie exists, if so it retrieves a value, if the cookie does not exist it creates it and sets the value.
Once this is done I write to the screen to see what action has been taken (all nice and simple). So on the first request the cookie is created; subsequent requests retrieve the value from the cookie.
When I test this in a normal asp.net web site everything works correctly – yay! However as soon as I transfer it to SharePoint something weird happens, the cookie is never saved - that is the code always branches into creating the cookie and never takes the branch to retrieve the value - regardless of page refreshes or secondary requests.
Heres the code...
public class SwithcMasterPage : IHttpModule
{
public void Dispose()
{
throw new NotImplementedException();
}
public void Init(HttpApplication context)
{
// register handler
context.PreRequestHandlerExecute += new EventHandler(PreRequestHandlerExecute);
}
void PreRequestHandlerExecute(object sender, EventArgs e)
{
string outputText = string.Empty;
HttpCookie cookie = null;
string cookieName = "MPSetting";
cookie = HttpContext.Current.Request.Cookies[cookieName];
if (cookie == null)
{
// cookie doesn't exist, create
HttpCookie ck = new HttpCookie(cookieName);
ck.Value = GetCorrectMasterPage();
ck.Expires = DateTime.Now.AddMinutes(5);
HttpContext.Current.Response.Cookies.Add(ck);
outputText = "storing master page setting in cookie.";
}
else
{
// get the master page from cookie
outputText = "retrieving master page setting from cookie.";
}
HttpContext.Current.Response.Write(outputText + "<br/>");
}
private string GetCorrectMasterPage()
{
// logic goes here to get the correct master page
return "/_catalogs/masterpage/BlackBand.master";
}
This turned out to be the authentication of the web app. To work correctly you must use a FQDM that has been configured for Forms Authentication.
You can use Fiddler or FireBug (on FireFox) to inspect response to see if your cookie is being sent. If not then perhaps you can try your logic in PostRequestHandlerExecute. This is assuming that Sharepoint or some other piece of code is tinkering with response cookies. This way, you can be the last one adding the cookie.