I have a page where a user logs in to a back-end application via a web service. The web service returns a session ID which I want to store in a cookie for 40 minutes, as after 40 minutes the back-end application automatically closes the session.
My code to write the cookie:
private void SetCookie()
{
Response.Cookies.Add(new HttpCookie("Cookie_SessionID"));
Response.Cookies["Cookie_SessionID"].Value = ni.NicheSessionID;
Response.Cookies["Cookie_SessionID"].Expires = DateTime.Now.AddMinutes(40);
//.... after a few more things
Response.Redirect(returnUrl);
}
Then on the receiving page I have this:
private HttpCookie GetCookie()
{
HttpCookie cookie = Request.Cookies["Cookie_SessionID"];
if (cookie != null && cookie.Value != null)
{
return cookie;
}
return null;
}
For some reason the cookie returned by GetCookie() always has an Expires value of 0001-01-01 00:00:00, even though when I view cookies in the browser it has the correct expiry time.
Having read this which states expired cookies are simply not sent to the server, I assume what could be happening is that the cookie is being written correctly but the browser is not sending the expiry date because it's actually unnecessary?...
My problem is that I want to capture precisely that - the cookie has 'expired' and so they have to log in again - but I need to display a message along the lines of "I know you have already logged in but you'll need to do it again" type thing.
Thanks
The browser will not send anything to the server except the cookie name and value. All of the other properties (expires, domain, path, httponly, ...) cannot be retrieved on requests after the cookie has been set.
If you want to display such a message then you will need some other mechanism of detecting that the user was logged in. You might set a presence cookie for a year or so and check to see if it exists.
The more accepted way to deal with this is to redirect the user to a login page when they try to access a protected resource and display some message along the lines of "You need to log in to view this page. If you were previously logged in, your session may have expired."
(Also note that you should be re-setting the cookie on every request, so that the user will not be logged out if they continue to use the site. It's not clear from your code whether you are doing this or not.)
The HTTP protocol does not send cookie expiration dates to the server.
Related
I am having trouble with a security issue of asp.net. On log out I want to make sure the session is destroyed so that someone can't take the same sessionid and auth cookies and edit there cookies and the server still responses to the session.
FormsAuthentication.SignOut();
Session.Abandon();
Session.RemoveAll();
Session.Clear();
I have also tried to write new cookies to the client
// clear authentication cookie
HttpCookie cookie1 = new HttpCookie(FormsAuthentication.FormsCookieName, "");
cookie1.Expires = DateTime.Now.AddYears(-1);
Response.Cookies.Add(cookie1);
HttpCookie cookie2 = new HttpCookie("ASP.NET_SessionId", "");
cookie2.Expires = DateTime.Now.AddYears(-1);
Response.Cookies.Add(cookie2);
I have tried using the following in different orders and still no dice. I can am still logged in if I use the original sessionid and Auth cookies. I want the server to totally forget about that sessionid when I logout. Is this even possible ?
There is no way, on the server side, to clear an authentication session permanently. You can clear the cookie, but If someone has made a copy of it, they can simply re-use it and they will get access to the site. Auth cookies are encrypted validation tickets, which means that the site decrypts them, and validates that they decrypt correctly and are not expired. If that's the case, then the auth cookie is valid.
Unfortunately, there's no way around this, short of using a short expiration value and updating the expiration date on a sliding window, but that means if the user is idle for a few minutes they will have to log in again.
There is no "session id" associated with an authentication ticket, although you could certainly generate a unique ID and store it in the ticket and then store that id on the server and compare it on each request to add an additional layer of security. When you want to kill that cookie, you clear whatever ID on the server and they can no longer log in, even if the ticket is otherwise valid.
Session cookies on the other hand do have id's and are associated with internal storage mapped to that ID, so when you clear the session you also delete the storage... if you reuse that cookie then it's just going to get null data.
You can also add more security by always using encrypted cookies, and using HTTPS. HTTPS does not store the cookies in a way that they can be acquired and re-used (they're encrypted with the SSL session, and once that SSL session is done the cookies are unusable).
Another option is to use non-persistent cookies, which don't get stored on the server.. although they can still be captured by man in the middle attacks.
To understand why there is no way around the problem, you have to understand that authentication cookies are intended to be long lived, and stored on the users computer.. When you come back, it can be a different session. Therefore, the only way to know if the users is authenticated is to simply rely on whether the cookie can be decrypted using the servers credentials (typically machine key). This means there is deliberately no link between a session and an authentication ticket.
The server does forget about everything related to that session and so make that session id invalid when you use Session.Clear().
As msdn documentation says:
Removes all keys and values from the session-state collection
So even if someone uses the same session id there will be no information attached to it in the server.
[Edit]
As Erik Funkenbusch points, my solution only works if you store authentication information in the session (obviously). This won't work if you have specific cookies for your authentication system.
The not programmatic way to make sure all session/cookie is wiped on the server side, is to add (if not present) or change (if present) machine key in application's web.config
It can be done via IIS Manager by selecting an application, and in ASP.NET section select "Machine Key", uncheck all checkboxes and hit Generate Keys then hit Apply
Please note this is not for a single user, it will kick out all users.
If You point on browser back button, I use bellow code (vb, but You can convert it) :
web.config:
<sessionState timeout="20" regenerateExpiredSessionId="true"/>
avsessexp.js (auto refresh page if is inactive after 15mins) :
var BeatTimer;
function StartCheck() {
if (BeatTimer == null) {
BeatTimer = setInterval("CheckBeat()", 900000);
}
}
function CheckBeat() {
PageMethods.PokePage();
}
and call StartCheck function in every page (<body onload="StarCheck();">)
in header of every page I put :
<meta http-equiv="pragma" content="no-cache" />
<meta http-equiv="cache-control" content="no-cache, no-store, max-age=0, must-revalidate" />
<meta http-equiv="expires" content="0" />
Module1.vb
Public Sub CheckSession()
With HttpContext.Current
.Response.Cache.SetExpires(DateTime.UtcNow.AddSeconds(-1))
.Response.Cache.SetCacheability(HttpCacheability.NoCache)
.Response.Cache.SetNoStore()
If .Session.IsNewSession = True Then LogOut()
'there You can put some code checking is user logged, ...
End With
End Sub
Public Sub LogOut()
With HttpContext.Current
.Session.RemoveAll()
.Session.Clear()
.Session.Abandon()
Try
.Response.Cookies.Add(New System.Web.HttpCookie("ASP.NET_SessionId", ""))
Catch ex As Exception
End Try
.Response.Redirect("Default.aspx")
End With
End Sub
and, in every page (code-behind) :
<System.Web.Services.WebMethod(EnableSession:=True)> Public Shared Sub PokePage()
CheckSession()
End Sub
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then CheckSession()
End Sub
If You try click browser back button after logout, CheckUser will prevent back to prev page, and always redirect user to login page.
It's very important to use on every page/master page <asp:ScriptManager runat="server" id="sm1" EnablePageMethods="True"><asp:ScriptManager>
p.s. sorry for my weak English.
Do cookieless="true" for Session State
i have this cookies
HttpCookie cookie2 = new HttpCookie("AuthorID", data.AuthorID.ToString());
cookie.Expires = DateTime.Now.AddMinutes(1);
HttpContext.Response.AppendCookie(cookie2);
is there a possible way to set a time of expiration when cookies is inactive of the user of the site is not doing anything? for example reloading the page etc.
what i did is set a time for 1 min and its not working does anybody know?.
i'm having trouble i have search in the internet but i didn't find any useful advice
I implemented something similar in my project.
Process:
For each request send a cookie.
On client side write a JavaScript code for reading the cookie when the page is loaded.
In JavaScript check the expiration date of the cookie.
If the cookie as expired (or not present) do your actions for inactive users (e.g. reload the page, display a message, disconnect him...).
You may also slightly change the above scenario like:
Set expiration date/time as cookie value.
Idem
Make sure cookie value < current date/time
Idem
If you care about users rejecting cookies you can do it in JavaScript without cookies. For example:
Set JavaScript variable 'lastActionTime'.
Create methods checking that 'lastActionTime' > time + delay
Attach common JavaScript events (clicks, focus...) and once called set 'lastActionTime' to the current time.
We have a ASP.NET 4.5 WebForms application using the native forms authentication and session functionality. Both have a timeout of 20 minutes with sliding expiration.
Imagine the following scenario. A user has worked in our application for a while and then proceeds to do some other things, leaving our application idle for 20 minutes. The user then returns to our application to write a report. However, when the user tries to save, he/she is treated with the login screen, and the report is lost.
Obviously, this is unwanted. Instead of this scenario, we want the browser to be redirected to the login page the moment either authentication or session has expired. To realize this, we have build a Web Api service that can be called to check whether this is the case.
public class SessionIsActiveController : ApiController
{
/// <summary>
/// Gets a value defining whether the session that belongs with the current HTTP request is still active or not.
/// </summary>
/// <returns>True if the session, that belongs with the current HTTP request, is still active; false, otherwise./returns>
public bool GetSessionIsActive()
{
CookieHeaderValue cookies = Request.Headers.GetCookies().FirstOrDefault();
if (cookies != null && cookies["authTicket"] != null && !string.IsNullOrEmpty(cookies["authTicket"].Value) && cookies["sessionId"] != null && !string.IsNullOrEmpty(cookies["sessionId"].Value))
{
var authenticationTicket = FormsAuthentication.Decrypt(cookies["authTicket"].Value);
if (authenticationTicket.Expired) return false;
using (var asdc = new ASPStateDataContext()) // LINQ2SQL connection to the database where our session objects are stored
{
var expirationDate = SessionManager.FetchSessionExpirationDate(cookies["sessionId"].Value + ApplicationIdInHex, asdc);
if (expirationDate == null || DateTime.Now.ToUniversalTime() > expirationDate.Value) return false;
}
return true;
}
return false;
}
}
This Web Api service is called every 10 seconds by the client to check if either authentication or session has expired. If so, the script redirects the browser to the login page. This works like a charm.
However, calling this service triggers the sliding expiration of both authentication and session. Thus, essentially, creating never ending authentication and session. I have set a breakpoint at the start of the service to check if it is one of our own functions that triggers this. But this is not the case, it seems to occur somewhere deeper in ASP.NET, before the execution of the service.
Is there a way to disable the triggering of ASP.NET's authentication and session sliding expirations for a specific request?
If not, what is best practice to tackle a scenario like this?
This seems to be impossible. Once sliding expiration is enabled, it is always triggered. If there is a way to access the session without extending it, we have not been able to find it.
So how to tackle this scenario? We came up with the following alternative solution to the one originally proposed in the question. This one is actually more efficient because it doesn't use a web service to phone home every x seconds.
So we want to have a way to know when either ASP.NET's forms authentication or session has expired, so we can pro-actively logout the user. A simple javascript timer on every page (as proposed by Khalid Abuhakmeh) would not suffice because the user could be working with the application in multiple browser windows/tabs at the same time.
The first decision we made to make this problem simpler is to make the expiration time of the session a few minutes longer than the expiration time of the forms authentication. This way, the session will never expire before the forms authentication. If there is a lingering old session the next time the user tries to log in, we abandon it to force a fresh new one.
All right, so now we only have to take the forms authentication expiration into account.
Next, we decided to disable the forms authentication's automatic sliding expiration (as set in the web.config) and create our own version of it.
public static void RenewAuthenticationTicket(HttpContext currentContext)
{
var authenticationTicketCookie = currentContext.Request.Cookies["AuthTicketNameHere"];
var oldAuthTicket = FormsAuthentication.Decrypt(authenticationTicketCookie.Value);
var newAuthTicket = oldAuthTicket;
newAuthTicket = FormsAuthentication.RenewTicketIfOld(oldAuthTicket); //This triggers the regular sliding expiration functionality.
if (newAuthTicket != oldAuthTicket)
{
//Add the renewed authentication ticket cookie to the response.
authenticationTicketCookie.Value = FormsAuthentication.Encrypt(newAuthTicket);
authenticationTicketCookie.Domain = FormsAuthentication.CookieDomain;
authenticationTicketCookie.Path = FormsAuthentication.FormsCookiePath;
authenticationTicketCookie.HttpOnly = true;
authenticationTicketCookie.Secure = FormsAuthentication.RequireSSL;
currentContext.Response.Cookies.Add(authenticationTicketCookie);
//Here we have the opportunity to do some extra stuff.
SetAuthenticationExpirationTicket(currentContext);
}
}
We call this method from the OnPreRenderComplete event in our application's BasePage class, from which every other page inherits. It does exactly the same thing as the normal sliding expiration functionality, but we get the opportunity to do some extra stuff; like call our SetAuthenticationExpirationTicket method.
public static void SetAuthenticationExpirationTicket(HttpContext currentContext)
{
//Take the current time, in UTC, and add the forms authentication timeout (plus one second for some elbow room ;-)
var expirationDateTimeInUtc = DateTime.UtcNow.AddMinutes(FormsAuthentication.Timeout.TotalMinutes).AddSeconds(1);
var authenticationExpirationTicketCookie = new HttpCookie("AuthenticationExpirationTicket");
//The value of the cookie will be the expiration date formatted as milliseconds since 01.01.1970.
authenticationExpirationTicketCookie.Value = expirationDateTimeInUtc.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds.ToString("F0");
authenticationExpirationTicketCookie.HttpOnly = false; //This is important, otherwise we cannot retrieve this cookie in javascript.
authenticationExpirationTicketCookie.Secure = FormsAuthentication.RequireSSL;
currentContext.Response.Cookies.Add(authenticationExpirationTicketCookie);
}
Now we have an extra cookie at our disposal that always represents the correct forms authentication expiration time, even if the user works in different browser windows/tabs. After all, cookies have a browser wide scope. Now the only thing left is a javascript function to verify the cookie's value.
function CheckAuthenticationExpiration() {
var c = $.cookie("AuthenticationExpirationTicket");
if (c != null && c != "" && !isNaN(c)) {
var now = new Date();
var ms = parseInt(c, 10);
var expiration = new Date().setTime(ms);
if (now > expiration) location.reload(true);
}
}
(Note that we use jQuery Cookie Plugin to retrieve the cookie.)
Put this function in an interval, and users will be logged out the moment his or her forms authentication has expired. VoilĂ :-) An extra perk of this implementation is that you now have control over when the forms authentication's expiration gets extended. If you want a bunch of web services that don't extend the expiration, just don't call the RenewAuthenticationTicket method for them.
Please drop a comment if you have anything to add!
Your website functionality should work without JavaScript or you just replace one problem with another. I have tackled this problem also and here is how it was solved:
When you authenticate yourself then session cookie is created with default lifetime on 20 min. When this expires user will be logged out.
When user selects "remember me" in the sign in form then additional persistence cookie [AuthCookie] is created in client side and in the database. This cookie has a lifetime of 1 month. Whenever page is loaded, session and persistence cookie data is recreated with a new lifetime (normally you want to decrypt/crypt the ticket).
Imagine the following scenario. A user has worked in our application
for a while and then proceeds to do some other things, leaving our
application idle for 20 minutes. The user then returns to our
application to write a report. When the user tries to save, his session is restored before the request.
One way to do this is to extend global.aspx to handle prerequest. Something in the lines of:
void application_PreRequestHandlerExecute(object sender, EventArgs e){
...
if (HttpContext.Current.Handler is IRequiresSessionState) {
if (!context.User.Identity.IsAuthenticated)
AuthService.DefaultProvider.AuthenticateUserFromExternalSource();
AuthenticateUserFromExternalSource should check if cookie data matches with the database one, because anything stored in client side can be changed. If you have paid services with access rights then you need to check if user still has those rights and then you can recreate the session.
This can all be solved client side, without the need to go back to the server.
In JavaScript do this.
var timeout = setTimeout(function () {
window.location = "/login";
}, twentyMinutesInMilliseconds + 1);
The timeout will be set to 20 minutes on every page refresh. This ensures that the user needs to get all their work done before the timeout happens. A lot of sites use this method, and it saves you from doing unnecessary server requests.
I am having trouble with the .Expires cookie attribute. It keeps coming back with 01/01/0001 12:00 AM, when I read the cookie back.
Here is the code. I added in the retrieve just below the save solely for debugging purposes. The save and retrieve happen in different places in the same file. I purposely did not specify a Domain, as I want the cookie to exist site wide.
The data shows up nicely, just not the expiration.
Note: I am testing under Visual Studio 2012 running under local host using .Net Framework 4.
System.Web.UI.Page oPage = this.Page;
HttpCookie oCookie = new HttpCookie("UserData");
// Set the cookie value.
oCookie.Secure = false;
oCookie["Field1"] = strField1;
oCookie["Field2"] = strField2;
oCookie.Expires = DateTime.Now.AddDays(1);
// Add the cookie.
oPage.Response.Cookies.Add(oCookie);
// Get the cookie.
oCookie = new HttpCookie("UserData");
oCookie = oPage.Request.Cookies["UserData"];
The browser will not send anything to the server except the cookie name and value. All of the other properties (expires, domain, path, httponly, ...) cannot be retrieved on requests after the cookie has been set.
The more accepted way to deal with this is to redirect the user to a login page when they try to access a protected resource and display some message along the lines of "You need to log in to view this page. If you were previously logged in, your session may have expired."
(Also note that you should be re-setting the cookie on every request, so that the user will not be logged out if they continue to use the site. It's not clear from your code whether you are doing this or not.)
I was just doing some more Google searching on my problem and saw this link, another posting here on Stackoverflow.
Cookies are always expired
I am also validating using the construct:
if (cookie != null && cookie.Expires > DateTime.Now)...
As several pointed out, expiration checking happens, if you can no longer retrieve the cookie. That is seriously dumb on whomever constructed this architecture. Yes, maybe there should be RequestCookie and ResponseCookie, the difference being ResponseCookie has no Expiry date.
The person who resopnded to me taught me that it is not just expires but other fields too.
In C# code, if using Form Authentication, You can find if cookie is persistent using below code
bool IsCookiePersistent = ((FormsIdentity)User.Identity).Ticket.IsPersistent;
Here Ticket will return the FormsAuthenticationTicket which has Expiration DateTime property.
I need to revoke an authentication cookie if the user no longer exists (or some other condition), after the forms authentication mechanism already have received the authentication cookie from the browser and have validated it. I.e. here is the use scenario:
The user have been authenticated, and granted non-expiring auth cookie.
In a few days, the user tries to access my web app again, and as the cookie is valid, the forms authentication mechanism will grant access.
Now I want to perform a second check (whatever condition I want), and decide if I want to let the user continue, or to revoke the authentication.
The question is - is there an official automated way for this? So far I have come with some possibilities, but I do not know which one is better. I can capture the Authenticate event in global.asax, check whatever I want, and to revoke I clear the cookie, and then one of these:
Redirect again to same url - this should work, as this time the forms authentication will fail, and it will redirect to logon page.
Throw some exception ??? which one to make the redirect happen w/o me specifying anything?
Somehow to get the logon page url from the config file (any ideas how/which config handler to use) and redirect directly?
Some FormsAuthentication class/method I have overlooked, which is designed for this?
Any other idea?
I don't think there is an automated way to achive this.
I think the best way would be to add a date to the auth cookie which will be the last time you checked whether the user exists.
So when a user logs-in you'll:
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // Ticket version
name, // Username associated with ticket
DateTime.Now, // Date/time issued
DateTime.Now.AddMonths(1), // Date/time to expire
true, // "true" for a persistent user cookie
DateTime.Now.ToUniversalTime(), // last time the users was checked
FormsAuthentication.FormsCookiePath);// Path cookie valid for
// Encrypt the cookie using the machine key for secure transport
string hash = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(
FormsAuthentication.FormsCookieName, // Name of auth cookie
hash); // Hashed ticket
cookie.HttpOnly = true;
// Set the cookie's expiration time to the tickets expiration time
if (ticket.IsPersistent) cookie.Expires = ticket.Expiration;
//cookie.Secure = FormsAuthentication.RequireSSL;
Response.Cookies.Add(cookie);
Then everytime a user is authenicated you can check the additional date you passed to the Authentication ticket and in 10 minute intervals or less double check against the database whether the user exists.
The code might look something like this:
public void FormsAuthentication_OnAuthenticate(object sender,
FormsAuthenticationEventArgs args)
{
if (FormsAuthentication.CookiesSupported)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(
Request.Cookies[FormsAuthentication.FormsCookieName].Value);
DateTime lastCheckedTime = DateTime.TryParse(ticket.UserData);
TimeSpan elapsed = DateTime.Now - lastCheckedTime;
if (elapsed.TotalMinutes > 10)//Get 10 from the config
{
//Check if user exists in the database.
if (CheckIfUserIsValid())
{
//Reset the last checked time
// and set the authentication cookie again
}
else
{
FormsAuthentication.SignOut();
FormsAuthentication.RedirectToLoginPage();
return;
}
}
}
catch (Exception e)
{
// Decrypt method failed.
}
}
}
}
You can even cache the users that have been deleted the last 10 minutes and check against that collection.
Hope that helps.
If you are rejecting the cookie for some other reason than an expired session, then I think you should redirect the user to a page that describes what they need to do in order to gain access. If logging on again is sufficient, then the logon page would suffice. It sounds, however, like there are conditions under which simply logging on again is not possible. In those cases, it would be better to redirect the user to a suitable error page that describes why they are unable to access the site and explains how to gain access again (if possible).