none
Exchange Web Service Managed API not authorizing RRS feed

  • Question

  • I find it very strange that I am able to create appointments in my calendar on our company's exchange 2010 server using the asp.net 4.0 web application running on my XP machine which is not even part of the domain!, BUT when I upload the same code to our company's production Web application server (which is not same as the Exchange server), then I get the error as follows:

    System.Net.WebException: The remote server returned an error: (401) Unauthorized

    I am using Window's authentication throughout. Using service.UseDefaultCredentials = true; I just cannot afford to use the username/paasword for every staff who will be using this application. I am thinking there is some problem (rights/permissions/disabled impersonation) issue at the production Web application server (Windows 2008 m/c). I even played with the Application pool identity in IIS 7 by selecting all the builtin accounts it can possibly run under, but same error. I can clearly see that it is running under my Windows account right before the Appointment.Save() call is made. I am briefly impersonating using the logged in user's credentials and then removing the impersonation. I saw this technique elsewhere. But that doesn't make any difference either.

    These are the code files:

    Default.aspx.cs

    //(nothing much is going on in the markup page Default.aspx. Therefore not including)

    using System;
                using System.Collections.Generic;
                using System.Web;
                using System.Web.UI;
                using System.Web.UI.WebControls;
    
    
                using  Microsoft.Exchange.WebServices.Data;
                using Microsoft.Exchange.WebServices.Autodiscover;
                using System.Web.Configuration;
    
                namespace TestExchangeWebServices
                {
                    public partial class _Default : System.Web.UI.Page
                    {
                        protected ExchangeService service;
    
                        protected void Page_Load(object sender, EventArgs e)
                        {
                            service = new ExchangeService(ExchangeVersion.Exchange2010);
                            service.UseDefaultCredentials = true;
                            service.Url = new Uri(WebConfigurationManager.AppSettings["EWSURL"]);
    
    
                            SetAppointment("Test", DateTime.Now, "Test");
    
                        }
    
                        public void SetAppointment(string Subject, DateTime AptDateTime, string Body)
                        {
                            Appointment apt = new Appointment(service);
                            apt.Subject = Subject;
                            apt.Body = Body;
                            apt.Body.BodyType = BodyType.HTML;
                            apt.Start = AptDateTime;
                            apt.End = apt.Start.AddMinutes(30.00);
                            apt.ReminderMinutesBeforeStart = 15;
                            apt.IsReminderSet = true;
    
                            HttpContext.Current.Trace.Write("Before Impersonation: System.Security.Principal.WindowsIdentity.GetCurrent().Name = " + System.Security.Principal.WindowsIdentity.GetCurrent().Name  );
    
                            System.Security.Principal.WindowsImpersonationContext impersonationContext;
                            impersonationContext = ((System.Security.Principal.WindowsIdentity)HttpContext.Current.User.Identity).Impersonate();// //System.Threading.Thread.CurrentPrincipal.Identity
    
                            HttpContext.Current.Trace.Write("Before Saving Appointment. System.Security.Principal.WindowsIdentity.GetCurrent().Name = " + System.Security.Principal.WindowsIdentity.GetCurrent().Name);
    //This is where the call is made and error occurs                            
    apt.Save(SendInvitationsMode.SendToNone);
                            HttpContext.Current.Trace.Write("After Saving Appointment.");
    
                            impersonationContext.Undo();
                        }
    
                    }
                }

    Web.Config

    <?xml version="1.0"?>
    
    
      <configuration>
        <appSettings configProtectionProvider="RsaProtectedConfigurationProvider">
          <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element"
            xmlns="http://www.w3.org/2001/04/xmlenc#">
            <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
              <EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#">
                <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
                <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                  <KeyName>Rsa Key</KeyName>
                </KeyInfo>
                <CipherData>
                  <CipherValue>0Sw7QiYFKoD65nCXfakXUhJrjapk4uyQ9u6aPBStxB1XBIIPtXbuZJZb/GyMxgl7Gi3sqIkoq66BKa+MSzjAkpkIfnZmOhMNVomKofC3rlEf9NeIAdCEvjcmENhfGyA6aEJj96mGDxRDBE/FP1iQ8Z3x8Rob+HG1sbD0YJy2rpA=</CipherValue>
                </CipherData>
              </EncryptedKey>
            </KeyInfo>
            <CipherData>
              <CipherValue>HmmlAzyuedvlQ/+grwRKjTs5Z7sg5dYShHFYsFcI0q2ugkZ7oYYNTTEycyqzKyXmaaqwyE2lAsApApSvT+JDys021+sMrqLrF37xAkjRimKbPTylgznRZLQx2qKAZstb6qIis2mcLykgURtp2ytfoPl83jJzEU1y6PtB0loB/p4=</CipherValue>
            </CipherData>
          </EncryptedData>
        </appSettings>
        <connectionStrings>
          <add name="ApplicationServices"
               connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnetdb.mdf;User Instance=true"
               providerName="System.Data.SqlClient" />
        </connectionStrings>
    
        <system.web>
          <identity impersonate="false"/>
    
          <customErrors mode="Off"></customErrors>
    
          <compilation debug="true" targetFramework="4.0" />
    
          <authentication mode="Windows">
    
          </authentication>
    
          <membership>
            <providers>
              <clear/>
              <add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="ApplicationServices"
                   enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false"
                   maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10"
                   applicationName="/" />
            </providers>
          </membership>
    
          <profile>
            <providers>
              <clear/>
              <add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="/"/>
            </providers>
          </profile>
    
          <roleManager enabled="false">
            <providers>
              <clear/>
              <add name="AspNetSqlRoleProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="ApplicationServices" applicationName="/" />
              <add name="AspNetWindowsTokenRoleProvider" type="System.Web.Security.WindowsTokenRoleProvider" applicationName="/" />
            </providers>
          </roleManager>
    
        </system.web>
    
        <system.webServer>
           <modules runAllManagedModulesForAllRequests="true"/>
    
           <httpErrors errorMode="Detailed" />
           <asp scriptErrorSentToBrowser="true"/>
    
        </system.webServer>
      </configuration>


    • Edited by aamirghanchi Friday, January 24, 2014 7:53 PM emphasized that the development and exchange servers are on separate machines
    Friday, January 24, 2014 7:23 PM

