Second Life of a Hungarian SharePoint Geek

May 14, 2010

Accessing internal Exchange web services from an external application through ISA server using forms-based authentication

Filed under: Exchange, FBA, ISA, Web service — Tags: , , , — Peter Holpar @ 23:14

I think it should be a quite common request to access the Exchange Server of a company from an application. In our case, the Exchange Server is version 2007, and it is published through ISA Server 2006 (but it might be a ForeFront TMG 2010 as well) for external access. The authentication method is forms-based through HTTPS channel.

Most common way to access Exchange Server 2007 programmatically is Exchange Web Services (EWS). The authentication scenario outlined above requires a few tricks, as you cannot use the standard NetworkCredential directly for authentication.

FBA requires you to post the credentials (like user name and password) to the authentication URL and the response contains the cookie(s) that must be added to forthcoming request for authentication.

My example is based on the code found here:

Access the Exchange store via WebDAV with Form-Based-Authentication turned on [Updated]

There are a few difference, for example, in our case not the owaauth.dll of OWA is used for authentication, but the CookieAuth.dll of ISA.

I’ve got the simple EWS sample for my post from this article:

Using Exchange Web Services 2007: The Basics

As I’ve mentioned above, in my scenario I cannot use NetworkCredential for the web service. Instead, I should get the FBA cookie and add it to the CookieContainer of the web service proxy.

  1. ExchangeServiceBinding.ExchangeServiceBinding esb = new ExchangeServiceBinding.ExchangeServiceBinding();
  2. esb.Url = "https://mail.company.com/EWS/Exchange.asmx";
  3. esb.Credentials = CredentialCache.DefaultCredentials;
  4. CookieCollection cookies = DoFormbasedAuthentication(esb.Url, new NetworkCredential("user", "password", "doamain"));
  5.  
  6. FindItemType fit = new FindItemType();
  7. fit.ItemShape = new ItemResponseShapeType { BaseShape = DefaultShapeNamesType.Default };
  8. fit.ParentFolderIds = new DistinguishedFolderIdType[]
  9. {
  10.     new DistinguishedFolderIdType
  11.     {
  12.         Mailbox = new EmailAddressType{ EmailAddress="user@gcompany.com"},
  13.         Id = DistinguishedFolderIdNameType.calendar
  14.     }
  15. };
  16. // add cookies to WS
  17. esb.CookieContainer = new CookieContainer();
  18. esb.CookieContainer.Add(cookies);
  19.  
  20. fit.Traversal = ItemQueryTraversalType.Shallow;
  21.  
  22. FindItemResponseType firt = esb.FindItem(fit);

In the DoFormbasedAuthentication method we get the cookie required for FBA authentication.

  1. private CookieCollection DoFormbasedAuthentication(String url, NetworkCredential credential)
  2. {
  3.     String server;
  4.     HttpWebRequest request;
  5.     HttpWebResponse response;
  6.     byte[] body;
  7.     Stream stream;
  8.  
  9.     try
  10.     {
  11.         Uri uri = new Uri(url);
  12.         // Get the server portion of the requested uri and appen the authentication dll from Exchange
  13.         server = String.Format("{0}://{1}/{2}", uri.Scheme, uri.DnsSafeHost, "CookieAuth.dll?Logon");
  14.  
  15.         // Initiate a new WebRequest to the given URI.
  16.         request = (HttpWebRequest)System.Net.WebRequest.Create(server);
  17.  
  18.         request.Method = "POST";
  19.         request.CookieContainer = new CookieContainer();
  20.         request.ContentType = "application/x-www-form-urlencoded";
  21.  
  22.         // Prepare the body of the request
  23.         body = Encoding.UTF8.GetBytes(string.Format("curl=Z2F&flags=0&forcedownlevel=0&formdir=6&trusted=4&username={1}\\{2}&password={3}",
  24.             uri, credential.Domain, credential.UserName, credential.Password));
  25.  
  26.         request.ContentLength = body.Length;
  27.  
  28.         // Send the request to the server
  29.         stream = request.GetRequestStream();
  30.         stream.Write(body, 0, body.Length);
  31.         stream.Close();
  32.  
  33.         // Get the response
  34.         response = (HttpWebResponse)request.GetResponse();
  35.  
  36.         // Check if the login was successful
  37.         if (response.Cookies.Count < 1) throw
  38.             new AuthenticationException("Failed to login to ISA. Be sure your user name, domain name and password are correct.");
  39.  
  40.         return response.Cookies;
  41.     }
  42.     catch (AuthenticationException ex)
  43.     {
  44.         throw;
  45.     }
  46.     catch (Exception ex)
  47.     {
  48.         throw new Exception("Failed to login to OWA. The following error occured: " + ex.Message, ex);
  49.     }
  50. }

You can see that I have included several form parameter beyond the credentials when assembling the request body. These parameters are hidden HTTP form fields or other form input fields from the source of the authentication page. You can verify that if you try to access the page using a browser and checking its source.

image

To add  some really SharePoint specific content to this post either, you might need similar tricks when accessing SharePoint web services (like the other EWS: Excel Web Services) on a FBA site. In this case you have to use the Login method of the Authentication web service to get the cookies you need as illustrated in this article:

How to: Set Various Credentials

