Introduction
Many cloud storage providers like www.box.com
use an authentication mechanism that directs you to a WEB page to enter
your credential. If you develop a Web based application this is a
natural way to do that, but in the case of a desktop application or a
windows store App you would want to provide your own login screen that
would be a windows form if you are writing a desktop application.
I have searched the forums for a solution to that problem but I failed
to find an end to end solution. However some posts guided me to the
solution I'm presenting in this article.
The method I describe is valid with the version 1 of the Box API. They
have just release a V2 where the authentication mechanism is now using
OAuth 2.0, but the current mechanism I describe is still valid.
Remark: You will need to
register as a developer with Box.com as this code needs an API_KEY that
you obtain after registering. This is a free process. This code uses
the discontinued free library BoxSync
that implements in C# the Box API v1.
WEB form submission with WebClient
My first solution to enter the credentials in a windows dialog was
to put a IE control in a dialog box and pass it the URL of the Box
login screen. The drawback of that solution is that once the WEB page
displays the successful login, you still need to press the OK button of
the dialog box.
The support of Box told me that they don't have any API to
authenticate a user with its username/password, so I looked for a
solution that would be to automate the login by submitting the
authentication form from a C# program. After all, a browser is built
on top of HTTP protocols that are accessible to a program using classes
like WebRequest
or WebClient
.
I could find some useful posts that demonstrate how to submit a form
using the WebClient
client but there is more than just
passing the data
to the submit call, in the case of a real authentication protocol like
the one of Box you need to understand exactly what data they submit to
the server when you click Login.
The final code to submit the login form to Box is the following.
public bool AuthenticateUser(string ticket, string userName, string password, out string requestToken)
{
bool authSuccess = false;
requestToken = string.Empty;
string url = string.Format("http://www.box.net/api/1.0/auth/{0}", ticket);
Stream respStream = ExecuteREST_Request(url, Method.GET);
StreamReader reader = new StreamReader(respStream);
string responseText = reader.ReadToEnd();
requestToken = ExtractRequestToken(responseText);
using (WebClient webClient = new WebClient())
{
try
{
NameValueCollection formFields = new NameValueCollection();
formFields.Add("login", userName);
formFields.Add("password", password);
formFields.Add("_pw_sql", "");
formFields.Add("remember_login", "on");
formFields.Add("__login", "1");
formFields.Add("dologin", "1");
formFields.Add("reg_step", "");
formFields.Add("submit1", "1");
formFields.Add("folder", "");
formFields.Add("skip_framework_login", "1");
formFields.Add("login_or_register_mode", "login");
formFields.Add("new_login_or_register_mode", "");
formFields.Add("request_token", requestToken);
webClient.Proxy = null;
string actionUrl = string.Format("https://www.box.net/api/1.0/auth/{0}", ticket);
byte[] result = webClient.UploadValues(actionUrl, METHOD_POST, formFields);
string htmlText = ASCIIEncoding.ASCII.GetString(result);
if (CheckAuthenticated(htmlText))
{
authSuccess = true;
}
else
{
requestToken = ExtractRequestToken(htmlText);
authSuccess = false;
}
}
catch (WebException ex)
{
Trace.WriteLine(ex.Message);
}
}
return authSuccess;
}
If you look at this code, and especially at the part that is preparing
the parameters for the submit request, you will notice that there are
many more parameters than the user name and password.
That's the interesting part of the problem, when you want to submit a
particular form using WebClient
for instance, you need to
know what the
submit request contains exactly when you press the submit button!
Analyzing the submit request
The first step you need to do is to analyze the source of the WEB
that contains the Login form. From that source you will extract the URL
where to post the data. You'll find this URL in the Action of the form.
In the case of Box, the URL is the following one.
https://www.box.net/api/1.0/auth/<ticket>
The ticket is an identification string that you get when
initializing the authentication process with Box.
Once you have the URL where to submit the form, you need to identify
all the parameters that need to be passed with the request. You can
identify them by looking at the source of the page but it may not be
easy to rebuild the request and in the case of an authentication, only
one bit is enough to fail it.
I used a HTTP debugging tool to see the exact request which is sent
to the server. The tool I found is Fiddler Web
Debugger that you can download on their web page. This tool proved
very useful as it is able to monitor an https request which is what I
needed to do as the Box login url is on https.
Using Fiddler configured to monitor the https traffic I could see the
exact content of the requested submitted to the Box server for the
authentication. I could see that there were many other fields than the
user/password and one particularly attracted my attention. This field
is request_token and its value
is a string token that is different for every login.
Fortunately I could easily find in the source of the page where
the
value was set and it is in a piece of script. I wrote a
simple method to extract that value from the login page and used it to
build the parameters.
The last step of this authentication method is to check whether the
authentication was successful or not. Once again you need to analyze
the response from the request submission in order to know the outcome.
In the page that is returned when the operation is successful I found
the following string that gives me the proper information: api_auth_success
.
So when I submit the form I simply check if that string is present
in the response page and I can decide if it is successful or not.
Conclusion
This code only works with the Box login form, however you can apply
this method to any form that you need to submit by program. There is no
simple way to make this code generic because you need to analyze the
source
code of the form and even if in some cases it could be totally
automated, in the case of Box it would be difficult because of that
field
that is set by the execution of a script.
Another limitation of that method is that it depends on the method used
by the builders of the page. If they change something in their page,
this code won't work any more.
So if suddenly the login doesn't work anymore, the chances are high
that they would have changed something in the format of the submit
request.
Points of Interest
It took me some time to gather all the knowledge to do this simple
login window to authenticate to a Box account, so I think that many
readers of CodeProject will find this simple code useful.