3

I have a duplex WCF service over netTcpBinding, the service uses subscription model where clients would subscribe to the service with a callback implementation using Subscribe/Unsubscribe methods.

The service has a custom IAuthorizationPolicy which authenticates users from the database and sets default thread principal to a custom principal in IAuthorizationPolicy.Evaluate() method.

The service is running as a service in IIS 7.5, under LOCALSYSTEM identity, Windows 7

First, here's how client (WinForms) authenticates users:

  1. Show logon form to ask for credentials
  2. Call another authentication service on a different endpoint to validate user credentials
  3. Set default thread principal using AppDomain.SetThreadPrincipal

The problem i'm facing is that in the callback threads, and only in the callback threads, Thread.CurrentPrincipal in the is reset to an anonymous GenericPrincipal object and my custom principal that was set after authentication is not propagated.


UPDATE: The principal is propagated as expected if i set securityMode and clientCredentialType to "None", obviously this have something to do with security configuration, i have tried lots of combination of configuration with no luck.


UPDATE: I have included a sample project reproducing the issue.

The solution is straight forward with everything configured and running it will take just few clicks and few minutes of your precious time, it contains 5 projects:

  1. PM.Services - The WCF service project
  2. PM.Contracts - a lib containing the service contract
  3. Client/Broadcaster - a console application broadcasts to other clients through the service.
  4. Client/Listener - a console application that subscribes to the service/listens to broadcasts (this is where the Thread.CurrentPrincipal is reset).
  5. Client/Shared - a lib shared between the two clients that contains the callback implementation and service creation code.

To configure the service on your machine (assuming IIS7 or later already enabled on machine), the solution root folder contains two batch files:

InstallService.bat: This batch will:

  • Enable the following windows features, and their parent features on your machine (if not already enabled): Windows Communication Foundation HTTP Activation and Windows Communication Foundation Non-HTTP Activation, to enable WCF and net.tcp.

  • Will create and add service certificate in TrustedPeople store.

  • Will create a website for the service that will listen on: 7359 (http) and 7357 (net.tcp)

  • Will create an ApplicationPool under LOCALSYSTEM identity (to avoid permission issues during the testing)

  • Will create a folder in c:\inetpub\WCFCallbackSample and copy service output to it.

UninstallService.bat: This batch will:

  • Remove the service certificate from TrustedPeople store.

  • Uninstall the service website.

  • Delete the website folder.

Running the sample solution:

  • Build the solution

  • Install the service using InstallService.bat.

  • Debug/Run the solution to run the two service consumers (Client\Broadcaster, Client\Listener)

  • Send messages from Broadcaster

  • In the listener you will notice that the callback thread identity (printed on the console) is different from the main thread's identity set by SetThreadPrincipal on application start.


Code from initial post:

Service Interface:

[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IFinanceServiceCallback))]
public interface IFinanceService : IServiceContract, ISupportCallback // <-- Interface that defines Subscribe and Unsubscribe methods
{
    [OperationContract]
    void DuplexMessageTest(string text);
}

Service implementation:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Single)] 
public class FinanceService : IFinanceService
{
    // Subscription boilerplate code
    private static readonly 
        DuplexServiceSubscriptionManager<IFinanceServiceCallback> _subscriptionManager = new DuplexServiceSubscriptionManager<IFinanceServiceCallback>();

    public bool Subscribe()
    {
        return _subscriptionManager.Subscribe();
    }

    public bool Unsubscribe()
    {
        return _subscriptionManager.Unsubscribe();
    }

    // A test method for simulation
    public void DuplexMessageTest(string text)
    {
        _subscriptionManager.InvokeCallbackMethod(callback => callback.OnDuplexTest(text));
    }

Callback implementation:

[CallbackBehavior(UseSynchronizationContext = false, ConcurrencyMode = ConcurrencyMode.Single,  AutomaticSessionShutdown=true)]
public class FinanceServiceCallbackSubscription :  BaseServiceSubscription<IFinanceService>, IFinanceServiceCallback
{
    public FinanceServiceCallbackSubscription() : base()
    {
    }

