Logging into Microsoft Online website programatically with C#
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 StreamWri开发者_JS百科ter(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();
精彩评论