Yet on BizTalk Impersonation With WCF Adapters

Scenario

Last year I wrote a post on how using BizTalk Server 2006 R2/2009 and Protocol Transition to impersonate the original caller when invoking a downstream service that uses the Windows Integrated Security. Recently, one customer posed the following question to my colleague, Tim Wieman:

Can I create a WCF Send Port that is able to impersonate a statically defined domain user, other than then the service account of the host instance process, when calling a downstream WCF service that exposes a BasicHttpBinding/WsHttpBinding endpoint configured to use the Transport security mode ?

The answer is:

  • Yes, when using the Basic or the Digest authentication scheme.
  • No, when using the Windows or NTLM client credential type.

To verify this constraint, you can proceed as follows:

  • Open the BizTalk Administration Console.
  • Create a new WCF-BasicHttp or WCF-WsHttp Static Solicit-Response Send Port
  • Click the Configure button.
  • Select the Security tab.
  • Choose the Transport security mode.

At this point, if you select the Basic or Digest transport client credential type from the corresponding drop-down list:

  • You can click the Edit button in the User name credentials section, as highlighted in the picture below.

  EditButton

  • You can specify the Username and Password of the account that the Send Port will impersonate when invoking the target WCF service. As an alternative, you can leverage the Single Sign-On to redeem a ticket and pick a user at runtime from a certain affiliate application.

ClientCredentials

Instead, if you select the Ntlm or Windows transport client credential type from the corresponding drop-down list, the Edit button in the User name credentials section is greyed out, as shown in the picture below:

WCF-BasicHtpp

So at this point some of you might ask yourselves:

How can I impersonate a statically-defined user, different from the service account of the host process running my WCF Send Port, when invoking an underlying WCF service that uses the Transport security mode along with the Ntlm or Windows authentication scheme?

The answer is straightforward, you can achieve this objective using the WCF-Custom adapter and writing a custom WCF channel. Indeed, I didn’t create a new component from scratch, I just used grabbed some code from MSDN, and extended the component I wrote one year ago for my previous post on BizTalk and Protocol Transition .  In particular, I made the following changes:

  • I extended the following components:
    • InspectingBindingExtensionElement
    • InspectingBindingElement
    • InspectingChannelFactory
    • InspectingRequestChannel
  • to expose two additional properties:
    • WindowsUserName: gets or sets the domain account in the form of DOMAIN\Username that the Send Port will impersonate at runtime.
    • WindowsUserPassword: gets or sets the password of the domain account.
  • Then I extended the WindowsUserPositionEnum type and therefore the WindowsUserPosition property to include a new mode called Static. As a consequence, the custom channel at runtime will retrieve the client credentials in a different way depending on the value of the WindowsUserPosition property exposed by the InspectingBindingExtensionElement component:
    • Context: username will be read from the message context property identified by the ContextPropertyName and ContextPropertyNamespace properties exposed by the InspectingBindingExtensionElement. In this case, the environment must be properly configured to use Protocol Transition. See my previous post for more details.
    • Message: username will be read from the message using the XPath expression contained in the WindowsUserXPath property. Even in this case, the environment must be properly configured to use Protocol Transition. See my previous post for more details.
    • Static: the custom channel will use the client credentials contained in the WindowsUserName and WindowsUserPassword to impersonate the corresponding domain account before invoking the downstream WCF service. Note: this pattern requires the component to uses the client credentials to invoke the LogonUser function at runtime, but it does not require to configure the BizTalk environment for Protocol Transition.
  • I finally extended the InspectingRequestChannel and InspectingHelper classes to support the new Static mode. In particular, the custom channel at runtime performs the following steps to impersonate a given domain account declaratively defined in the WCF Send Port configuration:
    • It calls the LogonUser static method exposed by the InspectingHelper class which in turn invokes the LogonUser Windows function which returns a token handle.
    • The channel creates a new WindowsIdentity object using the constructor that accepts the user token returned by the previous call.
    • Then, it invokes the Impersonate method exposed by the WindowsIdentity object.
    • The channel invokes the underlying channel.
    • In the finally block, when the call is complete, the channel invokes the Undo method on the WindowsImpersonationContext object returned by the Impersonate method, an then it invokes the CloseHandle static method exposed by the InspectingHelper class which in turn invokes the CloseHandle Windows function.