    public delegate void DuplexTestEventHandler(string message);

    public event DuplexTestEventHandler DuplexTest;

    public void OnDuplexTest(string message)
    {
        -->> At this point, the Thread.CurrentPrincipal is reset <<--
        if (DuplexTest != null)
            DuplexTest(message);
    }
}

UPDATE Service Configuration

<system.serviceModel>
    <services>
    ...
    <service behaviorConfiguration="PM.Behaviour_01" name="PM.Services.FinanceService">
        <endpoint address="" binding="netTcpBinding" bindingConfiguration="PMStandardBindingConfiguration"
            contract="PM.Contracts.IFinanceService" />
        <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
    </service>
    ...
    <services/>
    <bindings>
        ...
        <netTcpBinding>
            <binding name="PMStandardBindingConfiguration"
                    closeTimeout="00:01:00"
                    openTimeout="00:01:00"
                    receiveTimeout="00:10:00"
                    sendTimeout="00:10:00"
                    maxReceivedMessageSize="2147483647"
                    maxBufferPoolSize="0"
                    transferMode="Buffered"
                    portSharingEnabled="false">

              <readerQuotas maxDepth="2147483647"
                            maxStringContentLength="2147483647"
                            maxArrayLength="2147483647"
                            maxBytesPerRead="2147483647"
                            maxNameTableCharCount="16384" />
              <security mode="Message">
                <message clientCredentialType="UserName" />
              </security>
              <reliableSession ordered="true" inactivityTimeout="10675199.02:48:05.4775807" />
            </binding>
        </netTcpBinding>
        ...
    <bindings/>
    <behaviors>
        ...
        <behavior name="PM.Behaviour_01">
          <serviceMetadata httpGetEnabled="false" />
          <serviceCredentials>
            <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="PM.Services.Security.UserValidator, PM.Services" />
            <serviceCertificate findValue="PMSV" storeLocation="LocalMachine" storeName="TrustedPeople" x509FindType="FindBySubjectName" />
            <clientCertificate>
              <authentication certificateValidationMode="PeerTrust" />
            </clientCertificate>
          </serviceCredentials>