Answers

  • Glen, thanks for the response! I had already implemented a solution that is working for me as follows. Anyway your input is much appreciated.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    
    
    using  Microsoft.Exchange.WebServices.Data;
    using Microsoft.Exchange.WebServices.Autodiscover;
    using System.Web.Configuration;
    
    //for impersonation before making calls
    using System.Security.Principal;
    using System.Web.Security;
    using System.Runtime.InteropServices;
    
    namespace TestExchangeWebServices
    {
        public partial class _Default : System.Web.UI.Page
        {
            protected ExchangeService service;
    
            //The following Impersonator*** variables are of the exchange account which has been configured to impersonate other users by enabling impersonation on the exchange server as they show at this link: http://msdn.microsoft.com/en-us/library/office/bb204095(v=exchg.140).aspx
                protected string ImpersonatorUsername = WebConfigurationManager.AppSettings["ImpersonatorUsername"];
                protected string ImpersonatorPassword = WebConfigurationManager.AppSettings["ImpersonatorPassword"];
                protected string ImpersonatorDomain = WebConfigurationManager.AppSettings["ImpersonatorDomain"];
    
    
            // This is for the user for whom the appointment need to be set on their exchange server. This user will be impersonated by the above impersonator. You do not need to get the password information for this user, just the email address will work.
                private string Username = HttpContext.Current.User.Identity.Name.Split('\\').Last(); //extract the username out of the "Domain\Username" format. It doesn't have to be the currently logged in user. As per your need you can use the username of any other company user for whom you know the email address.
                protected string ImpersonatedEmailAddress ;//= Username +"@"+ WebConfigurationManager.AppSettings["EmailDomain"];
    
            //start impersonation setup block. Credits: Impersonate a Specific User in Code http://support.microsoft.com/kb/306158#4
                public const int LOGON32_LOGON_INTERACTIVE = 2;
                public const int LOGON32_PROVIDER_DEFAULT = 0;
    
                WindowsImpersonationContext impersonationContext;
    
                [DllImport("advapi32.dll")]
                public static extern int LogonUserA(String lpszUserName,
                    String lpszDomain,
                    String lpszPassword,
                    int dwLogonType,
                    int dwLogonProvider,
                    ref IntPtr phToken);
                [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
                public static extern int DuplicateToken(IntPtr hToken,
                    int impersonationLevel,
                    ref IntPtr hNewToken);
    
                [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
                public static extern bool RevertToSelf();
    
                [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
                public static extern bool CloseHandle(IntPtr handle);
            //end impersonation setup block;
    
    
            protected void Page_Load(object sender, EventArgs e)
            {
                ImpersonatedEmailAddress = Username + "@" + WebConfigurationManager.AppSettings["EmailDomain"]; //form the email address out of the username, provided they both are same
                
                service = new ExchangeService(ExchangeVersion.Exchange2010);
                //service.UseDefaultCredentials = true;
                service.Credentials = new WebCredentials(ImpersonatorUsername, ImpersonatorPassword, ImpersonatorDomain);
                service.Url = new Uri(WebConfigurationManager.AppSettings["EWSURL"]);
                service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, ImpersonatedEmailAddress);
    
                SetAppointment("Test", DateTime.Now, "Test");
    
            }
    
            public void SetAppointment(string Subject, DateTime AptDateTime, string Body)
            {
                Appointment apt = new Appointment(service);
                apt.Subject = Subject;
                apt.Body = Body;
                apt.Body.BodyType = BodyType.HTML;
                apt.Start = AptDateTime;
                apt.End = apt.Start.AddMinutes(30.00);
                apt.ReminderMinutesBeforeStart = 15;
                apt.IsReminderSet = true;
    
    
                if (impersonateValidUser(ImpersonatorUsername, ImpersonatorDomain, ImpersonatorPassword)) //For this code to work you will have to enable impersonation on the Exchange server. This code works on the web application running on the company server, but not from my XP PC that is not part of the domain but is on VPN connection.
    
                {
                    HttpContext.Current.Trace.Write("Before Saving Appointment. System.Security.Principal.WindowsIdentity.GetCurrent().Name = " + System.Security.Principal.WindowsIdentity.GetCurrent().Name);
    
                    apt.Save(SendInvitationsMode.SendToNone);
    
                    HttpContext.Current.Trace.Write("After Saving Appointment.");
    
                    Label1.Text = String.Format("Appointment set successfully for {0}", ImpersonatedEmailAddress);
    
                }
    
                else //fall back to the code that uses logged in user's window identity and not impersonation. This code "strangely" worked from the web application installed on my Windows XP PC that was not part of the domain but was on VPN connection and yet saved appointments on the company's exchange server. I guess, the VPN connection compensates for all the mumbo-jumbo round about impersonation code in the impersonateValidUser method. Hack, this code worked even I had not configured the impersonation on the exchange server as they tell you to do at this link:  http://msdn.microsoft.com/en-us/library/office/bb204095(v=exchg.140).aspx
                {
                    service.Credentials = null;
                    service.ImpersonatedUserId = null;
                    service.UseDefaultCredentials = true;
    
                    HttpContext.Current.Trace.Write("Before Impersonation: System.Security.Principal.WindowsIdentity.GetCurrent().Name = " + System.Security.Principal.WindowsIdentity.GetCurrent().Name);
    
                    //this is not impersonation. It uses the logged in user's window identity. The window identity does not have to be that of the company domain. The windows identity of Local PC that is not part of the domain will also work
                    System.Security.Principal.WindowsImpersonationContext impersonationContext;
                    impersonationContext = ((System.Security.Principal.WindowsIdentity)HttpContext.Current.User.Identity).Impersonate();// //System.Threading.Thread.CurrentPrincipal.Identity
    
                    HttpContext.Current.Trace.Write("Before Saving Appointment. System.Security.Principal.WindowsIdentity.GetCurrent().Name = " + System.Security.Principal.WindowsIdentity.GetCurrent().Name);
    
                    apt.Save(SendInvitationsMode.SendToNone);
    
                    impersonationContext.Undo();
    
                }
    
            }
    
            //impersonation methods. Credit: Impersonate a Specific User in Code: http://support.microsoft.com/kb/306158#4
            private bool impersonateValidUser(String userName, String domain, String password)
            {
                WindowsIdentity tempWindowsIdentity;
                IntPtr token = IntPtr.Zero;
                IntPtr tokenDuplicate = IntPtr.Zero;
    
                if (RevertToSelf())
                {
                    if (LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE,
                        LOGON32_PROVIDER_DEFAULT, ref token) != 0)
                    {
                        if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                        {
                            tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                            impersonationContext = tempWindowsIdentity.Impersonate();
                            if (impersonationContext != null)
                            {
                                CloseHandle(token);
                                CloseHandle(tokenDuplicate);
                                return true;
                            }
                        }
                    }
                }
                if (token != IntPtr.Zero)
                    CloseHandle(token);
                if (tokenDuplicate != IntPtr.Zero)
                    CloseHandle(tokenDuplicate);
                return false;
            }
    
            private void undoImpersonation()
            {
                impersonationContext.Undo();
            }
    
        }
    }
    

     
    • Marked as answer by aamirghanchi Thursday, January 30, 2014 5:50 PM
    Thursday, January 30, 2014 5:50 PM

All replies

  • I am thinking it has something to do with the Server-to-Server Authorization . Thats a lots of configurations to make it work and the article is very vague on where the changes need to be made. It baffles me that here I am able to run the same web application from my home XP pc (granted that it is on VPN connection, but not on domain) without any problem and on the other hand have to make all these  changes to one of the servers (production web application and/or the exchange server), both of which are on the same domain. Doesn't make sense.

    Saturday, January 25, 2014 5:30 AM
  • For you to use Impersonated credentials in IIS to access a resource located on another server you need to use Kerberos delegation (or constrained delegation). There is an EWS sample of what you need to setup and test app on http://blogs.msdn.com/b/emeamsgdev/archive/2012/11/05/exchange-web-services-from-a-web-application-using-windows-authentication.aspx and http://blogs.msdn.com/b/emeamsgdev/archive/2012/07/26/exchange-web-services-and-sharepoint-without-applicationimpersonation.aspx

    Cheers
    Glen

     
    Monday, January 27, 2014 4:43 AM
  • Glen, thanks for the response! I had already implemented a solution that is working for me as follows. Anyway your input is much appreciated.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    
    
    using  Microsoft.Exchange.WebServices.Data;
    using Microsoft.Exchange.WebServices.Autodiscover;
    using System.Web.Configuration;
    
    //for impersonation before making calls
    using System.Security.Principal;
    using System.Web.Security;
    using System.Runtime.InteropServices;
    
    namespace TestExchangeWebServices
    {
        public partial class _Default : System.Web.UI.Page
        {
            protected ExchangeService service;
    
            //The following Impersonator*** variables are of the exchange account which has been configured to impersonate other users by enabling impersonation on the exchange server as they show at this link: http://msdn.microsoft.com/en-us/library/office/bb204095(v=exchg.140).aspx
                protected string ImpersonatorUsername = WebConfigurationManager.AppSettings["ImpersonatorUsername"];
                protected string ImpersonatorPassword = WebConfigurationManager.AppSettings["ImpersonatorPassword"];
                protected string ImpersonatorDomain = WebConfigurationManager.AppSettings["ImpersonatorDomain"];
    
    
            // This is for the user for whom the appointment need to be set on their exchange server. This user will be impersonated by the above impersonator. You do not need to get the password information for this user, just the email address will work.
                private string Username = HttpContext.Current.User.Identity.Name.Split('\\').Last(); //extract the username out of the "Domain\Username" format. It doesn't have to be the currently logged in user. As per your need you can use the username of any other company user for whom you know the email address.
                protected string ImpersonatedEmailAddress ;//= Username +"@"+ WebConfigurationManager.AppSettings["EmailDomain"];
    
            //start impersonation setup block. Credits: Impersonate a Specific User in Code http://support.microsoft.com/kb/306158#4
                public const int LOGON32_LOGON_INTERACTIVE = 2;
                public const int LOGON32_PROVIDER_DEFAULT = 0;
    
                WindowsImpersonationContext impersonationContext;
    
                [DllImport("advapi32.dll")]
                public static extern int LogonUserA(String lpszUserName,
                    String lpszDomain,
                    String lpszPassword,
                    int dwLogonType,
                    int dwLogonProvider,
                    ref IntPtr phToken);
                [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
                public static extern int DuplicateToken(IntPtr hToken,
                    int impersonationLevel,
                    ref IntPtr hNewToken);
    
                [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
                public static extern bool RevertToSelf();
    
                [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
                public static extern bool CloseHandle(IntPtr handle);
            //end impersonation setup block;
    
    
            protected void Page_Load(object sender, EventArgs e)
            {
                ImpersonatedEmailAddress = Username + "@" + WebConfigurationManager.AppSettings["EmailDomain"]; //form the email address out of the username, provided they both are same
                
                service = new ExchangeService(ExchangeVersion.Exchange2010);
                //service.UseDefaultCredentials = true;
                service.Credentials = new WebCredentials(ImpersonatorUsername, ImpersonatorPassword, ImpersonatorDomain);
                service.Url = new Uri(WebConfigurationManager.AppSettings["EWSURL"]);
                service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, ImpersonatedEmailAddress);
    
                SetAppointment("Test", DateTime.Now, "Test");
    
            }
    
            public void SetAppointment(string Subject, DateTime AptDateTime, string Body)
            {
                Appointment apt = new Appointment(service);
                apt.Subject = Subject;
                apt.Body = Body;
                apt.Body.BodyType = BodyType.HTML;
                apt.Start = AptDateTime;
                apt.End = apt.Start.AddMinutes(30.00);
                apt.ReminderMinutesBeforeStart = 15;
                apt.IsReminderSet = true;
    
    
                if (impersonateValidUser(ImpersonatorUsername, ImpersonatorDomain, ImpersonatorPassword)) //For this code to work you will have to enable impersonation on the Exchange server. This code works on the web application running on the company server, but not from my XP PC that is not part of the domain but is on VPN connection.
    
                {
                    HttpContext.Current.Trace.Write("Before Saving Appointment. System.Security.Principal.WindowsIdentity.GetCurrent().Name = " + System.Security.Principal.WindowsIdentity.GetCurrent().Name);
    
                    apt.Save(SendInvitationsMode.SendToNone);
    
                    HttpContext.Current.Trace.Write("After Saving Appointment.");
    
                    Label1.Text = String.Format("Appointment set successfully for {0}", ImpersonatedEmailAddress);
    
                }
    
                else //fall back to the code that uses logged in user's window identity and not impersonation. This code "strangely" worked from the web application installed on my Windows XP PC that was not part of the domain but was on VPN connection and yet saved appointments on the company's exchange server. I guess, the VPN connection compensates for all the mumbo-jumbo round about impersonation code in the impersonateValidUser method. Hack, this code worked even I had not configured the impersonation on the exchange server as they tell you to do at this link:  http://msdn.microsoft.com/en-us/library/office/bb204095(v=exchg.140).aspx
                {
                    service.Credentials = null;
                    service.ImpersonatedUserId = null;
                    service.UseDefaultCredentials = true;
    
                    HttpContext.Current.Trace.Write("Before Impersonation: System.Security.Principal.WindowsIdentity.GetCurrent().Name = " + System.Security.Principal.WindowsIdentity.GetCurrent().Name);
    
                    //this is not impersonation. It uses the logged in user's window identity. The window identity does not have to be that of the company domain. The windows identity of Local PC that is not part of the domain will also work
                    System.Security.Principal.WindowsImpersonationContext impersonationContext;
                    impersonationContext = ((System.Security.Principal.WindowsIdentity)HttpContext.Current.User.Identity).Impersonate();// //System.Threading.Thread.CurrentPrincipal.Identity
    
                    HttpContext.Current.Trace.Write("Before Saving Appointment. System.Security.Principal.WindowsIdentity.GetCurrent().Name = " + System.Security.Principal.WindowsIdentity.GetCurrent().Name);
    
                    apt.Save(SendInvitationsMode.SendToNone);
    
                    impersonationContext.Undo();
    
                }
    
            }
    
            //impersonation methods. Credit: Impersonate a Specific User in Code: http://support.microsoft.com/kb/306158#4
            private bool impersonateValidUser(String userName, String domain, String password)
            {
                WindowsIdentity tempWindowsIdentity;
                IntPtr token = IntPtr.Zero;
                IntPtr tokenDuplicate = IntPtr.Zero;
    
                if (RevertToSelf())
                {
                    if (LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE,
                        LOGON32_PROVIDER_DEFAULT, ref token) != 0)
                    {
                        if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                        {
                            tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                            impersonationContext = tempWindowsIdentity.Impersonate();
                            if (impersonationContext != null)
                            {
                                CloseHandle(token);
                                CloseHandle(tokenDuplicate);
                                return true;
                            }
                        }
                    }
                }
                if (token != IntPtr.Zero)
                    CloseHandle(token);
                if (tokenDuplicate != IntPtr.Zero)
                    CloseHandle(tokenDuplicate);
                return false;
            }
    
            private void undoImpersonation()
            {
                impersonationContext.Undo();
            }
    
        }
    }
    

     
    • Marked as answer by aamirghanchi Thursday, January 30, 2014 5:50 PM
    Thursday, January 30, 2014 5:50 PM