Techniques for logging into websites programmatically - c#
I am trying to automate logging into Photobucket for API use for a project that requires automated photo downloading using stored credentials.
The API generates a URL to use for logging in, and using Firebug i can see what requests and responses are being sent/received.
My question is, how can i use HttpWebRequest and HttpWebResponse to mimic what happens in the browser in C#?
Would it be possible to use a web browser component inside a C# app, populate the username and password fields and submit the login?
I've done this kind of thing before, and ended up with a nice toolkit for writing these types of applications. I've used this toolkit to handle non-trivial back-n-forth web requests, so it's entirely possible, and not extremely difficult.
I found out quickly that doing the HttpWebRequest/HttpWebResponse from scratch really was lower-level than I wanted to be dealing with. My tools are based entirely around the HtmlAgilityPack by Simon Mourier. It's an excellent toolset. It does a lot of the heavy lifting for you, and makes parsing of the fetched HTML really easy. If you can rock XPath queries, the HtmlAgilityPack is where you want to start. It handles poorly foormed HTML quite well too!
You still need a good tool to help debug. Besides what you have in your debugger, being able to inspect the http/https traffic as it goes back-n-forth across the wire is priceless. Since you're code is going to be making these requests, not your browser, FireBug isn't going to be of much help debugging your code. There's all sorts of packet sniffer tools, but for HTTP/HTTPS debugging, I don't think you can beat the ease of use and power of Fiddler 2. The newest version even comes with a plugin for firefox to quickly divert requests through fiddler and back. Because it can also act as a seamless HTTPS proxy you can inspect your HTTPS traffic as well.
Give 'em a try, I'm sure they'll be two indispensable tools in your hacking.
Update: Added the below code example. This is pulled from a not-much-larger "Session" class that logs into a website and keeps a hold of the related cookies for you. I choose this because it does more than a simple 'please fetch that web page for me' code, plus it has a line-or-two of XPath querying against the final destination page.
public bool Connect() {
if (string.IsNullOrEmpty(_Username)) { base.ThrowHelper(new SessionException("Username not specified.")); }
if (string.IsNullOrEmpty(_Password)) { base.ThrowHelper(new SessionException("Password not specified.")); }
_Cookies = new CookieContainer();
HtmlWeb webFetcher = new HtmlWeb();
webFetcher.UsingCache = false;
webFetcher.UseCookies = true;
HtmlWeb.PreRequestHandler justSetCookies = delegate(HttpWebRequest webRequest) {
SetRequestHeaders(webRequest, false);
return true;
};
HtmlWeb.PreRequestHandler postLoginInformation = delegate(HttpWebRequest webRequest) {
SetRequestHeaders(webRequest, false);
// before we let webGrabber get the response from the server, we must POST the login form's data
// This posted form data is *VERY* specific to the web site in question, and it must be exactly right,
// and exactly what the remote server is expecting, otherwise it will not work!
//
// You need to use an HTTP proxy/debugger such as Fiddler in order to adequately inspect the
// posted form data.
ASCIIEncoding encoding = new ASCIIEncoding();
string postDataString = string.Format("edit%5Bname%5D={0}&edit%5Bpass%5D={1}&edit%5Bform_id%5D=user_login&op=Log+in", _Username, _Password);
byte[] postData = encoding.GetBytes(postDataString);
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.ContentLength = postData.Length;
webRequest.Referer = Util.MakeUrlCore("/user"); // builds a proper-for-this-website referer string
using (Stream postStream = webRequest.GetRequestStream()) {
postStream.Write(postData, 0, postData.Length);
postStream.Close();
}
return true;
};
string loginUrl = Util.GetUrlCore(ProjectUrl.Login);
bool atEndOfRedirects = false;
string method = "POST";
webFetcher.PreRequest = postLoginInformation;
// this is trimmed...this was trimmed in order to handle one of those 'interesting'
// login processes...
webFetcher.PostResponse = delegate(HttpWebRequest webRequest, HttpWebResponse response) {
if (response.StatusCode == HttpStatusCode.Found) {
// the login process is forwarding us on...update the URL to move to...
loginUrl = response.Headers["Location"] as String;
method = "GET";
webFetcher.PreRequest = justSetCookies; // we only need to post cookies now, not all the login info
} else {
atEndOfRedirects = true;
}
foreach (Cookie cookie in response.Cookies) {
// *snip*
}
};
// Real work starts here:
HtmlDocument retrievedDocument = null;
while (!atEndOfRedirects) {
retrievedDocument = webFetcher.Load(loginUrl, method);
}
// ok, we're fully logged in. Check the returned HTML to see if we're sitting at an error page, or
// if we're successfully logged in.
if (retrievedDocument != null) {
HtmlNode errorNode = retrievedDocument.DocumentNode.SelectSingleNode("//div[contains(#class, 'error')]");
if (errorNode != null) { return false; }
}
return true;
}
public void SetRequestHeaders(HttpWebRequest webRequest) { SetRequestHeaders(webRequest, true); }
public void SetRequestHeaders(HttpWebRequest webRequest, bool allowAutoRedirect) {
try {
webRequest.AllowAutoRedirect = allowAutoRedirect;
webRequest.CookieContainer = _Cookies;
// the rest of this stuff is just to try and make our request *look* like FireFox.
webRequest.UserAgent = #"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.3) Gecko/20070309 Firefox/2.0.0.3";
webRequest.Accept = #"text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
webRequest.KeepAlive = true;
webRequest.Headers.Add(#"Accept-Language: en-us,en;q=0.5");
//webRequest.Headers.Add(#"Accept-Encoding: gzip,deflate");
}
catch (Exception ex) { base.ThrowHelper(ex); }
}
Here is how i solved it:
public partial class Form1 : Form {
private string LoginUrl = "/apilogin/login";
private string authorizeUrl = "/apilogin/authorize";
private string doneUrl = "/apilogin/done";
public Form1() {
InitializeComponent();
this.Load += new EventHandler(Form1_Load);
}
void Form1_Load(object sender, EventArgs e) {
PhotobucketNet.Photobucket pb = new Photobucket("pubkey","privatekey");
string url = pb.GenerateUserLoginUrl();
webBrowser1.Url = new Uri(url);
webBrowser1.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(webBrowser1_DocumentCompleted);
}
void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) {
if (e.Url.AbsolutePath.StartsWith(LoginUrl))
{
webBrowser1.Document.GetElementById("usernameemail").SetAttribute("Value","some username");
webBrowser1.Document.GetElementById("password").SetAttribute("Value","some password");
webBrowser1.Document.GetElementById("login").InvokeMember("click");
}
if (e.Url.AbsolutePath.StartsWith(authorizeUrl))
{
webBrowser1.Document.GetElementById("allow").InvokeMember("click");
}
if (e.Url.AbsolutePath.StartsWith(doneUrl))
{
string token = webBrowser1.Document.GetElementById("oauth_token").GetAttribute("value");
}
}
}
the token capture in the last if block is what is needed to continue using the API. This method works fine for me as of course the code that needs this will be running on windows so i have no problem spawning a process to load this separate app to extract the token.
It is possible to use the native WebbrowserControl to login into websites. But as u see in the example u'll have to identify the name of the control before.
private void webBrowserLogin_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
if (webBrowserLogin.Url.ToString() == WebSiteUrl)
{
foreach (HtmlElement elem in webBrowserLogin.Document.All)
{
if (elem.Name == "user_name") // name of the username input
{
elem.InnerText = UserName;
}
if (elem.Name == "password") // name of the password input
{
elem.InnerText = Password;
}
}
foreach (HtmlElement elem in webBrowserLogin.Document.All)
{
if (elem.GetAttribute("value") == "Login")
{
elem.InvokeMember("Click");
}
}
}
}
Check out Rohit's BrowserSession class which he's described here (and part 2 here). Based on HtmlAgilityPack but does some of the boring work of populating POST data from a FORM.
Related
Beginning a new, quick http request while a long running one is in progress
As per the title, I have a long running GET request executing and need to execute small, quick ones while waiting for a response for the long one. Unfortunately, the quick requests seem to have to wait for the long running one to execute before they are allowed to execute. If it helps to visualize, this is a web service taking requests that it is relaying to another web service and returning the results back to it's client so they can come in pretty much however and whenever the client is firing them. I have tried using the same httpclient, different httpclients, I have tried changing HttpClientHandler.MaxConnectionsPerServer and ServicePointManager.DefaultConnectionLimit and nothing seems to do it. I have seen the solutions using WhenAll, etc but that doesn't work here because the small requests are coming in after the long running one has long since started and "WhenAll-ed". I created a test app that is a simple windows form to troubleshoot this, it looks pretty much like this (with button clicks to simulate web requests being received): CookieContainer CookieContainer = null; MyClient myClient; public Form1() { InitializeComponent(); ServicePointManager.DefaultConnectionLimit = 10; myClient = new MyClient(ref CookieContainer); } private async void button1_Click(object sender, EventArgs e) { bool success = await myClient.GetStockItemTables(); this.label1.Text = success.ToString(); } private async void button2_Click(object sender, EventArgs e) { bool success = await myClient.GetGiftCardBalance(); this.label2.Text = success.ToString(); } class MyClient { string EndpointURL = #"XXX"; private HttpClient Client; private HttpClientHandler ClientHandler; public MyClient(ref CookieContainer cookieContainer) { try { bool loginRequired = false; if (cookieContainer == null) { loginRequired = true; cookieContainer = new CookieContainer(); //clientHandler = new HttpClientHandler { UseCookies = true, CookieContainer = new CookieContainer() }; } ClientHandler = new HttpClientHandler { UseCookies = true, CookieContainer = cookieContainer, MaxConnectionsPerServer = 10 }; Client = new HttpClient(ClientHandler) { BaseAddress = new Uri(EndpointURL), DefaultRequestHeaders = { Accept = {MediaTypeWithQualityHeaderValue.Parse("text/json")} }, Timeout = TimeSpan.FromHours(1) }; if (loginRequired) DoLogin(false); } catch (Exception ex) { } } public async Task<bool> LongRunningRequest() { try { var result = await Client.GetAsync(EndpointURL + "YYY"); return true; } catch (Exception ex) { return false; } } public async Task<bool> QuickRequest() { try { var result = await Client.GetAsync(EndpointURL + "ZZZ"); return true; } catch (Exception ex) { return false; } } } The CookieContainer is passed in/out to maintain login info and Login method excluded because it works and it isn't really relevant to the issue. The result of clicking button 1 then button 2 is code getting 'stuck' at the quick requests 'PostAsync' until the long requests 'GetAsync' completes. Same results if each call uses it's own MyClient instead of sharing a global one. Help?
So looks like it wasn't me or the code. The web service I am using was blocking concurrent requests from the same session. Changing the quick request to just do a 'get' from google evaluates immediately and is not blocked, as expected. Looks like the solution is to create a second session for these quick calls so they aren't blocked if we are in the middle of a long request.
Way to get ALL cookies created in Awesomium? (including Http)
I'm using the Awesomium .NET API to log in to http://www.habbo.com using the username and password provided by the user in a text box. The issue I have is that to send further requests (eg. navigate to a room - Habbo is an online game) via Http I need to send the same cookies, otherwise it doesn't work. I'm trying to send the navigate request using this code: using (WebClient WC = new WebClient()) { WC.Headers["X-App-Key"] = Token; WC.DownloadString(new Uri(string.Format("http://www.habbo.com/components/roomNavigation?targetId={0}&roomType=private&move=true", roomID))); } I need to attach the Http cookies to WC in order for it to be successful. I'm logging in to http://www.habbo.com using this code: public int Timer = 0; public void AttemptLogin_Tick(object sender, EventArgs e) { Timer++; if (Timer == 1) { webControl1.Source = new Uri("http://www.habbo.com/client"); } if (Timer == 3) { webControl1.Source = new Uri(string.Format("https://www.habbo.com/account/submit?credentials.username={0}&credentials.password={1}", username2, password2)); } if(Timer == 6) { webControl1.Source = new Uri("http://www.habbo.com/client"); } } I know I can get some of the cookies using webControl1.ExecuteJavascriptWithResult("document.cookie;"); but this doesn't get the Http ones I need also. The cookies are unique to each session, so as far as I'm aware using HttpWebRequest to get them would result in different Http cookies to the ones I need (and therefore log me out if I tried sending them instead, as it would indicate I had two sessions logged in). Does anyone have any ideas? Thanks.
Twitter API Safe Authentication
I'm using the C# Twitterizer in a WPF application to authenticate users to Twitter so I can publish tweets to their stream. (But that's irrelevant because the question is about the API itself). I do not wish to create a new login interface, I want to use Twitter's Login page embedded in a WebBrowser control. Does Twitter support the same authentication style as Facebook where the user logs in to the regular FB login page and the access token is sent back in the callback URL? Or sending the username and password is the only way to get an access token (in Twitter)?!
Here's an Oauth 1.0a class that works with Twitter, and allows what you want. There's also a simple example that shows how to use the class. The code looks like this: OAuth.Manager oauth; AuthSettings settings; public void Foo() { oauth = new OAuth.Manager(); oauth["consumer_key"] = TWITTER_CONSUMER_KEY; oauth["consumer_secret"] = TWITTER_CONSUMER_SECRET; settings = AuthSettings.ReadFromStorage(); if (VerifyAuthentication()) { Tweet("Hello, World"); } } private void Tweet(string message) { var url = "http://api.twitter.com/1/statuses/update.xml?status=" + message; var authzHeader = oauth.GenerateAuthzHeader(url, "POST"); var request = (HttpWebRequest)WebRequest.Create(url); request.Method = "POST"; request.PreAuthenticate = true; request.AllowWriteStreamBuffering = true; request.Headers.Add("Authorization", authzHeader); using (var response = (HttpWebResponse)request.GetResponse()) { if (response.StatusCode != HttpStatusCode.OK) { ... } } } private bool VerifyAuthentication() { if (!settings.Completed) { var dlg = new TwitterAppApprovalForm(); // your form with an embedded webbrowser dlg.ShowDialog(); if (dlg.DialogResult == DialogResult.OK) { settings.access_token = dlg.AccessToken; settings.token_secret = dlg.TokenSecret; settings.Save(); } if (!settings.Completed) { MessageBox.Show("You must approve this app for use with Twitter\n" + "before updating your status with it.\n\n", "No Authorizaiton for TweetIt", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); return false; } } // apply stored information into the oauth manager oauth["token"] = settings.access_token; oauth["token_secret"] = settings.token_secret; return true; } The TwitterAppApprovalForm is boilerplate, and is included in the example. When you have no cached access_token and token-secret, then that form pops open, hosting an embedded webbrowser that displays the Twitter authorization form. If you have the cached data, then you don't need to display that form.
Yes, Twitter seupports the same authentication style than Facebook called OAuth. Facebook uses OAuth 2 and Twitter uses OAuth 1.0a Take a look to Spring.NET Social Twitter : http://springframework.net/social-twitter/ It provides samples for what you are trying to do.
Test Credentials to access a web page
Is there a nice and tested piece of code out there that I can use for this purpose: get the user/pass and the address of a web service (asmx page) and check if the user/pass are valid or not. I think I should use HTTPRequest,etc to do that but I do not have a good knowledge on that topic , causing my current method to not working properly. If there is a good piece of code for this purpose I appreciate for pointing me to that. Thanks P.S: I am not using DefaultCredentials in my code. Since I want them to enter user/pass so now I need to be able to TEST their user/pass and show proper message to them if their credentials is not valid.
You can use the HttpWebRequest.Credentials Property (depends on the web service authentication) and the CredentialCache Class. Also some code examples (from google): Retrieving HTTP content in .NET Combine Invoking Web Service dynamically using HttpWebRequest with .Credentials.
public bool TestCredentials(string url, string username, string password) { var web = new WebClient(); web.Credentials = new NetworkCredential(username,password); try { web.DownloadData(url); return true; } catch (WebException ex) { var response = (HttpWebResponse)ex.Response; if (response.StatusCode == HttpStatusCode.Unauthorized) { return false; } throw; } }
Update Twitter Status in C#
I'm trying to update a user's Twitter status from my C# application. I searched the web and found several possibilities, but I'm a bit confused by the recent (?) change in Twitter's authentication process. I also found what seems to be a relevant StackOverflow post, but it simply does not answer my question because it's ultra-specific regading a code snippet that does not work. I'm attempting to reach the REST API and not the Search API, which means I should live up to the stricter OAuth authentication. I looked at two solutions. The Twitterizer Framework worked fine, but it's an external DLL and I would rather use source code. Just as an example, the code using it is very clear and looks like so: Twitter twitter = new Twitter("username", "password"); twitter.Status.Update("Hello World!"); I also examined Yedda's Twitter library, but this one failed on what I believe to be the authentication process, when trying basically the same code as above (Yedda expects the username and password in the status update itself but everything else is supposed to be the same). Since I could not find a clear cut answer on the web, I'm bringing it to StackOverflow. What's the simplest way to get a Twitter status update working in a C# application, without external DLL dependency? Thanks
If you like the Twitterizer Framework but just don't like not having the source, why not download the source? (Or browse it if you just want to see what it's doing...)
I'm not a fan of re-inventing the wheel, especially when it comes to products that already exist that provide 100% of the sought functionality. I actually have the source code for Twitterizer running side by side my ASP.NET MVC application just so that I could make any necessary changes... If you really don't want the DLL reference to exist, here is an example on how to code the updates in C#. Check this out from dreamincode. /* * A function to post an update to Twitter programmatically * Author: Danny Battison * Contact: gabehabe#hotmail.com */ /// <summary> /// Post an update to a Twitter acount /// </summary> /// <param name="username">The username of the account</param> /// <param name="password">The password of the account</param> /// <param name="tweet">The status to post</param> public static void PostTweet(string username, string password, string tweet) { try { // encode the username/password string user = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(username + ":" + password)); // determine what we want to upload as a status byte[] bytes = System.Text.Encoding.ASCII.GetBytes("status=" + tweet); // connect with the update page HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://twitter.com/statuses/update.xml"); // set the method to POST request.Method="POST"; request.ServicePoint.Expect100Continue = false; // thanks to argodev for this recent change! // set the authorisation levels request.Headers.Add("Authorization", "Basic " + user); request.ContentType="application/x-www-form-urlencoded"; // set the length of the content request.ContentLength = bytes.Length; // set up the stream Stream reqStream = request.GetRequestStream(); // write to the stream reqStream.Write(bytes, 0, bytes.Length); // close the stream reqStream.Close(); } catch (Exception ex) {/* DO NOTHING */} }
Another Twitter library I have used sucessfully is TweetSharp, which provides a fluent API. The source code is available at Google code. Why don't you want to use a dll? That is by far the easiest way to include a library in a project.
The simplest way to post stuff to twitter is to use basic authentication , which isn't very strong. static void PostTweet(string username, string password, string tweet) { // Create a webclient with the twitter account credentials, which will be used to set the HTTP header for basic authentication WebClient client = new WebClient { Credentials = new NetworkCredential { UserName = username, Password = password } }; // Don't wait to receive a 100 Continue HTTP response from the server before sending out the message body ServicePointManager.Expect100Continue = false; // Construct the message body byte[] messageBody = Encoding.ASCII.GetBytes("status=" + tweet); // Send the HTTP headers and message body (a.k.a. Post the data) client.UploadData("http://twitter.com/statuses/update.xml", messageBody); }
Try LINQ To Twitter. Find LINQ To Twitter update status with media complete code example that works with Twitter REST API V1.1. Solution is also available for download. LINQ To Twitter Code Sample var twitterCtx = new TwitterContext(auth); string status = "Testing TweetWithMedia #Linq2Twitter " + DateTime.Now.ToString(CultureInfo.InvariantCulture); const bool PossiblySensitive = false; const decimal Latitude = StatusExtensions.NoCoordinate; const decimal Longitude = StatusExtensions.NoCoordinate; const bool DisplayCoordinates = false; string ReplaceThisWithYourImageLocation = Server.MapPath("~/test.jpg"); var mediaItems = new List<media> { new Media { Data = Utilities.GetFileBytes(ReplaceThisWithYourImageLocation), FileName = "test.jpg", ContentType = MediaContentType.Jpeg } }; Status tweet = twitterCtx.TweetWithMedia( status, PossiblySensitive, Latitude, Longitude, null, DisplayCoordinates, mediaItems, null);
Try TweetSharp . Find TweetSharp update status with media complete code example works with Twitter REST API V1.1. Solution is also available for download. TweetSharp Code Sample //if you want status update only uncomment the below line of code instead //var result = tService.SendTweet(new SendTweetOptions { Status = Guid.NewGuid().ToString() }); Bitmap img = new Bitmap(Server.MapPath("~/test.jpg")); if (img != null) { MemoryStream ms = new MemoryStream(); img.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); ms.Seek(0, SeekOrigin.Begin); Dictionary<string, Stream> images = new Dictionary<string, Stream>{{"mypicture", ms}}; //Twitter compares status contents and rejects dublicated status messages. //Therefore in order to create a unique message dynamically, a generic guid has been used var result = tService.SendTweetWithMedia(new SendTweetWithMediaOptions { Status = Guid.NewGuid().ToString(), Images = images }); if (result != null && result.Id > 0) { Response.Redirect("https://twitter.com"); } else { Response.Write("fails to update status"); } }
Here's another solution with minimal code using the excellent AsyncOAuth Nuget package and Microsoft's HttpClient. This solution also assumes you're posting on your own behalf so you have your access token key/secret already, however even if you don't the flow is pretty easy (see AsyncOauth docs). using System.Threading.Tasks; using AsyncOAuth; using System.Net.Http; using System.Security.Cryptography; public class TwitterClient { private readonly HttpClient _httpClient; public TwitterClient() { // See AsyncOAuth docs (differs for WinRT) OAuthUtility.ComputeHash = (key, buffer) => { using (var hmac = new HMACSHA1(key)) { return hmac.ComputeHash(buffer); } }; // Best to store secrets outside app (Azure Portal/etc.) _httpClient = OAuthUtility.CreateOAuthClient( AppSettings.TwitterAppId, AppSettings.TwitterAppSecret, new AccessToken(AppSettings.TwitterAccessTokenKey, AppSettings.TwitterAccessTokenSecret)); } public async Task UpdateStatus(string status) { try { var content = new FormUrlEncodedContent(new Dictionary<string, string>() { {"status", status} }); var response = await _httpClient.PostAsync("https://api.twitter.com/1.1/statuses/update.json", content); if (response.IsSuccessStatusCode) { // OK } else { // Not OK } } catch (Exception ex) { // Log ex } } } This works on all platforms due to HttpClient's nature. I use this method myself on Windows Phone 7/8 for a completely different service.