Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread.CurrentPrincipal is not set in WCF service called using WebGet

I have a web site hosted in IIS that uses Windows Authentication and exposes WCF web services.

I configure this service with an endpoint behavior:

<serviceAuthorization principalPermissionMode ="UseAspNetRoles" 
                   roleProviderName="MyRoleProvider"/>

and a binding:

 <security mode="TransportCredentialOnly">
    <transport clientCredentialType="Ntlm" />
 </security>

When the service is called, Thread.CurrentPrincipal is set to a RolePrincipal with the client's Windows identity and roles provided by by configured provider.

All is well with the world.

Now I've added some additional WCF services that are consumed by REST-ful Ajax calls: Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory" in the svc file, WebGet attribute in the service contract, and the AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed) attribute on the service implementation.

I also add the following incantation to web.config as recommended in MSDN:

<system.serviceModel>
    ...
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
    ...
</system.serviceModel>

My Ajax service almost works the way I want it to. When it's called, HttpContext.Current.User is set to a RolePrincipal with the roles I expect. But Thread.CurrentPrincipal remains set to an unauthenticated GenericPrincipal.

So I need to add a line of code to each of my service methods:

Thread.CurrentPrincipal = HttpContext.Current.User

Is there any incantation in the configuration file I can use to get Thread.CurrentPrincipal to be set automagically, like it is for a normal SOAP service?

UPDATE Here's a blog from someone who had the same problem, and solved it by implementing custom behaviors. Surely there's a way to do this out of the box?

UPDATE 2 Coming back to add a bounty to this as it's bugging me again in a new project, using a WCF WebGet-enabled service on .NET 3.5.

I've experimented with a number of options, including setting principalPermissionMode="None", but nothing works. Here's what happens:

  • I navigate to a WebGet URL that calls my service: http://myserver/MyService.svc/...

  • I've put a breakpoint in Global.asax "Application_AuthorizeRequest". When this breakpoint is hit, both "HttpContext.Current.User" and "Thread.CurrentPrincipal" have been set to a "RolePrincipal" that uses my configured ASP.NET RoleProvider. This is the behavior I want.

  • I have a second breakpoint when my service's OperationContract method is called. When this breakpoint is hit, HttpContext.Current.User still references my RolePrincipal, but Thread.CurrentPrincipal has been changed to a GenericPrincipal. Aaargh.

I've seen suggestions to implement a custom IAuthorizationPolicy, and will look into that if I don't find a better solution, but why should I need to implement a custom policy to make use of existing ASP.NET authorization functionality? If I have principalPermissionMode = "UseAspNetRoles", surely WCF should know what I want?

like image 411
Joe Avatar asked Jan 27 '12 07:01

Joe


2 Answers

This is an interesting question. I don't have the same setup as you, so its difficult to test whether my recommendations will apply exactly to your use case, but I can share what has worked for us with similar projects.

How we keep Thread.CurrentPrincipal and HttpContext.Current.User in Sync

We wrote an HttpModule called "AuthenticationModule" which inherits from IHtppModule.

We then attached to the HttpApplication.AuthenticateRequest event which happens very early in request lifecycle.

In our AuthenticateRequest event handler, we implement our application specific requirements including setting Thread.CurrentPrincipal and if necessary also the current context user. In this way you only implement this code once for your entire application and if it changes (like if you implement a custom Principal IIDentity) you have only one place to change it. (Don't duplicate this code in every service method.)

public class AuthenticationModule : IHttpModule
{
    public void Dispose() { return; }

    public void Init(HttpApplication app)
    {
        app.AuthenticateRequest += new EventHandler(app_AuthenticateRequest);
    }

    void app_AuthenticateRequest(object sender, EventArgs e)
    {
        HttpApplication app = (HttpApplication)sender;

        // This is what you were asking for, but hey you 
        // could change this behavior easily.
        Thread.CurrentPrincipal = app.Context.User;
    }
}

Ours is actually a bit more complex as we implement a custom IIdentity, create an instance of GenericPrincipal and then assign it to both app.Context.User and Thread.CurrentPrincipal; but, the above is what you were asking for.

Don't forget to register your new HttpModule in your web.config!

For integrated app pools:

<system.webServer>
    <modules>
      <add name="AuthenticationModule" type="YourNameSpace.AuthenticationModule" preCondition="integratedMode" />
    </modules>
</system.webServer>

For old classic app pools you'd have to put it in <system.web><httpModules></httpModules></system.web>

You might need to play with what goes inside that AuthenticationRequest event handler and/or the order with which you register the handler. Because ours is totally custom it might be different than what you need. We actually grab the Forms Authentication cookie, decrypt it, etc... you might need to ping some built in methods for WindowsAuthentication.

I believe this is a more generic way to handle your application authentication stuff as it applies to all HttpRequests whether that be a page request, an IHttpHandler, some 3rd party component, etc... That will keep it consistent throughout your app.

like image 117
BenSwayne Avatar answered Sep 22 '22 21:09

BenSwayne


I'm not sure. Maybe this will help

<configuration>
  <system.web>
    <identity impersonate="true" />
  </system.web>
</configuration>

http://msdn.microsoft.com/en-us/library/134ec8tc(v=vs.80).aspx

like image 41
Jack Spektor Avatar answered Sep 20 '22 21:09

Jack Spektor