HttpCookie to Cookie - how to deal with missing Domain? - c#

I am creating a proxy page in ASP .NET (I need to get around cross-site scripting limitations). I need the Page_Load handler to send out an HttpWebRequest based on the incoming request to this page.
var request = (HttpWebRequest)WebRequest.Create(urlToProxy);
request.Method = "GET";
request.GetResponse();
I'm running into some authentication problems (yes, I do set request.Credentials correctly, it's just not shown here), however, so I'd like to forward cookies from the incoming request (that hits my proxy page) to this outgoing request. You would think this would be straightforward:
request.CookieContiner = Request.Cookies;
However, this doesn't work. The outgoing HttpWebRequest uses HttpCookies and the incoming HttpRequest uses Cookies. These classes have no inheritance or interface relationship (indeed one is in System.Net, the other in System.Web.)
I've tried to copy the HttpCookies into Cookies and then add them to the CookieContainer, but I can't get this to work either.
public static IEnumerable<Cookie> ToCookies(this HttpCookie cookie)
{
var cookies = new List<Cookie>(cookie.Values.Count);
foreach (String key in cookie.Values)
{
cookies.Add(new Cookie()
{
Domain = cookie.Domain,
Expires = cookie.Expires,
HttpOnly = cookie.HttpOnly,
Name = cookie.Name,
Path = cookie.Path,
Secure = cookie.Secure,
Value = cookie.Values[key]
});
}
return cookies;
}
...
var cookieContainer = new CookieContainer();
foreach(var httpCookie in Request.Cookies)
{
foreach(var cookie in httpCookie.ToCookies())
{
cookieContainer.Add(cookie); //Exception thrown here
}
}
The exception being thrown says that a Cookie cannot be added that has a null "Domain". Apparently, some of the HttpCookies have null "Domain" properties.
Does anyone have any suggestions on the best way around this problem turning HttpCookie into Cookie? I'd like whatever solution I use with to be as general purpose as possible (and thus not involve hardcoding known domains into the new Cookies.)
Even better, is there an easier way to do what I want simply using the HttpRequest class rather than the HttpWebRequest class?

Related

Why can I not iterate through the cookies when I perform a request to an url?

I am trying to perform a get request to https://www.footlocker.dk/. I need to obtain the session that is created, when visiting this url.
I perform the request as following:
string url = "https://www.footlocker.dk/";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Proxy = null;
request.CookieContainer = new CookieContainer();
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
foreach (Cookie cookie in response.Cookies)
{
Console.WriteLine("name=" + cookie.Name);
Console.WriteLine("value=" + cookie.Value);
Console.WriteLine("path=" + cookie.Path);
Console.WriteLine("domain=" + cookie.Discard);
}
}
However it doesnt return me any cookies..
I need these cookies, when I want to go ahead and make a post request afterward, to perform a specific action on this url.
Regards!
Check the response payloads:
You'll see that the call: https://www.footlocker.dk/api/session?timestamp=1611587725706 has a response header like this:
set-cookie: JSESSIONID=orfy8ma22lox1nd8wxdyf8h4e.fzcxwefapipdb018883; Path=/; HTTPOnly
The https://www.footlocker.dk/ sets no such cookies. Bear in mind that if you go throug the calls, you'll see multiple cookie sets, and you can't be absolutely sure which one you need to make things work, though I believe that you can get the cookie you need just by making the session call.
The final cookie definition looks like this:

C# httpwebrequest cookies

I'm using the below code to reuse the sessions.
Will I get the same session id on the grabUrl response cookies?
How do I know if both requests are using the same session/cookie?
Thanks.
CookieContainer cookCon = new CookieContainer();
Httpwebrequest loginReq = (httpwebrequest) loginReq.create(loginurl);
loginReq.cookiecontainer = cookCon;
... All the response stuffs
Httpwebrequest grabReq = (httpwebrequest) grabReq.create(grabUrl);
grabUrl.cookiecontainer = cookCon
When I add in below code to see the container contents, it shows that there is a session id:
foreach (Cookie cook in cookieContainer.GetCookies(new Uri(loginurl)))
{
Console.WriteLine("Cookie_Get_Container:");
Console.WriteLine("====================================================");
Console.WriteLine("String: {0}", cook.ToString());
Console.WriteLine("====================================================");
}
So I added cookies to the login response:
cookieContainer.Add(new Uri(domainUrl), loginRes.Cookies);
I am not able to get the session id when I try to get from the cookieContainer.grabRes.Cookies.
Any advise?
Since you are not showing code that grabs session cookie from first response I assume that you are not and the answer is "no, new session is created for second requset".
You want to get session ID (and other) from first response and set them on new requests (i.e. by adding them to the container).
Side note: depending on authenitcation method the information to authenticate the next requests (different from session ID) may not come in cookies.