For your convenience, I report below the new code for the InspectingRequestChannel and InspectingHelper classes (I purposely omitted parts for ease of reading):

InspectingHelper class

 #region Copyright//-------------------------------------------------// Author:  Paolo Salvatori// Email:   paolos@microsoft.com// History: 2008-09-17 Created//-------------------------------------------------#endregion#region Using Directivesusing System;using System.Diagnostics;using System.Configuration;using System.Runtime.InteropServices;using System.Security.Principal;using System.Security.Permissions;using System.ServiceModel;using System.ServiceModel.Channels;using System.ServiceModel.Configuration;using System.DirectoryServices.ActiveDirectory;using System.Xml;using System.IO;using System.Text;using Microsoft.BizTalk.XPath;#endregionnamespace Microsoft.BizTalk.CAT.Samples.ProtocolTransition.WCFExtensionLibrary{    /// <summary>    /// This class exposes the logic to impersonate another user using the Protocol Transition mechanism.    /// </summary>    public class InspectingHelper    {        #region DllImport        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]        public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,            int dwLogonType, int dwLogonProvider, ref IntPtr phToken);        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]        public extern static bool CloseHandle(IntPtr handle);        #endregion        #region Private Constants        ...        // The following constants are used when calling the LogonUser external function        private const int LOGON32_PROVIDER_DEFAULT = 0;        //This parameter causes LogonUser to create a primary token.        private const int LOGON32_LOGON_INTERACTIVE = 2;        #endregion        #region Private Static Fields        private static string domainFQDN = string.Empty;        #endregion        #region Public Static Constructor        static InspectingHelper()        {            try            {                Domain domain = Domain.GetComputerDomain();                if (domain != null)                {                    domainFQDN = domain.Name;                }            }            catch (Exception ex)            {                Debug.WriteLine(string.Format(MessageFormat, ex.Message));            }        }        #endregion        #region Static Public Methods        public static string GetUserPrincipalName(ref Message message,                                                   WindowsUserPositionEnum windowsUserPosition,                                                  string contextPropertyName,                                                  string contextPropertyNamespace,                                                  string windowsUserXPath,                                                  string windowsUserName,                                                  string windowsUserPassword,                                                  int maxBufferSize,                                                  bool traceEnabled,                                                  out string userName,                                                  out string domainName)        {            string windowsUser = null;            domainName = null;            userName = null;            try            {                switch (windowsUserPosition)                {                    case WindowsUserPositionEnum.Message:                        if (message != null &&                            !string.IsNullOrEmpty(windowsUserXPath))                        {                            MessageBuffer messageBuffer = message.CreateBufferedCopy(maxBufferSize);                            if (messageBuffer == null)                            {                                throw new ApplicationException(MessageBufferCannotBeNull);                            }                            Message clone = messageBuffer.CreateMessage();                            if (message == null)                            {                                throw new ApplicationException(CloneCannotBeNull);                            }                            message = messageBuffer.CreateMessage();                            if (message == null)                            {                                throw new ApplicationException(MessageCannotBeNull);                            }                            XmlDictionaryReader xmlDictionaryReader = clone.GetReaderAtBodyContents();                            if (xmlDictionaryReader == null)                            {                                throw new ApplicationException(XmlDictionaryReaderCannotBeNull);                            }                            XPathCollection xPathCollection = new XPathCollection();                            if (xPathCollection == null)                            {                                throw new ApplicationException(XPathCollectionCannotBeNull);                            }                            XPathReader xPathReader = new XPathReader(xmlDictionaryReader, xPathCollection);                            if (xPathReader == null)                            {                                throw new ApplicationException(XPathReaderCannotBeNull);                            }                            xPathCollection.Add(windowsUserXPath);                            bool ok = false;                            while (xPathReader.ReadUntilMatch())                            {                                if (xPathReader.Match(0) && !ok)                                {                                    windowsUser = xPathReader.ReadString();                                    ok = true;                                }                            }                        }                        break;                    case WindowsUserPositionEnum.Context:                        if (string.IsNullOrEmpty(contextPropertyName))                        {                            throw new ApplicationException(ContextPropertyNameCannotBeNull);                        }                        if (string.IsNullOrEmpty(contextPropertyNamespace))                        {                            throw new ApplicationException(ContextPropertyNamespaceCannotBeNull);                        }                        string contextPropertyKey = string.Format(ContextPropertyKeyFormat,                                                                   contextPropertyNamespace,                                                                   contextPropertyName);                        if (message.Properties.ContainsKey(contextPropertyKey))                        {                            windowsUser = message.Properties[contextPropertyKey] as string;                        }                        else                        {                            throw new ApplicationException(string.Format(NoContextPropertyFormat, contextPropertyKey));                        }                        break;                    case WindowsUserPositionEnum.Static:                        windowsUser = windowsUserName;                        break;                }                if (!string.IsNullOrEmpty(windowsUser))                {                    string[] parts = windowsUser.Split(new char[] { Path.DirectorySeparatorChar });                    if (parts != null &&                        parts.Length > 1)                    {                        domainName = parts[0];                        userName = parts[1];                        Debug.WriteLineIf(traceEnabled, string.Format(CreatingUPNFormat, windowsUser));                        string upn = string.Format(UserPrincipalNameFormat, parts[1], domainFQDN);                        Debug.WriteLineIf(traceEnabled, string.Format(UsingUserPrincipalNameFormat, upn));                        return upn;                    }                }            }            catch (Exception ex)            {                Debug.WriteLineIf(traceEnabled, string.Format(MessageFormat, ex.Message));                throw ex;            }            return null;        }        public static bool LogonUser(string userName,                                      string domainName,                                      string windowsUserPassword,                                     bool traceEnabled,                                     ref IntPtr tokenHandle)        {            Debug.WriteLineIf(traceEnabled, string.Format(StartLogonUserFormat,                                                           domainName ?? Unknown,                                                           userName ?? Unknown));            // Call LogonUser to obtain a handle to an access token.            bool ok = LogonUser(userName,                                 domainName,                                 windowsUserPassword,                                LOGON32_LOGON_INTERACTIVE,                                 LOGON32_PROVIDER_DEFAULT,                                ref tokenHandle);            if (traceEnabled)            {                if (ok)                {                    Debug.WriteLineIf(traceEnabled, string.Format(LogonUserSucceededFormat,                                                                   domainName ?? Unknown,                                                                   userName ?? Unknown));                }                else                {                    Debug.WriteLineIf(traceEnabled, string.Format(LogonUserFailedFormat,                                                                   domainName ?? Unknown,                                                                   userName ?? Unknown));                }            }            return ok;        }        public static bool CloseHandle(IntPtr tokenHandle,                                       string userName,                                       string domainName,                                       bool traceEnabled)        {            Debug.WriteLineIf(traceEnabled, string.Format(StartCloseTokenFormat,                                                           domainName ?? Unknown,                                                           userName ?? Unknown));            bool ok = CloseHandle(tokenHandle);            if (traceEnabled)            {                if (ok)                {                    Debug.WriteLineIf(traceEnabled, string.Format(CloseTokenSucceededFormat,                                                                   domainName ?? Unknown,                                                                   userName ?? Unknown));                }                else                {                    Debug.WriteLineIf(traceEnabled, string.Format(CloseTokenFailedFormat,                                                                   domainName ?? Unknown,                                                                   userName ?? Unknown));                }            }            return ok;        }        #endregion    }}