          <serviceAuthorization principalPermissionMode="Custom">
            <authorizationPolicies>
              <add policyType='PM.Services.Security.PMAuthorizationPolicy, PM.Services' />
            </authorizationPolicies>
          </serviceAuthorization>
          <ErrorHandler /> <!--Error handling behaviour-->
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
        ...
    <behaviors/>
<system.serviceModel/>

UPDATE Factory method i create the service with:

public static ServiceClient<TServiceContract> CreateService<TServiceContract>(object callbackInstance, string username, string password) 
    where TServiceContract: IServiceContract
{
    Binding binding = STANDARD_BINDING;
    bool supplyCredentials = true;
    Type callbackImplementationType = null;
    string protocol = "http";
    string addr = "";
    int port = PM_STANDARD_HTTP_PORT;

    if (BINDING_MAPPING.ContainsKey(typeof(TServiceContract)))
    {
        var args = BINDING_MAPPING[typeof(TServiceContract)];
        binding = (Binding)args[0];
        protocol = (string)args[1];
        object callbackType = args[2];
        if (callbackType != null)
            callbackImplementationType = (Type)callbackType;
        supplyCredentials = (bool)args[3];
    }

    // Convention: Service .svc file in url is the same as contract 
    // without the leading "I"
    // e.g. IFinanceService becomes FinanceService
    // so url becomes 
    // http://localhost/PMServices/FinanceService.svc

    string serviceName = typeof(TServiceContract).Name.Substring(1); // Remove leading I from interface name
    string baseUri = Settings.GetString(iSetting.ServiceBaseURI);

    UriBuilder b = new UriBuilder(baseUri);
    if (protocol == "net.tcp")
        port = PM_STANDARD_TCP_PORT;

    addr = string.Format("{0}://{1}:{2}/{3}/{4}.svc",
        protocol,
        b.Host,
        port,
        b.Path.Replace("/", ""),
        serviceName);

    EndpointIdentity identity = 
        EndpointIdentity.CreateDnsIdentity("PMSV");
    EndpointAddress endpointAddress =
        new EndpointAddress(new Uri(addr), identity);

    ChannelFactory<TServiceContract> channelFactory = null;

    // Check whether service is duplex
    if((binding.GetType() == typeof(NetTcpBinding) || binding.GetType() == typeof(WSDualHttpBinding))
        && (callbackImplementationType != null || callbackInstance != null))
    {
        object callbackImplementation = callbackInstance;
        if(callbackImplementation == null && callbackImplementationType != null)
            callbackImplementation = Activator.CreateInstance(callbackImplementationType, null);

        if (callbackImplementation != null)
            channelFactory = new DuplexChannelFactory<TServiceContract>(
                callbackImplementation,
                binding, 
                endpointAddress);
        else // Create non-duplex channel if no callback implementation is specified
            channelFactory = new ChannelFactory<TServiceContract>(binding,
                endpointAddress);
    }
    else
        channelFactory = new ChannelFactory<TServiceContract>(binding, 
            endpointAddress);

    if (supplyCredentials)
    {
        channelFactory.Credentials.UserName.UserName = 
            username ?? PMClientPrincipal.Current.User.Name;
        channelFactory.Credentials.UserName.Password = 
            password ?? ((PMClientIdentity)PMClientPrincipal
            .Current
            .User).Password;
    }

    return new ServiceClient<TServiceContract>(channelFactory,
        channelFactory.CreateChannel());
}

PMClientPrincipal is my implementation of IPrincipal, and PMClientPrincipal.Current is a static property that just casts the current thread principal to PMClientPrincipal.

UPDATE The class that manages client subscriptions (used in the service)

public class DuplexServiceSubscriptionManager<TCallbackImplementation>
    {
        public DuplexServiceSubscriptionManager()
        {
            _subscribers = new List<TCallbackImplementation>();
        }

        private object _syncRoot = new object();
        public List<TCallbackImplementation> _subscribers;

        private void AddClient(TCallbackImplementation callback)
        {
            lock (_syncRoot)
            {
                if (!_subscribers.Contains(callback))
                {
                    _subscribers.Add(callback);
                    ((ICommunicationObject)callback).Closed += OnClientLost;
                    ((ICommunicationObject)callback).Faulted += OnClientLost;
                }
            }
        }

        private void RemoveClient(TCallbackImplementation callback)
        {
            lock (_syncRoot)
            {
                if (_subscribers.Contains(callback))
                {
                    _subscribers.Remove(callback);
                    ((ICommunicationObject)callback).Closed -= OnClientLost;
                    ((ICommunicationObject)callback).Faulted -= OnClientLost;
                }
            }
        }

        void OnClientLost(object sender, EventArgs e)
        {
            ServiceLogger.Log.DebugFormat("Client lost, reason: {0}", ((ICommunicationObject)sender).State);
            if (OperationContext.Current == null)
                return;

            TCallbackImplementation callback = OperationContext.Current.GetCallbackChannel<TCallbackImplementation>();
            RemoveClient(callback);
        }

