Why does HttpContext.Response.Cookies["foo"] add a cookie? - asp.net

I have just spent half a day tracking down a bug due to the following behaviour: -
Imagine that there is not a cookie called "foo" in either the Http request's or response's cookie collections. The following code returns null
A) HttpContext.Current.Request.Cookies["foo"]
The following code creates a new cookie called "foo" (with path="/" and blank value), adds it to the response's cookie collection and returns that
B) HttpContext.Current.Response.Cookies["foo"]
So (B) has the side effect that any pre-existing cookie called "foo" is overwritten on the client's browser.
This is not a bug. Someone actually coded this deliberately. Here is the disassembly of the Get method (the Item[string name] indexer delegates to this method.
public HttpCookie Get(String name) {
HttpCookie cookie = (HttpCookie)BaseGet(name);
if (cookie == null && _response != null) {
// response cookies are created on demand
cookie = new HttpCookie(name);
AddCookie(cookie, true);
_response.OnCookieAdd(cookie);
}
return cookie;
}
Obviously the MSDN docs do not mention this behaviour (although some users have added comments at the bottom of the docs that describe this behaviour).
My question is can someone explain to me the rational why the HttpCookieCollection class has this behaviour.

From MSDN doc about HttpCookieCollection.Get method: http://msdn.microsoft.com/en-us/library/ezy11xy2.aspx
If the named cookie does not exist, this method creates a new cookie
with that name.
So yes, this is done on purpose.

Why? At a guess because it makes sense. If you're doing anything with the response the typical use case is that you want things to be sent to the browser.
You check for values inbound (Request.Cookies) and then you set them outbound (Response.Cookies). The Request is a READ, the Response is a WRITE.

Related

MVC Cookie losing values after a HTTP Request & Cookie Add value

I have a multi key-value pair cookie. The cookie is a session cookie, i.e. not persistent. It contains several key-value pairs of data and well within the 4k size limit.
What's working: In my initialise action method (on Controller A), the cookie has a number of key-value pairs successfully added to it. All good so far.
Problem: When I navigate to another page, i.e. different action method (on Controller B), and then add a value to the cookie, I find all the previous values in the Cookie are now gone.
Please note, I verified the problem occurs only when a new value is added here. NOT adding a value to the cookie and navigating across many pages (and controllers) the existing values in the cookie are all preserved.
Investigations: I have spent one and a half days on this and tried a number of thing. In my cookie WriteCookie() method:
Making the Cookie persistent (by setting it's expiry to tomorrow). Verified the cookie in Firefox exists and has the correct date. But once I add a value to the cookie in Controller B, the cookie has lost all it's data.
Create a new cookie with the same name, added the previous values to this new cookie along with the new value. But again, navigating to another page (Controller B) and adding a value to the cookie has lost all it's previous data.
Code: Below is the original code I started with which doesn't contain the many heartbreaking attempts:
public static class CookieHelper
{
public static string ReadCookie(string key)
{
string value = string.Empty;
HttpCookie cookie = HttpContext.Current.Request.Cookies["mycookie"];
if (cookie != null)
{
value = HttpContext.Current.Request.Cookies["mycookie"].Values[key];
}
return value;
}
public static void WriteCookie(string key, string value)
{
HttpContext.Current.Response.Cookies["mycookie"].Values[key] = value;
}
}
Can anyone please help and explain why the cookie values are being lost. My guess, for some unknown reason subsequent writes to the cookie AFTER a HTTP Request is creating a new cookie and overwriting the existing cookie.
From MSDN:
You cannot directly modify a cookie. Instead, changing a cookie consists of creating a new cookie with new values and then sending the cookie to the browser to overwrite the old version on the client.
Modifying an individual subkey is the same as creating it.
To delete an individual subkey, you manipulate the cookie's Values collection, which holds the subkeys. You first recreate the cookie by getting it from the Cookies object. You can then call the Remove method of the Values collection, passing to the Remove method the name of the subkey to delete. You then add the cookie to the Cookies collection so it will be sent in its modified form back to the browser.

TempData implementation changes - Reasons for the change

In ASP.NET MVC 2, the lifespan of an entry in the TempDataDictionary was just one HTTP Request.
That translated to setting a value in one request, redirecting, and having access to the same item at the other end of the line. After this the entry would be no longer available, regardless of whether you read the value out of the dictionary at the latter end of the line or not.
Since ASP.NET MVC 3 (I believe), this implementation detail has changed quite significantly.
Entries in the TempDataDictionary are now only removed once they've been read.
MVC 4
public object this[string key]
{
get
{
object obj;
if (!this.TryGetValue(key, out obj))
return (object) null;
this._initialKeys.Remove(key);
return obj;
}
}
and
public bool TryGetValue(string key, out object value)
{
this._initialKeys.Remove(key);
return this._data.TryGetValue(key, out value);
}
MVC 2:
public object this[string key] {
get {
object value;
if (TryGetValue(key, out value)) {
return value;
}
return null;
}
and
public bool TryGetValue(string key, out object value) {
return _data.TryGetValue(key, out value);
}
Since most people seem to put items in the TempData collection in one request and immediately read them back out in the immediate next request, the functionality seems roughtly the same.
In scenarios where this is not the case such as wanting to read the TempData entry if redirected to one place, and expecting it to have been removed if requesting other resources and navigating back, this change has quite an impact.
No longer is the entry available for one http request but is available over many HTTP requests, be it only available to one single get on the dictionary.
I'd like to know more about this implimentation change, what were the reasons for the change, was this simply to cater for multiple redirects or are there deeper benefits?
Secondary to that, I'm intrigued to know if there's anything built in that now caters for single HTTP request sharing of data in the same way that TempData used to cater for?
You're correct that TempData keys are only cleared if they’ve been read (or after the user’s session expires) but this has been the case since MVC2, (http://forums.asp.net/post/3692286.aspx)
I'd like to know more about this implimentation change, what were the
reasons for the change, was this simply to cater for multiple
redirects or are there deeper benefits?
This change prevented problems that arose in MVC 1, such as TempData keys being deleted before they were read. So yes, the primary benefit is in avoiding these problems when you have multiple re-directs, or interleaved requests. In addition, the RedirectToRouteResult or RedirectResult methods now automatically call TempData.Keep() to prevent clearing of keys, even after they've been read so keep that in mind as well.
In scenarios where this is not the case such as wanting to read the
TempData entry if redirected to one place, and expecting it to have
been removed if requesting other resources and navigating back, this
change has quite an impact.
You’re correct, if you've been coding under the assumption that the TempData keys are cleared automatically you could run into unexpected problems. You can call TempData.Clear() to manually remove all keys from the TempDataDictionary, or TempData.Remove(key) to remove a specific key. You can also use TempData.Peek() to read the value of a TempData key without flagging it for removal from the TempDataDictionary.
Secondary to that, I'm intrigued to know if there's anything built in
that now caters for single HTTP request sharing of data in the same
way that TempData used to cater for?
I'm not aware of any new objects or functions that replicate the original implementation of TempData. Essentially we still use TempData but have to be mindful that the data persists until read and clear the dictionary manually if needed.

ASP.NET session key for web service security?

I've got a web service (ASP.NET 2.0) that I'm calling from javascript using jQuery's $.ajax(). I've been told that the session key is often used as a nonce in a situation like this for security purposes; the web service takes a key as one of its parameters and only returns data if it matches the current session key.
I'm attempting to accomplish this by setting the value of a hidden field to the current SessionID on every Page_Load (i.e. every postback), then grabbing it in the javascript to pass as a parameter. But it's never the same key as the web service's current key (Context.Session.SessionID).
Is this possible to resolve, or should I be doing this another way?
EDIT: code to set session in hidden field as requested.
hfSession.Value = Context.Session.SessionID;
That's in the Page_Load of a .ascx control, not under any conditional (i.e. not wrapped with if (!Page.IsPostBack).
I believe you are trying to prevent Cross Site Script Request Forgery (CSRF). The Session ID is actually sent across as a cookie and the attacker can set this. Rather than use the Session ID itself, you should use a randomly generated number stored in a Session variable.
String GetCSRFToken()
{
String token = (String)Session["CSRFToken"];
if( token == null )
{
token = GenerateLongRandomString();
Session["CSRFToken"] = token;
}
return token;
}
void AssertValidCSRFToken(String token)
{
if( token != GetCSRFToken() )
{
throw new Exception("Invalid Request!")
}
}
Here is a link to another question with more info on preventing this kind of attack:
CSRF Validation Token: session id safe?
Asp.net actually generates a new Session ID for every request until you use the Session State to store some value. This could be a reason why the values are different. Try and save something in the session. Perhaps
Session["SessionID"] = Context.Session.SessionID;
hfSession.Value = Context.Session.SessionID;
A new SessionID is generated each time the page loads until the session is actually allocated. So if you don't actually allocate anything to the session, the SessionID will change upon each page load.

How to protect cookies from an attack

I want to use cookies for storing userId during the session which lets to avoid unnecessary roundtrips to the data base. This userId is used to access some user specific information. As cookies can be easily edited I'm now conserned with the security issue.
In order to forbid an logged in user to edit their userId and so get access to other users' information I use a pretty straightforward method. I add one more cookie at the userId cookie creation moment which stores a hashed value for it. While hashing I use a hard coded 64 byte key. When retrieving the userId from the cookie it is always checked if it matches with its hashed value.
Here is basically my code:
public static int GetUserId(Page page)
{
int userId;
if (page.Request.Cookies["userId"] != null && page.Request.Cookies["userIdHashed"] != null)
{
string userIdHashed = page.Request.Cookies["userIdHashed"].Value;
string userIdCoockie = page.Request.Cookies["userId"].Value;
string coockie = (userIdCoockie + "945AFF2FD0F1D89B4B1DBEB1B0C5D3B8B5DCE000AAEA331EB0C3F3A68C3865EFA73BC6EBF30C8DF1AD6B9ECB7094DA5B0C1AF36B5BBD096E3D873E9589E3F664").GetHashCode().ToString();
if (userIdHashed == coockie)
{
userId = Int32.Parse(userIdCoockie);
}
else
{
throw new Exception("UserId does not match!");
}
}
else
{
userId = ...//here userId is being retrieved from the data base and than:
page.Response.Cookies["userId"].Value = userId.ToString();
page.Response.Cookies["userId"].HttpOnly = true;
string userIdHashed = (userId.ToString() + "945AFF2FD0F1D89B4B1DBEB1B0C5D3B8B5DCE000AAEA331EB0C3F3A68C3865EFA73BC6EBF30C8DF1AD6B9ECB7094DA5B0C1AF36B5BBD096E3D873E9589E3F664").GetHashCode().ToString();
page.Response.Cookies["userIdHashed"].Value = userIdHashed;
page.Response.Cookies["userIdHashed"].HttpOnly = true;
}
return userId;
}
So my questions are:
Can such an approach be considered
reliable enough in this situation?
If not should I modify it and how or
should I look for something different
(e.g. encryption/decryption via
System.Security.Cryptography as
recommended here)?
And additional question: Does it really make sense to set HttpCookie.HttpOnly = true to prevent javascript from accessing the cookie given that it can also easily be modified by the user?
UPDATE
Great thanks for answers to Kerrek SB and Darin Dimitrov who share the opinion that it does not make sense to try to protect cookies on my own taking into account that there are already built in protected mechanisms of storing of such kind of information between postbacks.
Options suggested are:
Using the ASP.NET cache (but I believe it is generally supposed to
store information which should be shared between users, so I look at
other two options).
Adding a custom string with userId into UserData part of the
FormsAuthenticationTicket.
Using the Session State.
So currently I'm deciding between the latter two.
Changing the FormsAuthenticationTicket is not really straightforward. Additionally it does not work with the Cookieless Forms Authentication (as stated here).
Using the Session State is much easier but it can affect the performance because it stores the values in the server memory. However may be in my case it is not so dramatic because we store only userId of type int.
So for now the last option looks much better for me. However I would greatly appreciate if anybody else could comment and support or criticise any of options discussed.
Thanks in advance!
You seem to be reinventing some wheels here. While this could be acceptable when doing some standard code, when security is involved this almost always leads to catastrophic consequences.
To track actively logged in user I would recommend you using forms authentication (and here's another useful tutorial). It uses authentication cookies to track users. Those cookies are securely encrypted by the framework so that they cannot be modified using the <machineKey> section of the server machine.config file.
Form your code all you need to do to access the currently logged in user name is the following:
public static string GetUserId(HttpContextBase context)
{
if (context == null || !context.User.Identity.IsAuthenticated)
{
return null;
}
return context.User.Identity.Name;
}
You really shouldn't be handling all this stuff manually especially when the ASP.NET framework has a built-in mechanism for it.
That's terribly roundabout and obscure. If you already have an active session, why don't you just store this kind of data (which the client never needs to know, mind you) in your server-side session data?
All you should ever need to exchange with the client is the session ID, really.
you can also encrypt the cookies with Secure Socket Layer
HttpCookie cookie = new HttpCookie();
cookie.Secure = true;

ASP.NET: How to redirect, prefilling form data?

i want a handler to redirect to a web-forms page, pre-filling in the values of some controls on the form.
i tried setting my current Request.Form data:
if (theyWantToDoSomething)
{
//pre-fill form values
context.Request.Form["TextBox1"] = "test";
context.Request.Form["ComboBox1"] = "test 2";
context.Request.Form["TextBox2"] = GetTheTextForTheThing();
//tell the client to go there
context.Response.Redirect("~/SomeWebForm.aspx");
return;
}
But i get an exception that Form values are read only.
What would be a way to send the client to another page, pre-filling form data?
Answer
i used the Session state to store values. It's important to note that by default a Handler doesn't have access to Session (the Session object will be null). You have to tell IIS to give you the Session object by adding the IRequiresSessionState marker interface to your handler class:
public class Handler : IHttpHandler, System.Web.SessionState.IRequiresSessionState
{
public void ProcessRequest(HttpContext context)
{
...
if (theyWantToDoSomething)
{
//pre-fill form values
context.Session["thing1"] = "test";
context.Session["thing2"] = "test 2";
context.Session["thing3"] = GetTheTextForTheThing();
//tell the client to go there
context.Response.Redirect("~/SomeWebForm.aspx");
return; //not strictly needed, since Redirect ends processing
}
...
}
}
You can only populate your Response, the Request is input data and is indeed read-only.
If you are using ASP.NET, there are a variety of ways you could accomplish what you need:
The best way would probably be to pass the data you need to be pre-populated to SomeWebForm.aspx via the Session object, and on that pages Load method, populate your form. Keep in mind that when you do Response.Redirect, a 302 response is sent to the client with the URL the client should redirect to. The process is transparent to the user...but there is a full round trip involved.
Another alternative to populating the users Session would be to add GET parameters via a query string to the redirect to SomeWebForm.aspx.
If you need to transfer processing to the SomeWebForm.aspx page without round tripping, you could use Server.Transfer. This will transfer execution from the current page to the page you choose...however, this can cause some odd behavior on the client end because the URL does not update. As far as the user is concerned, it will still appear as though they are on the same page they started on.
A few ideas that might get you started:
Pass the values in the query string
Store the values in the session state or in a seperate cookie
Store the values in HttpContext.Items and use Server.Transfer instead of Response.Redirect
Another approach that hasn't been mentioned yet is using Server.Transfer makes it possible to use the Page.PreviousPage property, allowing access to the controls on the page that transferred control.
As jrista mentioned though, using Transfer doesn't update the URL shown to the user, which may or may not be an issue. For example, a user can't precisely bookmark a page they got transferred to since the URL will be that of the original transferring page.

Resources