I am running in a stupid problem. I have an method which returns initialized request resposible for loggin in on some external web site.
protected HttpWebRequest GetLoginRequest()
{
const string url = "https://someurl.com/login";
var queryParams = new ArrayList
{
String.Format("{0}={1}", "email", Email),
String.Format("{0}={1}", "password", DecryptedPassword)
};
var parameters = String.Join("&", (String[])queryParams.ToArray(typeof(String)));
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = parameters.Length;
request.Timeout = 10000;
var streamWriter = new StreamWriter(request.GetRequestStream());
streamWriter.Write(parameters);
streamWriter.Close();
return request;
}
I'm calling this method from two places in my code. First call looks like that:
var request = GetLoginRequest();
var response = (HttpWebResponse)request.GetResponse();
And the second one has CookieContainer assigned to request:
var cookieContainer = new CookieContainer();
var request = GetLoginRequest();
request.CookieContainer = cookieContainer;
var response = (HttpWebResponse)request.GetResponse();
because I need to store CookieContainer.
The thing is that the logon is performed only in second case. In the first case i'm getting response from the login page. I've checked all the cases and both resulting requests seem identic. I would suggest that it is target site secific, but still I don't see any reason for that.
Can you please explain what is the reason, because this behavior seems pretty unobvious to me.
When you set the CookieContainer property on your request, the response is populating that CookieContainer instance with the cookies received from the executed request.
Most login mechanisms use a cookie to store the state related to the established login. I.e. in the case of Forms authentication the cookie is the container for the forms authentication ticket. The ticket is passed as the value of the forms authentication cookie with each request and is used by forms authentication, on the server, to identify an authenticated user.
In short you need a CookieContainer for each request after you login, and it needs to contain the forms authentication cookie that you received when you logged in.
Edit to clarify comment - from MSDN:
CookieContainer is null by default.
You must assign a CookieContainer
object to the property to have cookies
returned in the Cookies property of
the HttpWebResponse returned by the
GetResponse method.
For security reasons, cookies are disabled by default. If you want to
use cookies, use the CookieContainer
property to enable cookies.
Related
I have a CLR function getting data from cookie authorized website. The first request gets a login cookies and the second request gets xml data I need. The problem is in that I am always getting 401 unauthorized on a second request when run it from SQL Server as a function. The testing console app using the same DLL is working fine. Looks like the second request has no cookies but I checked in exception the amount of cookie container of the second request, it is not empty.
String encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(UserName + ":" + Password));
try
{
HttpWebRequest loginrequest = (HttpWebRequest)WebRequest.Create(string.Format("{0}", BaseOrdersAddress));
CookieContainer logincookies = new CookieContainer();
loginrequest.Headers.Add(HttpRequestHeader.Authorization, "Basic " + encoded);
loginrequest.AllowAutoRedirect = false;
loginrequest.CookieContainer = logincookies;
loginrequest.Method = WebRequestMethods.Http.Get;
HttpWebResponse loginresponse = (HttpWebResponse)loginrequest.GetResponse();
loginresponse.Close();
if (loginresponse.StatusCode == HttpStatusCode.Found)
{
location = loginresponse.Headers[HttpResponseHeader.Location];
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(location);
request.CookieContainer = logincookies;
request.Method = WebRequestMethods.Http.Get;
HttpWebResponse response1 = request.GetResponse() as HttpWebResponse;
var xresult = new XmlSerializer(typeof(Local_Response));
r = (Local_Response)xresult.Deserialize(response1.GetResponseStream());
}
Solved.
The problem was in using HttpWebRequest.CookieContainer, don't know why but it does not work while running as a CLR function, no cookies are sent. Have to do it manually adding HttpRequestHeader.Cookie to request headers collection.
Don't forget, your SQLCLR code executes in the context of SQL Server. I see you have a username, password in the code - what does that do and where is the username/password retrieved from. My bet is that there is something wrong with this based on what I said earlier.
I have been trying to log in to a server to grab the authentication cookie (a session cookie), which I can then use for further calls to the server. Every example I have read follows the same pattern:
HttpWebRequest request = WebRequest.Create(loginURL) as HttpWebRequest;
var response = request.GetResponse() as HttpWebResponse;
var cookies = response.Cookies;
This didn't work for me, as the cookies variable ended up empty, and a debug analysis showed response.Cookies was empty. The server is mine, and I can see, through debugging, the cookie is being set. I can also see the cookie in Firefox if I log in to my site with it. So I know the cookie is being set.
After some messing around, I discovered the cookie was being set in the request, not the response. So the code below worked. My question is: Why? Why is the request being populated, but not the response? Is it something to do with being a post, not a get? I am totally baffled.
private void Login()
{
string userName = UserNameText.Text;
string password = PasswordText.Password;
string baseURL = URLText.Text;
string loginURL = baseURL + "/Authentication/LoginAction";
HttpWebRequest request = WebRequest.Create(loginURL) as HttpWebRequest;
request.Method = "POST";
string formContent =
"UserName=" + userName +
"&Password=" + password;
var byteArray = Encoding.UTF8.GetBytes(formContent);
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = byteArray.Length;
request.CookieContainer = new CookieContainer();
try
{
using (var dataStream = request.GetRequestStream())
{
dataStream.Write(byteArray, 0, byteArray.Length);
using (var response = request.GetResponse() as HttpWebResponse)
{
var cookies = request.CookieContainer;
if (cookies.Count != 0)
{
cookies_ = cookies;
}
}
}
}
catch(Exception ex)
{
// don't bother too much
Debug.WriteLine(ex.Message);
}
}
The CookieContainer should be considered similar to a browser's cookie cache for a particular site. The idea is that you supply the container as part of the request, and then it's populated by the cookies you receive and you can reuse that container for subsequent requests. When you make a request, the cookies in the container are sent with the request (just like the browser would with stored cookies).
So, for example, if you have a page that uses cookies to store an authentication token, you can pass the cookie container with the login request, and then pass it with subsequent requests which require an authenticated cookie.
As to why you can't simply extract it from the request, I guess Microsoft just didn't want to duplicate things when you can pass in a reference to a mutable cookie container in the request.
I am using HttpWebRequest with HttpWebResponse, the latter named response22 in my code, so here is a snippet from my code:
HttpWebResponse response22 = request22.GetResponse() as HttpWebResponse;
CookieCollection cookiezzz = new CookieCollection();
cookiezzz.Add(response22.Cookies);
foreach (System.Net.Cookie cookie in cookiezzz)
{
MessageBox.Show(cookie.Name);
}
Strangely enough, Fiddler shows 5 cookies in the response, but when I iterate through the cookies, I get only four.
Also, my request is set to:
equest22.AllowAutoRedirect = false;
Target framework is .Net 4.5, using WinForms
And using CookieContaner did not help at all as it "picks" only 2 of these cookies, but I don't want to worry about that right now, just want to figure out how to get all five cookies.
Instead of trying to retrieve them from the response, you have to supply the cookie container to the request. That will force the container and the response cookies to be filled:
var cookiezzz = new CookieContainer();
request22.CookieContainer = cookiezzz;
HttpWebResponse response22 = request22.GetResponse() as HttpWebResponse;
foreach (System.Net.Cookie cookie in cookiezzz)
{
MessageBox.Show(cookie.Name);
}
I am making a email template and this template will be send to the multiple users and in this template there is only a gridview control and I will fill the grid with a session variable, Session variable is having the datatable.
My issue is this when i am using the "WebResponse" for this page then i do not get the session value that is null.(i have cross check that my session Name is same);
code Snippet:
WebRequest request = WebRequest.Create(strFullUrl);
WebResponse response = request.GetResponse();
what i am doing wrong, Please give some guidance.
By default, Sessions does not persist with HttpWebResponse.
Create the helper class called CookieContainer and attach it with your web request.
CookieContainer cookieContainer = new CookieContainer();
WebRequest request = (WebRequest)WebRequest.Create(strFullUrl);
request.CookieContainer = cookieContainer;
WebResponse response = (WebResponse)request.GetResponse();
I'm trying to write a utility which will attempt to login to the Microsoft Online Admin website and report back as to whether it is reachable.
Using code mainly from this article, http://odetocode.com/articles/162.aspx and some screen scraping I have pieced together the following. Unfortunately it doesn't work, the final response shows that I am still looking at the login page rather than the target page.
Any help would be terrific. Thanks in advance.
private void LoginToSite()
{
const string LOGIN_URL = "https://admin.microsoftonline.com/Login.aspx";
const string USERNAME = "<username>";
const string PASSWORD = "<password>";
const string TARGET_PAGE_URL = "https://admin.noam.microsoftonline.com/Home/Home.aspx";
// first, request the login form to get the viewstate value
HttpWebRequest webRequest = WebRequest.Create(LOGIN_URL) as HttpWebRequest;
StreamReader responseReader = new StreamReader(
webRequest.GetResponse().GetResponseStream()
);
string responseData = responseReader.ReadToEnd();
responseReader.Close();
// extract the viewstate value and build out POST data
string viewState = ExtractViewState(responseData);
string postData =
String.Format(
"__VIEWSTATE={0}&AdminCenterLoginControl$UserNameTextBox={1}&AdminCenterLoginControl$PasswordTextbox={2}&__EVENTTARGET=AdminCenterLoginControl_ActionButton",
viewState, USERNAME, PASSWORD
);
// have a cookie container ready to receive the forms auth cookie
CookieContainer cookies = new CookieContainer();
// now post to the login form
webRequest = WebRequest.Create(LOGIN_URL) as HttpWebRequest;
webRequest.Method = "POST";
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.CookieContainer = cookies;
// write the form values into the request message
StreamWriter requestWriter = new StreamWriter(webRequest.GetRequestStream());
requestWriter.Write(postData);
requestWriter.Close();
// we don't need the contents of the response, just the cookie it issues
webRequest.GetResponse().Close();
// now we can send out cookie along with a request for the protected page
webRequest = WebRequest.Create(TARGET_PAGE_URL) as HttpWebRequest;
webRequest.CookieContainer = cookies;
responseReader = new StreamReader(webRequest.GetResponse().GetResponseStream());
// and read the response
responseData = responseReader.ReadToEnd();
responseReader.Close();
MessageBox.Show(responseData);
}
private string ExtractViewState(string s)
{
string viewStateNameDelimiter = "__VIEWSTATE";
string valueDelimiter = "value=\"";
int viewStateNamePosition = s.IndexOf(viewStateNameDelimiter);
int viewStateValuePosition = s.IndexOf(
valueDelimiter, viewStateNamePosition
);
int viewStateStartPosition = viewStateValuePosition +
valueDelimiter.Length;
int viewStateEndPosition = s.IndexOf("\"", viewStateStartPosition);
return HttpUtility.UrlEncodeUnicode(
s.Substring(
viewStateStartPosition,
viewStateEndPosition - viewStateStartPosition
)
);
}
edit
private void LoginToSite()
{
const string LOGIN_URL = "https://admin.microsoftonline.com/login.aspx?ReturnUrl=%2fDefault.aspx";
const string USERNAME = "<username>";
const string PASSWORD = "<password>";
// Request the login form to get the viewstate value
HttpWebRequest webRequest = WebRequest.Create(LOGIN_URL) as HttpWebRequest;
string response1 = new StreamReader(webRequest.GetResponse().GetResponseStream()).ReadToEnd();
// Extract the viewstate value and build our POST data
string viewState = ExtractViewState(response1);
string postData = String.Format(
"__VIEWSTATE={0}&AdminCenterLoginControl$UserNameTextBox={1}&AdminCenterLoginControl$PasswordTextbox={2}&__EVENTTARGET=AdminCenterLoginControl_ActionButton",
viewState, USERNAME, PASSWORD);
// Set up the Request properties
webRequest = WebRequest.Create(LOGIN_URL) as HttpWebRequest;
webRequest.Method = "POST";
webRequest.ContentType = "application/x-www-form-urlencoded";
CookieContainer cookies = new CookieContainer();
webRequest.CookieContainer = cookies;
// Post back to the form
using (StreamWriter requestWriter = new StreamWriter(webRequest.GetRequestStream()))
{
requestWriter.Write(postData);
}
// Read response
string response2 = new StreamReader(webRequest.GetResponse().GetResponseStream()).ReadToEnd();
MessageBox.Show(response2);
}
It would appear that MicrosoftOnline.com does not use Windows Live IDs (aka Passport) for login. This is a shame, since there are libraries available that make logging into LiveID pretty simple for client apps.
Your code hits the login page first, scraps cookies from the response, then attempts to navigate to the target page. This doesn't match the normal flow of user behavior. Normally, the user clicks on a link to go to a target page and the web site redirects the request to the login page if the user is not logged in. After logging in, the login page redirects back to the originally requested target page.
You can see this by looking at the login URL when you visit admin.microsoftonline.com in the browser. You are immediately redirected to the login page, but the full URL on the login page is: https://admin.microsoftonline.com/login.aspx?ReturnUrl=%2fDefault.aspx
Note the ReturnUrl query param at the end. This tells the login page what page to redirect back to when the login is completed.
I don't know if redirect is required by the login page, but since this is the primary path for actual end user interaction (that works) and not the path your code is taking, it's something to consider. Among other things, the redirect to login/ redirect back to target technique will take care of setting the browser cookies for the target domain automatically.
p.s. I notice also that the email administration portion of Microsoft Online services uses a different login URL. From this page (http://www.microsoft.com/online/signin.aspx) clicking on the Exchange Hosted Services Administrative Center link takes you to http:admin.messaging.microsoft.com, which immediately redirects to a login url of https://sts.messaging.microsoft.com/login.aspx?ReturnUrl=%2fDefault.aspx%3fwa%3dwsignin1.0%26wtrealm%3dhttps%253a%252f%252fadmin.messaging.microsoft.com%26wctx%3drm%253d0%2526id%253dpassive%2526ru%253d%25252f%26wct%3d2010-10-27T17%253a11%253a50Z&wa=wsignin1.0&wtrealm=https%3a%2f%2fadmin.messaging.microsoft.com&wctx=rm%3d0%26id%3dpassive%26ru%3d%252f&wct=2010-10-27T17%3a11%3a50Z
The domain name sts.messaging.microsoft.com suggests that the Exchange Hosted Services portion of Microsoft Online Services is using a Security Token Service, which suggests that this login system is capable of federated single sign on between different services. You might be able to connect to this using something like the Windows Identity Foundation (WIF) client components. Will that work with the rest of Microsoft Online Services? I don't know.
> // now we can send out cookie along with a request for the protected page
> webRequest = WebRequest.Create(TARGET_PAGE_URL) as
> HttpWebRequest;
> webRequest.CookieContainer = cookies;
> responseReader = new StreamReader(webRequest.GetResponse().GetResponseStream());
Aren't you setting WebRequest.CookieContainer equal to the blank cookie container that you generated earlier?
Shouldn't you be doing something like:
// we don't need the contents of the response, just the cookie it issues
WebResponse response = webRequest.GetResponse();
cookies = response.cookies;
response.Close();