        public bool Subscribe()
        {
            try
            {
                TCallbackImplementation callback = OperationContext.Current.GetCallbackChannel<TCallbackImplementation>();
                AddClient(callback);
                ServiceLogger.Log.Debug("Client subscribed.");
                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool Unsubscribe()
        {
            try
            {
                TCallbackImplementation callback = OperationContext.Current.GetCallbackChannel<TCallbackImplementation>();
                RemoveClient(callback);
                ServiceLogger.Log.Debug("Client unsubscribed.");
                return true;
            }
            catch
            {
                return false;
            }
        }

        public void InvokeCallbackMethod(Action<TCallbackImplementation> target)
        {
            _subscribers.ForEach(delegate(TCallbackImplementation callback)
            {
                if (((ICommunicationObject)callback).State == CommunicationState.Opened)
                {
                    try
                    {
                        target.Invoke(callback);
                    }
                    catch (Exception)
                    {
                        Unsubscribe();
                    }
                }
                else
                {
                    Unsubscribe();
                }
            });
        }
    }

UPDATE Call stack to the callback method

System.ServiceModel.dll!System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(object instance, object[] inputs, out object[] outputs) Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(ref System.ServiceModel.Dispatcher.MessageRpc rpc)  Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(ref System.ServiceModel.Dispatcher.MessageRpc rpc)  Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage41(ref System.ServiceModel.Dispatcher.MessageRpc rpc) Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(ref System.ServiceModel.Dispatcher.MessageRpc rpc)  Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(ref System.ServiceModel.Dispatcher.MessageRpc rpc) Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(ref System.ServiceModel.Dispatcher.MessageRpc rpc)  Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(ref System.ServiceModel.Dispatcher.MessageRpc rpc)  Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(ref System.ServiceModel.Dispatcher.MessageRpc rpc) Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(ref System.ServiceModel.Dispatcher.MessageRpc rpc)  Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.MessageRpc.Process(bool isOperationContextSet)   Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.Dispatch(ref System.ServiceModel.Dispatcher.MessageRpc rpc, bool isOperationContextSet) Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ChannelHandler.DispatchAndReleasePump(System.ServiceModel.Channels.RequestContext request, bool cleanThread, System.ServiceModel.OperationContext currentOperationContext)   Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ChannelHandler.HandleRequest(System.ServiceModel.Channels.RequestContext request, System.ServiceModel.OperationContext currentOperationContext)  Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ChannelHandler.AsyncMessagePump(System.IAsyncResult result)  Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ChannelHandler.OnAsyncReceiveComplete(System.IAsyncResult result)    Unknown
System.ServiceModel.Internals.dll!System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(System.IAsyncResult result)  Unknown
System.ServiceModel.Internals.dll!System.Runtime.AsyncResult.Complete(bool completedSynchronously)  Unknown
System.ServiceModel.Internals.dll!System.Runtime.InputQueue<System.ServiceModel.Channels.Message>.AsyncQueueReader.Set(System.Runtime.InputQueue<System.ServiceModel.Channels.Message>.Item item)   Unknown
System.ServiceModel.Internals.dll!System.Runtime.InputQueue<System.ServiceModel.Channels.Message>.EnqueueAndDispatch(System.Runtime.InputQueue<System.ServiceModel.Channels.Message>.Item item, bool canDispatchOnThisThread)   Unknown
System.ServiceModel.Internals.dll!System.Runtime.InputQueue<System.ServiceModel.Channels.Message>.EnqueueAndDispatch(System.ServiceModel.Channels.Message item, System.Action dequeuedCallback, bool canDispatchOnThisThread)   Unknown
System.ServiceModel.Internals.dll!System.Runtime.InputQueue<System.__Canon>.EnqueueAndDispatch(System.__Canon item) Unknown
System.ServiceModel.dll!System.ServiceModel.Security.SecuritySessionClientSettings<System.ServiceModel.Channels.IDuplexSessionChannel>.ClientSecurityDuplexSessionChannel.CompleteReceive(System.IAsyncResult result)   Unknown
System.ServiceModel.dll!System.ServiceModel.Security.SecuritySessionClientSettings<System.ServiceModel.Channels.IDuplexSessionChannel>.ClientSecurityDuplexSessionChannel.OnReceive(System.IAsyncResult result) Unknown
System.ServiceModel.Internals.dll!System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(System.IAsyncResult result)  Unknown
System.ServiceModel.Internals.dll!System.Runtime.AsyncResult.Complete(bool completedSynchronously)  Unknown
System.ServiceModel.Internals.dll!System.Runtime.AsyncResult.Complete(bool completedSynchronously, System.Exception exception)  Unknown
System.ServiceModel.dll!System.ServiceModel.Security.SecuritySessionClientSettings<System.ServiceModel.Channels.IDuplexSessionChannel>.ClientSecuritySessionChannel.ReceiveAsyncResult.OnReceive(System.IAsyncResult result)    Unknown
System.ServiceModel.Internals.dll!System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(System.IAsyncResult result)  Unknown
System.ServiceModel.Internals.dll!System.Runtime.AsyncResult.Complete(bool completedSynchronously)  Unknown
System.ServiceModel.Internals.dll!System.Runtime.AsyncResult.Complete(bool completedSynchronously, System.Exception exception)  Unknown
System.ServiceModel.dll!System.ServiceModel.Channels.ReliableChannelBinder<System.ServiceModel.Channels.IDuplexSessionChannel>.InputAsyncResult<System.ServiceModel.Channels.ReliableChannelBinder<System.ServiceModel.Channels.IDuplexSessionChannel>>.OnInputComplete(System.IAsyncResult result) Unknown
System.ServiceModel.dll!System.ServiceModel.Channels.ReliableChannelBinder<System.ServiceModel.Channels.IDuplexSessionChannel>.InputAsyncResult<System.ServiceModel.Channels.ReliableChannelBinder<System.ServiceModel.Channels.IDuplexSessionChannel>>.OnInputCompleteStatic(System.IAsyncResult result)   Unknown
System.ServiceModel.Internals.dll!System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(System.IAsyncResult result)  Unknown
System.ServiceModel.Internals.dll!System.Runtime.AsyncResult.Complete(bool completedSynchronously)  Unknown
System.ServiceModel.Internals.dll!System.Runtime.AsyncResult.Complete(bool completedSynchronously, System.Exception exception)  Unknown
System.ServiceModel.dll!System.ServiceModel.Channels.TransportDuplexSessionChannel.TryReceiveAsyncResult.OnReceive(System.IAsyncResult result)  Unknown
System.ServiceModel.Internals.dll!System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(System.IAsyncResult result)  Unknown
System.ServiceModel.Internals.dll!System.Runtime.AsyncResult.Complete(bool completedSynchronously)  Unknown
System.ServiceModel.Internals.dll!System.Runtime.AsyncResult.Complete(bool completedSynchronously, System.Exception exception)  Unknown
System.ServiceModel.dll!System.ServiceModel.Channels.SynchronizedMessageSource.SynchronizedAsyncResult<System.ServiceModel.Channels.Message>.CompleteWithUnlock(bool synchronous, System.Exception exception)   Unknown
System.ServiceModel.dll!System.ServiceModel.Channels.SynchronizedMessageSource.ReceiveAsyncResult.OnReceiveComplete(object state)   Unknown
System.ServiceModel.dll!System.ServiceModel.Channels.SessionConnectionReader.OnAsyncReadComplete(object state)  Unknown
System.ServiceModel.dll!System.ServiceModel.Channels.SocketConnection.FinishRead()  Unknown
System.ServiceModel.dll!System.ServiceModel.Channels.SocketConnection.OnReceiveAsync(object sender, System.Net.Sockets.SocketAsyncEventArgs eventArgs)  Unknown
System.ServiceModel.dll!System.ServiceModel.Channels.SocketConnection.OnReceiveAsyncCompleted(object sender, System.Net.Sockets.SocketAsyncEventArgs e) Unknown
System.dll!System.Net.Sockets.SocketAsyncEventArgs.OnCompleted(System.Net.Sockets.SocketAsyncEventArgs e)   Unknown
System.dll!System.Net.Sockets.SocketAsyncEventArgs.FinishOperationSuccess(System.Net.Sockets.SocketError socketError, int bytesTransferred, System.Net.Sockets.SocketFlags flags)   Unknown
System.dll!System.Net.Sockets.SocketAsyncEventArgs.CompletionPortCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* nativeOverlapped)   Unknown
mscorlib.dll!System.Threading._IOCompletionCallback.PerformIOCompletionCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* pOVERLAP) Unknown
  • How is your service configured? – Erik Funkenbusch Oct 24 '14 at 16:19
  • @ErikFunkenbusch Updated question with service configuration – Mustapha Elmalah Oct 24 '14 at 16:47
  • Is your code running in IIS, an application or a service? What is the identity of the process it runs under? How is the service created? – Erik Funkenbusch Oct 24 '14 at 16:58
  • 1
    Oh, and FYI, your inactivity timeout doesn't do what you think it does... this is a widely misunderstood setting and works hand in hand with receiveTimeout. receiveTimeout controls the maximum time that no messages can be received before the connection is aborted. inactivityTimeout controls the time in which heartbeat messages are sent (half of the time specified) – Erik Funkenbusch Oct 24 '14 at 17:01
  • @ErikFunkenbusch I've included the factory method that creates the service. – Mustapha Elmalah Oct 24 '14 at 17:22
  • @ErikFunkenbusch Thanks for pointing out the receiveTimeout issue, i'm read of that earlier, and planning to re-configure the timeout values, also the BaseServiceSubscription which the service callback inherits from have logic to re-subscribe client upon connection drop. – Mustapha Elmalah Oct 24 '14 at 17:25
  • @ErikFunkenbusch and sorry for not including the required informations in the first place. – Mustapha Elmalah Oct 24 '14 at 17:40
  • @ErikFunkenbusch The service is running as a service in IIS 7.5, under LOCALSYSTEM identity, Windows 7 SP1 – Mustapha Elmalah Oct 24 '14 at 18:08
  • I don't see where you're calling SetThreadPrincipal either.... – Erik Funkenbusch Oct 24 '14 at 18:16
  • It's actually done at authentication, which is the first call done in the Main method. – Mustapha Elmalah Oct 24 '14 at 18:25
  • I have no problem with SetThreadPrincipal, and i can assure you that the principal is set before the connecting to the service, and even after connecting to the service from other threads either standard threads or pool threads. – Mustapha Elmalah Oct 24 '14 at 18:27
  • @ErikFunkenbusch and if i call SetThreadPrincipal from the callback thread it throws exception that the thread principal is already set. – Mustapha Elmalah Oct 24 '14 at 18:28
  • If, instead of calling Invoke, you call the callback directly, do you have the same problem? – Erik Funkenbusch Oct 24 '14 at 18:37
  • @ErikFunkenbusch Should i move this discussion to chat? – Mustapha Elmalah Oct 24 '14 at 18:38
  • @ErikFunkenbusch i tried your suggestion, called the callback directly from the service, same issue – Mustapha Elmalah Oct 24 '14 at 18:46
  • I'm pretty suspicious of that "subscription manager" and the fact that you're reusing the same instance. I believe the CurrentPrincipal is set by the Creator of the callback, not the caller of it. If you're using it over and over, and creation happens prior to a correct context being setup... – Erik Funkenbusch Oct 24 '14 at 18:54
  • @Erik By the "creator of the callback" you mean the client which created the object and passed it to the channel factory? I've included the server-side subscription manager code in the original question. – Mustapha Elmalah Oct 24 '14 at 18:58
  • @Erik If by "creator of the callback " you mean the subscription manager on the service side, although i'm not sure how it would affect the client thread identity, the correct principal is set right before the invocation of the callback. – Mustapha Elmalah Oct 24 '14 at 19:15
  • @Erik can you please elaborate you last comment? Thanks – Mustapha Elmalah Oct 25 '14 at 05:16
  • @ErikFunkenbusch I've included a sample project that will take just a minute to get it running, can you have a look at it? – Mustapha Elmalah Oct 27 '14 at 06:47

0 Answers0