12 Comments »

  1. Hi,

    Do I need to enable persistent cookies in the ISA server for it to work? I tried the above code but instead of accessing an exchange server service Im trying to access a Sharepoint 2010 web service. On fiddler, only one cookie is returned, and I assume its a sharepoint cookie. What I need is the cookie used by ISA server.

    Thanks!

    Comment by Ernie Balbaligo — February 14, 2011 @ 09:35

    • Hi Ernie,

      A few weeks after posting my code it began to malfunction, so I’ve updated it to handle the new situation. I’ve tried to find the reason, but got not too much info from the Exchange / ISA admins. Might be that persisten cookies were allowed for a short time, and later they changed this setting, as it is usually not a very secure solution. As my time allows I will post the updated solution that will hopefully help you.

      Peter

      Comment by Peter Holpar — February 16, 2011 @ 19:30

      • Hi Ernie,

        Sorry for the late answer! I’ve checked my updated code, and found the only significant difference is in the DoFormbasedAuthentication method. To get the cookie from the ISA, I had to include the following line after preparing the request:
        request.AllowAutoRedirect = false;
        Let me know if it works for you.

        Peter

        Comment by Peter Holpar — May 3, 2011 @ 07:59

  2. Hi Peter,
    first of all thanks for this, if I actually manage to make it work it will save me a great deal of work and dirty workarounds.
    I tried this piece of code but I get an error code 400 “Bad Request”.
    Here’s the body I used:
    curl=Z2FOWAZ2F&flags=0&forcedownlevel=0&formdir=3&trusted=0&username={1}\{2}&password={3}&SubmitCreds=Log+On
    Do you have any idea why am I getting this error?

    Thanks,
    Roberto.

    Comment by Roberto Santoro — May 2, 2011 @ 20:22

    • Hi Roberto,

      I’ve got the same result (400 – Bad Request) when using curl=Z2FOWAZ2F, try simply curl=Z2F.

      Hope it helps.

      Peter

      Comment by Peter Holpar — May 3, 2011 @ 07:52

  3. P.S.: don’t worry about the trailing “SubmitCreds=Log+On”, I tried with and without it and I get the same error.

    Comment by Roberto Santoro — May 2, 2011 @ 20:24

  4. The solution was the answer you already gave to Ernie:
    request.AllowAutoRedirect = false

    Thanks a lot for your help
    Roberto.

    Comment by Roberto Santoro — May 3, 2011 @ 15:09

    • Hi Roberto,

      I’m glad that my other answer helped (actually it was posted a few minutes later I replied your question), however, missing that line throws a different exception for me: 403 – Forbidden.

      Peter

      Comment by Peter Holpar — May 4, 2011 @ 11:17

  5. Yeah, I was getting both 403 and 400 depending on how I configured the body of the request.
    I don’t know why I was convinced the body that was getting 403 was not the correct one and I was trying to correct the 400 instead.

    Thanks a lot for this, it really helped me a lot (the alternative was to get ISA to treat EWS differently from the other folders which I don’t even know whether is possible).

    Comment by Roberto Santoro — May 4, 2011 @ 15:56

  6. Hello,
    how often do you think we can check for emails without risking to overload the exchange server?

    Thanks,
    Roberto.

    Comment by Roberto Santoro — May 7, 2011 @ 08:01

    • Hi Roberto,

      I assume it depends on the performance and other load / traffic on of your exchange server and the size of the mail storage. You should make some experiments while monitoring performance to get the answer for your question.

      Peter

      Comment by Peter Holpar — May 7, 2011 @ 21:44

  7. Hi,

    I am trying to do Form Based Authentication from Java. So I am trying the same way. I am using Exchange server 2010 for testing.

    But I am not able find “CookieAuth.dll”. If I access “http:///Cookie/Auth.dll”, I am getting 404 “Not Found” Error.

    Is there something that I should in the Exchange server to to make CookieAuth.dll available?

    But if I access, the URL “http:///owa/auth/owaauth.dll”, then I am getting some Cookies back in the response.

    If I use owaauth.dll, I have to pass “destination”, “username” and “Password” as the form parameters in the POST request.

    What will be the value of “destination” in Exchange server 2010? Will it be “http:///owa”? Or some thing else?

    Once I have the cookies, I should send them along with EWS request. So I send a request with the cookie data like :

    POST /owa HTTP/1.1
    Content-type: text/xml; charset=utf-8
    User-Agent: ExchangeServicesClient/0.0.0.0
    Accept: text/xml
    Keep-Alive: 300
    Connection: Keep-Alive
    Accept-Encoding: gzip,deflate
    Cookie: OutlookSession=c9748798e2f840b49e5e2df4ae611e06; path=/; HttpOnly, sessionid=9c3446f0-9e46-4867-9ad1-3cb73944f2be; path=/, cadata=”1aJ0Vpf3Aux0aoFhNQdA1gvzZtd1Td9ZptRU7LHL8yEYWaKoox9z99IeN3no/xlGzwdq03WoSft1BCkkVrf9vsbao+CrFwc2JcY6xvXx6uRLFf5QqSHpVaHy/o2EyHo5D”; HttpOnly; path=/, OutlookSession=c9748798e2f840b49e5e2df4ae611e06; path=/; HttpOnly, sessionid=9c3446f0-9e46-4867-9ad1-3cb73944f2be; path=/; path=/
    Host: 10.192.37.30:1234
    Content-Length: 630

    AllProperties

    But the response returned is

    HTTP/1.1 301 Moved Permanently
    Content-Length: 0
    Location: /owa/
    Server: Microsoft-IIS/7.5
    X-Powered-By: ASP.NET
    X-UA-Compatible: IE=EmulateIE7
    Date: Thu, 15 Sep 2011 22:33:46 GMT
    Connection: close

    Could you please tell me what I am doing wrong.

    Thanks,
    Paul

    Comment by Paul — November 18, 2011 @ 14:58


RSS feed for comments on this post. TrackBack URI

Leave a comment

Create a free website or blog at WordPress.com.