InspectingRequestChannel class

 public class InspectingRequestChannel : InspectingChannelBase<IRequestChannel>,                                        IRequestChannel{    ...    public Message Request(Message message, TimeSpan timeout)    {        Message reply = null;        string upn = null;        WindowsImpersonationContext impersonationContext = null;        IntPtr tokenHandle = new IntPtr(0);        string userName = null;        string domainName = null;        try        {            if (componentEnabled)            {                WindowsIdentity identity = null;                upn = InspectingHelper.GetUserPrincipalName(ref message,                                                                windowsUserPosition,                                                                contextPropertyName,                                                                contextPropertyNamespace,                                                                windowsUserXPath,                                                                windowsUserName,                                                                windowsUserPassword,                                                                maxBufferSize,                                                                traceEnabled,                                                                out userName,                                                                out domainName);                if (windowsUserPosition == WindowsUserPositionEnum.Static)                {                    // Call LogonUser to obtain a handle to an access token.                    bool returnValue = InspectingHelper.LogonUser(userName,                                                                   domainName,                                                                   windowsUserPassword,                                                                  traceEnabled,                                                                  ref tokenHandle);                    // Protocol Transition is not necessary in this case                    identity = new WindowsIdentity(tokenHandle);                }                else                {                    if (!string.IsNullOrEmpty(upn))                    {                        // Protocol Transition must be properly configured,                        // otherwise the impersonation will fail                        identity = new WindowsIdentity(upn);                    }                }                Debug.WriteLineIf(traceEnabled, string.Format(ImpersonatingFormat, upn));                impersonationContext = identity.Impersonate();                Debug.WriteLineIf(traceEnabled, string.Format(ImpersonatedFormat, upn));            }            Debug.WriteLineIf(traceEnabled, CallingWebService);            reply = this.InnerChannel.Request(message);            Debug.WriteLineIf(traceEnabled, WebServiceCalled);        }        catch (Exception ex)        {            Debug.WriteLineIf(traceEnabled, string.Format(MessageFormat, ex.Message));            throw ex;        }        finally        {            if (impersonationContext != null)            {                impersonationContext.Undo();                Debug.WriteLineIf(traceEnabled, string.Format(ImpersonationUndoneFormat, upn ?? Unknown));            }            if (tokenHandle != IntPtr.Zero)            {                InspectingHelper.CloseHandle(tokenHandle,                                             userName,                                             domainName,                                             traceEnabled);            }        }        return reply;    }}

Test Case

To test my component, I created the following test case:

UseCase

WinForm driver application submits a new request to a WCF-NetTcp Request-Response Receive Location

  1. The Message Agent submits the incoming request message to the MessageBox (BizTalkMsgBoxDb).
  2. The inbound request is consumed by a Solicit Response WCF-Custom Send Port. This latter uses a Filter Expression to receive all the documents published by the Receive Port hosting the WCF Receive Location.
  3. The inbound message is mapped to the request format by the downstream HelloWorldService web service. This latter is hosted by IIS and exposes a single WsHttpBinding endpoint.
  4. The WCF-Custom Send Port impersonates the user statically defined in the Port configuration and invokes the underlying HelloWorldService WCF service that in the scenario is hosted by a separate Application Pool (w3wp.exe) on the same IIS instance.
  5. The HelloWorldService WCF service returns a response message.
  6. The incoming response message is mapped to the format expected by the client application.
  7. The transformed response message is published to the MessageBox.
  8. The response message is retrieved by the Request-Response WCF Custom Receive Location which originally received the request call.
  9. The response message is returned to the client WinForm application.

The following picture shows the binding configuration of the WCF Send Port used to communicate with the HelloWorldService.

BindingConfig

Finally, the picture below reports the trace captured during a test run.

DebugView

Conclusions

As I explained in one of my recent posts, using WCF extensibility points allows you customize in-depth the default behavior of BizTalk WCF Adapters. In particular, the WCF-Custom Adapter provides the possibility to specify the customize the composition of the binding and hence of the channel stack that will be created and used at runtime to communicate with external applications.

In this article we have seen how to exploit this characteristic to workaround and bypass a constraint of WCF Adapters. As usual, I had just a few hours to write the code and write the article, so should you find an error or a problem in my component, please send me an email or leave a comment on my blog, thanks!

You can find the new version of the code here.

Comments

  • Anonymous
    April 15, 2010
    Thanks for this information, it will help allot for future work, this has given me an idea to expose promoted properties to the adapter if needed :) Cheers Romiko

  • Anonymous
    April 15, 2010
    The comment has been removed

  • Anonymous
    February 11, 2011
    Hi, First off,  this is amazing!  Question, is it possible for this concept to be use in conjunction with wcf custom sqlbinding???  I'm unsure whether or not I'm missing a configuration or something, but I haven't been able to get this to work.  Using the InspectingBehaviorExtensionElement, it appears that the adapter is using sql authentication instead of windows authentication as I'm receiving a transmission failure "Login failed for user…",see below.  Is there a configuration I can set that would use windows authentication?   Any help would be greatly appreciated. Jeff Microsoft.ServiceModel.Channels.Common.ConnectionException: Login failed for user ''. The user is not associated with a trusted SQL Server connection. ---> System.Data.SqlClient.SqlException: Login failed for user ''. The user is not associated with a trusted SQL Server connection.   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)   at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)   at System.Data.SqlClient.SqlInternalConnectionTds.CompleteLogin(Boolean enlistOK)   at System.Data.SqlClient.SqlInternalConnectionTds.AttemptOneLogin(ServerInfo serverInfo, String newPassword, Boolean ignoreSniOpenTimeout, Int64 timerExpire, SqlConnection owningObject)   at System.Data.SqlClient.SqlInternalConnectionTds.LoginNoFailover(Str

  • Anonymous
    February 13, 2011
    The comment has been removed

  • Anonymous
    February 13, 2011
    The comment has been removed

  • Anonymous
    February 13, 2011
    Hi Jeff, did you try to specify the full domain account name? I mean "DomainNameUsername"? If the answer is yes, you could try one of the following solutions: a) You could configure domain B to trust users configured on domain A. b) You could use NTLM pass-through authentication. AFAIR, the only thing you have to do in this case is creating the user account U on both domains, A and B, with the same name and password. At this point, you can simply use the user U defined on domain A to access the SQL Server located in the domain B using Windows authentication. See support.microsoft.com/.../102716 for more information on NTLM pass-through authentication.

  • Anonymous
    February 14, 2011
    Paolo, Yes I'm specifying the domain. So I built an app to test the cross domain impersonation.   Using the logon type LOGON32_LOGON_NEW_CREDENTIALS tests were successful as the app was able to reach across domains and return data.  This is the case for all resources, file, service, database, and etc.  At this point I'm thinking that accounts are properly configured…  What I'm not quite sure about is what the sqlAdapter is doing behind the scenes…  as it appears to be doing something totally different when going after data on a different domain versus going after data on the same domain… wow, this is blowing my mind.   Thoughts?

  • Anonymous
    February 14, 2011
    Mmm, the only suggestion I can give you at this point is to use Reflector to disassemble and examine the code of the SqlAdapter (Microsoft.Adapter.Sql.dll). Unfortunately, I can't do it in first person because I'm very busy at the moment, sorry about that! :-( Using Reflector Pro, you could even debug through the SQL Adapter code, even if you don't have the source code or pdb files!

  • Anonymous
    February 14, 2011
    Hi, Thanks a lot for your assistance.  It's greatly appreciated!  I'll take a look at Reflector, and maybe it'll lead to a resolution.  I'll let you know my findings. Jeff

  • Anonymous
    August 24, 2011
    Hi, Thank you for the great solution – It was exactly what I needed. I use it to implement impersonation of statically-defined user in BizTalk send port when send a request to SharePoint hosted service with NTLM authentication. Just a quick question – Is it possible to hide the password in the binding, It is not acceptable to store it in plain text? Or is it possible to get credentials from SSO application which for me is the better solution? Sonya

  • Anonymous
    November 29, 2011
    Hi Paolo! Thanks a lot for this post/information. It's very helpfull. I was trying to resolve a similar scenario (a WCF-basicHttp receive location in BizTalk that needs to "translate" credentials to a backend's web service being called via the WCF-custom send adapter). For testing purposes I used a similar scenario as you did (I published a Web Service in the same IIS as BizTalk simulating the backend logic). I used the "Context" mode of your binding (so, the BTS.WindowsUser variable was used) and all works fine! But when I used the backend's web service a problem appears. According to your Debugview debug information: "The HTTP request is unauthorized with client authentication.........". If instead of using the "Context" mode I used the "Static" mode the call to the backend's web service works fine. Could you help me with this issue please? Regards, Diego P.D.: The called web service is not hosted in an IIS. It's hosted in a UNIX.

  • Anonymous
    November 29, 2011
    Hi Diego, is the backend service using the Windows Integrated Security despite the fact it runs in a UNIX system? When using the Context mode, did you properly set the ContextPropertyName and ContextPropertyNamespace to refer to the WindowsUser or any other context property? I assume the answer is yes, but just in case. ;) Can you stop the Send Port to suspend the message and check that the WindowsUser context property has the expected value? Are you running the WCF receive location in a trusted host? Did you configure the WCF receive location to use the Windows Integrated Security? ;) I know, lots of questions, but I need to have the full picture to provide an answer. In any case I strongly suggest you to attach  the BizTalk process running the send port and debug through my custom channel to see what's going on and above all to read the value of variables at runtime! Ciao, Paolo

  • Anonymous
    November 30, 2011
    Hi Paolo, Thank you very much for your answer! Most of your questions are YES.    - ContextPropertyName and Namespace are correctly set to read the BTS.WindowsUser (the Debugview help me to confirm that your custom channel is obtaining the information OK).   - The BTS.WindowsUser is available (unenlisting the Send Port or using the Debugview confirm that the property is promoted correctly).   - The Receive Location is a WCF-basicHttp running on a trusted host and using TransportCredentialOnly (TransportType = Windows). IIS is configured to use Windows Integrated Security.  -  The backend Web Service only accepts Kerberos (could the problem be here?). What any information I could bring you in order to help me with this issue? Thanks!!!!!, Diego

  • Anonymous
    November 30, 2011
    Mmm... it seems that you did everything well. :) When using the Static mode instead of the Context mode, do you use the same Windows user? Did you configure Protocol Transition and Contrained delegation as explained in the articles referenced in the first post of the series? Ciao, Paolo

  • Anonymous
    December 21, 2011
    Hi again Paolo, I'm having no luck with this. I'm still having problems (KDC_ERR_WRONG_REALM and KDC_ERR_PREAUTH_FAILED Kerberos errors). Those errors made me think that some configuration was wrong (or missed) regarding the server (not a BizTalk problem). However, it seems that all the Active Directory stuffs (computer and IIS App Pool Domain User) are correct because I hosted in the BizTalk server's IIS a simple WCF service that calls the backend web service and all goes fine. I have doubts regarding some things explained in your article:

  • The userId used by the IIS App Pool need to be trusted for delegation in AD, right...?
  • What about the UserId associated to the Receive Location (SOAP or WCF) Isolated Trusted Host Instance. Could this user be the same as the IIS App Pool? Or it need to be a different one and also be configured in AD for trusted delegation?
  • What about the userId associated to the Send Port Host Instance. This Host Instance is not necessary to be trusted in BizTalk? No AD trusted for delegation UserId is necessary?
  • The MessageBox server need to be added in AD as trusted for delegation server? It's also need to create a SPN for it?
  • Active Directory configuration: the BizTalk server and the IIS App Pool UserId are configured as "Trust this computer/user for delegation for any service (Kerberos only)". Or it's need by BizTalk scenarios set other option? What about "Services to which this account can present delegated credentials"? Should I need to add the backend server there? During these last weeks I learn a lot of Kerberos Delegation..., but I couldn't make it work with BizTalk! Any help I'll really appreciate it. Regards, Diego
  • Anonymous
    December 22, 2011
    When I said "...because I hosted in the BizTalk server's IIS a simple WCF service that calls the backend web service and all goes fine." I wanted to say "...because I hosted in the BizTalk server's IIS a simple WCF service that impersonates the user and calls the backend web service with the original credentials, and all goes fine.". Regards, Diego

  • Anonymous
    January 08, 2012
    Hi Diego, sorry for the delayed answer, but as you can easily understand, I was on vacation during Xmas time. To answer your questions, the userId used by the IIS App Pool needs to trusted for delegation as well as the userId used by the trusted isolated host instance running the WCF and/or SOAP receive location. To be safe, all the domain accounts used by the BizTalk application should be trusted for delegation, include the service account of the host instance running the Send Port. I don't think I created a SPN for the SQL Server instance running the MessageBox, but only because everything was running on the same machine when I built the demo (BizTalk, SQL, AD), but creating a SPN for SQL Server it's not only a good practice, but in some cases a necessary step. Finally, AFAIR, I n my demo I used the "Trust this computer/user for delegation for any service (Kerberos only)" option. I hope this can help you to solve your issues, also because I'm not really a security guru. ;) Ciao, Paolo

  • Anonymous
    June 23, 2014
    HI Paulo, Thanks for the excellent blog. I am trying to implement it and getting an error as below, "System.ServiceModel.Security.MessageSecurityException: The HTTP request is unauthorized with client authentication scheme 'Ntlm'. The authentication header received from the server was 'Negotiate,NTLM'. ---> System.Net.WebException: The remote server returned an error: (401) Unauthorized. I have specified "authenticationscheme"=ntlm in "httptransport" binding element. I could see that the  impersonation is happening correctly from debugview. Any thoughts...did i miss something

  • Anonymous
    June 23, 2014
    The comment has been removed