Problems authenticating to website from code

I am trying to write code that will authenticate to the website wallbase.cc. I've looked at what it does using Firfebug/Chrome Developer tools and it seems fairly easy:
Post "usrname=$USER&pass=$PASS&nopass_email=Type+in+your+e-mail+and+press+enter&nopass=0" to the webpage "http://wallbase.cc/user/login", store the returned cookies and use them on all future requests.
Here is my code:
private CookieContainer _cookies = new CookieContainer();
//......
HttpPost("http://wallbase.cc/user/login", string.Format("usrname={0}&pass={1}&nopass_email=Type+in+your+e-mail+and+press+enter&nopass=0", Username, assword));
//......
private string HttpPost(string url, string parameters)
{
try
{
System.Net.WebRequest req = System.Net.WebRequest.Create(url);
//Add these, as we're doing a POST
req.ContentType = "application/x-www-form-urlencoded";
req.Method = "POST";
((HttpWebRequest)req).Referer = "http://wallbase.cc/home/";
((HttpWebRequest)req).CookieContainer = _cookies;
//We need to count how many bytes we're sending. Post'ed Faked Forms should be name=value&
byte[] bytes = System.Text.Encoding.ASCII.GetBytes(parameters);
req.ContentLength = bytes.Length;
System.IO.Stream os = req.GetRequestStream();
os.Write(bytes, 0, bytes.Length); //Push it out there
os.Close();
//get response
using (System.Net.WebResponse resp = req.GetResponse())
{
if (resp == null) return null;
using (Stream st = resp.GetResponseStream())
{
System.IO.StreamReader sr = new System.IO.StreamReader(st);
return sr.ReadToEnd().Trim();
}
}
}
catch (Exception)
{
return null;
}
}
After calling HttpPost with my login parameters I would expect all future calls using this same method to be authenticated (assuming a valid username/password). I do get a session cookie in my cookie collection but for some reason I'm not authenticated. I get a session cookie in my cookie collection regardless of which page I visit so I tried loading the home page first to get the initial session cookie and then logging in but there was no change.
To my knowledge this Python version works: https://github.com/sevensins/Wallbase-Downloader/blob/master/wallbase.sh (line 336)
Any ideas on how to get authentication working?
Update #1
When using a correct user/password pair the response automatically redirects to the referrer but when an incorrect user/pass pair is received it does not redirect and returns a bad user/pass pair. Based on this it seems as though authentication is happening, but maybe not all the key pieces of information are being saved??
Update #2
I am using .NET 3.5. When I tried the above code in .NET 4, with the added line of System.Net.ServicePointManager.Expect100Continue = false (which was in my code, just not shown here) it works, no changes necessary. The problem seems to stem directly from some pre-.Net 4 issue.
This is based on code from one of my projects, as well as code found from various answers here on stackoverflow.
First we need to set up a Cookie aware WebClient that is going to use HTML 1.0.
public class CookieAwareWebClient : WebClient
{
private CookieContainer cookie = new CookieContainer();
protected override WebRequest GetWebRequest(Uri address)
{
HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(address);
request.ProtocolVersion = HttpVersion.Version10;
if (request is HttpWebRequest)
{
(request as HttpWebRequest).CookieContainer = cookie;
}
return request;
}
}
Next we set up the code that handles the Authentication and then finally loads the response.
var client = new CookieAwareWebClient();
client.UseDefaultCredentials = true;
client.BaseAddress = #"http://wallbase.cc";
var loginData = new NameValueCollection();
loginData.Add("usrname", "test");
loginData.Add("pass", "123");
loginData.Add("nopass_email", "Type in your e-mail and press enter");
loginData.Add("nopass", "0");
var result = client.UploadValues(#"http://wallbase.cc/user/login", "POST", loginData);
string response = System.Text.Encoding.UTF8.GetString(result);
We can try this out using the HTML Visualizer inbuilt into Visual Studio while staying in debug mode and use that to confirm that we were able to authenticate and load the Home page while staying authenticated.
The key here is to set up a CookieContainer and use HTTP 1.0, instead of 1.1. I am not entirely sure why forcing it to use 1.0 allows you to authenticate and load the page successfully, but part of the solution is based on this answer.
https://stackoverflow.com/a/10916014/408182
I used Fiddler to make sure that the response sent by the C# Client was the same as with my web browser Chrome. It also allows me to confirm if the C# client is being redirect correctly. In this case we can see that with HTML 1.0 we are getting the HTTP/1.0 302 Found and then redirects us to the home page as intended. If we switch back to HTML 1.1 we will get an HTTP/1.1 417 Expectation Failed message instead.
There is some information on this error message available in this stackoverflow thread.
HTTP POST Returns Error: 417 "Expectation Failed."
Edit: Hack/Fix for .NET 3.5
I have spent a lot of time trying to figure out the difference between 3.5 and 4.0, but I seriously have no clue. It looks like 3.5 is creating a new cookie after the authentication and the only way I found around this was to authenticate the user twice.
I also had to make some changes on the WebClient based on information from this post.
http://dot-net-expertise.blogspot.fr/2009/10/cookiecontainer-domain-handling-bug-fix.html
public class CookieAwareWebClient : WebClient
{
public CookieContainer cookies = new CookieContainer();
protected override WebRequest GetWebRequest(Uri address)
{
var request = base.GetWebRequest(address);
var httpRequest = request as HttpWebRequest;
if (httpRequest != null)
{
httpRequest.ProtocolVersion = HttpVersion.Version10;
httpRequest.CookieContainer = cookies;
var table = (Hashtable)cookies.GetType().InvokeMember("m_domainTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.Instance, null, cookies, new object[] { });
var keys = new ArrayList(table.Keys);
foreach (var key in keys)
{
var newKey = (key as string).Substring(1);
table[newKey] = table[key];
}
}
return request;
}
}
var client = new CookieAwareWebClient();
var loginData = new NameValueCollection();
loginData.Add("usrname", "test");
loginData.Add("pass", "123");
loginData.Add("nopass_email", "Type in your e-mail and press enter");
loginData.Add("nopass", "0");
// Hack: Authenticate the user twice!
client.UploadValues(#"http://wallbase.cc/user/login", "POST", loginData);
var result = client.UploadValues(#"http://wallbase.cc/user/login", "POST", loginData);
string response = System.Text.Encoding.UTF8.GetString(result);
You may need to add the following:
//get response
using (System.Net.WebResponse resp = req.GetResponse())
{
foreach (Cookie c in resp.Cookies)
_cookies.Add(c);
// Do other stuff with response....
}
Another thing that you might have to do is, if the server responds with a 302 (redirect) the .Net web request will automatically follow it and in the process you might lose the cookie you're after. You can turn off this behavior with the following code:
req.AllowAutoRedirect = false;
The Python you reference uses a different referrer (http://wallbase.cc/start/). It is also followed by another post to (http://wallbase.cc/user/adult_confirm/1). Try the other referrer and followup with this POST.
I think you are authenticating correctly, but that the site needs more info/assertions from you before proceeding.

FormsAuthenticate through WCF (How do I make the Session apply to browser?!)

Users are authenticating to a REST WCF Service (my own). The credentials are sent through AJAX with Javascript and JSON format. The service reply with a OK and little info (redirect url) to the client, when authenticated.
Now, There are a new method provided for external authentication, and I have to create a compact code snippet that are easy to paste & run inside a asp.net code file method.
A typical wcf request could end up like this,
http://testuri.org/WebService/AuthenticationService.svc/ExtLogin?cId=197&aId=someName&password=!!pwd
My code snippet so far,
protected void bn_Click(object sender, EventArgs e)
{
WebHttpBinding webHttpBinding = new WebHttpBinding();
EndpointAddress endpointAddress = new EndpointAddress(url);
ContractDescription cd =
ContractDescription.GetContract(typeof(IAuthenticationService));
ServiceEndpoint sep = new ServiceEndpoint(cd);
sep.Behaviors.Add(new WebHttpBehavior());
sep.Address = endpointAddress;
sep.Binding = webHttpBinding;
var resp = new ChannelFactory<IAuthenticationService>(sepREST).CreateChannel();
LoginResult result = resp.ExtLogin(cId, aId, hashPwd);
Response.Redirect(result.RedirectUri);
// I.e. http://testuri.org/Profile.aspx (Require authenticated to visit)
}
I recieve correct authenticated reply in the resp/result objects. So, the communication are fine. When redirecting to the actual website, I'm not authenticated. I can't locate the problem? If I take the URI above (with valid credentials) and paste into my Webbrowser URL, and then manually type the uri, i'm authenticated.
I've spent a day searched the net for this, without success.
There are a LOT of info but none seem to apply.
What am I missing?
I also tried another approach but the same problem persist.
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uriWithParameters);
CookieContainer cookieContainer = new CookieContainer();
request.CookieContainer = cookieContainer;
request.ContentType = "application/json";
request.Accept = "application/json";
request.Method = "GET";
string result;
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
using (Stream stream = response.GetResponseStream())
using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
result = reader.ReadToEnd();
JavaScriptSerializer jsonDeserializer = new JavaScriptSerializer();
LoginResult contact = jsonDeserializer.Deserialize<LoginResult>(result);
Response.Redirect(result.RedirectUri);
I'm not sure about this answer, but will offer it anyway as nobody else has posted:
I think it's because the request that has been authenticated is the request sent via code.
When you redirect it's a totally different request - so is still not authenticated.
All authentication techniques require some way of maintaining the authenticated state across 'stateless' requests = session cookies or some kind of authentication token.
Whatever token you get back from the call to the authentication service needs to be available to your website requests as well - dumping the token from the request into a cookie might be an option.
Can you see (in something like Fiddler) an auth token being sent as part of the request to 'RedirectUrl'?

In .NET, how do you provide a session identifier to a web service that requires authentication?

I am using a web service that requires authentication from .NET (Visual Studio 2010). According to the documentation, you first request a session identifier from the first web service. I can do that with no problem. Then you are supposed to call the second web service for actually performing your query, passing the session identifier in a cookie. Here is my sample code:
AuthenticateService authenticate_service = new AuthenticateService();
string session_identifier = authenticate_service.Authenticate();
SearchService search_service = new SearchService();
search_service.CookieContainer = new CookieContainer();
Cookie cookie = new Cookie("Cookie", "SID=" + session_identifier, null, search_service.Url);
search_service.CookieContainer.Add(cookie);
search_service.Test();
However, I am getting the following exception on the last line:
System.Web.Services.Protocols.SoapException was unhandled
Message=Session ID cookie value cannot be null or empty string - It is required that the high level Web service client program participate in the session initialized by the server.
Does anybody know how to properly send a cookie with a session ID to a web service?
Just figured it out...
It all had to do with the domain parameter of the Cookie constructor. I was passing search_service.Url because I wasn't sure what it was supposed to be. Apparently it should have been something like "search.google.com". When I passed that to the constructor instead, everything started working as expected.
I found this somewhere along the line. It is a cookie-aware web-client that I have been using. This allows me to have the ease of use of WebClient and pass cookies.
public class CookieAwareWebClient : WebClient
{
private CookieContainer m_container = new CookieContainer();
protected override WebRequest GetWebRequest(Uri address)
{
WebRequest request = base.GetWebRequest(address);
if (request is HttpWebRequest)
{
(request as HttpWebRequest).CookieContainer = m_container;
}
return request;
}
}
Then you can just use the WebClient methods and the cookie is passed automatically after you authenticate.
Example code:
CookieAwareWebClient webClient = new CookieAwareWebClient();
NameValueCollection data = new System.Collections.Specialized.NameValueCollection();
data["user"] = "myusername"; //now holds a user=username map
byte[] response = webClient.UploadValues("http://localhost:8080/somewebservice/auth/","POST", data); //point to the webservice authentication URI
Now you can use webclient.UploadFile or UploadValues or whatever you want assuming that the previous authentication was OK.

Categories

Resources