1

I have a WCF service, which is hosted inside of an ASP.NET MVC application (as described in http://msdn.microsoft.com/en-us/library/aa702682.aspx). Part of the MVC actions and WCF service operations are protected, and I use ASP.NET Forms Authentication for both:

// protected MVC action
[Authorize]
public ActionResult ProtectedMvcAction(string args)

// protected WCF operation
[PrincipalPermission(SecurityAction.Demand, Role = "User")]
public void ProtectedWcfOperation(string args)

My WCF client makes sure that the Forms Authentication .ASPXAUTH cookie gets transmitted to the server on every WCF call.

This worked very well for a long time. Now I'm adding HTTPS encryption to my server using an SSL certificate. This required me to make the following changes to the Web.config`:

<basicHttpBinding>
  <binding name="ApiServiceBinding">
    <security mode="Transport">
      <transport clientCredentialType="None" />
    </security>
  </binding>
</basicHttpBinding>

The service gets activated and the client can invoke the server operations. However, the [PrincipalPermission] attribute in front of the protected server operations suddenly blocks all service calls. I found out the following:

  • In the HTTP case (without <security mode="Transport">), both Thread.CurrentPrincipal and HttpContext.Current.User are set to a RolePrincipal instance, with a FormsIdentity instance in the RolePrincipal.Identity property. In this case, everything works fine.
  • In the HTTPS case (with <security mode="Transport"> in the web.config), the property HttpContext.Current.User is still set to the RolePrincipal/FormsIdentity combination. But, the property Thread.CurrentPrincipal is suddenly set to WindowsPrincipal/WindowsIdentity instances, which makes the [PrincipalPermission] attribute throw an exception.

I tried the following:

  • Changed the AppDomain.CurrentDomain.SetPrincipalPolicy to every possible value (in Global.asax's Application_Start), but that did not change anything.
  • Set the property Thread.CurrentPrincipal in Application_PostAuthenticate, but between Application_PostAuthenticate and the actual service invoke, the Thread's CurrentPrincipal is changed to a WindowsPrincipal again.

Any hints? What am I doing wrong?

Jonas Sourlier
  • 13,684
  • 16
  • 77
  • 148

2 Answers2

3

This solves it:

http://www.codeproject.com/Articles/304877/WCF-REST-4-0-Authorization-with-From-Based-authent

I mofied this code to cover Windows and Forms endpoints and the same service - which also works -

public bool Evaluate( EvaluationContext evaluationContext, ref object state )
{
    bool ret = false;
    // get the authenticated client identity
    HttpCookie formsAuth = HttpContext.Current.Request.Cookies[ ".MyFormsCookie" ];
    if( null != formsAuth )
    {
        FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt( formsAuth.Value );
        if( null != ticket )
        {
            GenericIdentity client = new GenericIdentity( ticket.Name, "Forms" );

            // set the custom principal
            CustomPrincipal p = new CustomPrincipal( client );
            p.RoleManagerProvider = "Internet";
            evaluationContext.Properties[ "Principal" ] = p;

            ret = true;
        }
    }
    else
    {
        CustomPrincipal p = new CustomPrincipal( HttpContext.Current.User.Identity );
        p.RoleManagerProvider = "Intranet";
        evaluationContext.Properties[ "Principal" ] = p;

        // assume windows auth
        ret = true;

    }
    return ret;
}

Which looks for the forms auth cookie and tries to use windows authentication if its not there. I also "flip" the role provider for internal and external

This allows me to propogate the users credentials from an internet (by forwarding the cookie) and intranet (using windows constrained delegation) to the same internal service.

I did the config in config (rather than code as per sample) and it seems fine.

For the behaviour its something like:

 <behavior name="FormsPaymentsBehavior">
          <serviceAuthorization principalPermissionMode="Custom" >
            <authorizationPolicies>
              <add policyType="FormsPolicy.AuthorizationPolicy,FormsPolicy" />
            </authorizationPolicies> 
          </serviceAuthorization>

This is used for both endpoints as the FormsPolicy (above) handle both and you cannot specify different behaviours for different endpoints.

The bindings enforce the windows credentials handshake on the appropriate endpoint:

<basicHttpBinding>
        <binding name="WindowsHttpBinding">
          <security mode="TransportCredentialOnly">
            <transport clientCredentialType="Windows" />
          </security>
        </binding>
        <binding name="FormsHttpBinding" allowCookies="true">          
          <security mode="None">
            <transport clientCredentialType="None" />            
          </security>
        </binding>
      </basicHttpBinding>

The transport mode can be changed to

<security mode="Transport">
            <transport clientCredentialType="None" />
          </security>

For https and it works fine.

For your custom principal I found I had to explicitly call the role manager

...


public bool IsInRole( string role )
        {
            RoleProvider p = Roles.Providers[ RoleManagerProvider ];
            return p.IsUserInRole( Identity.Name, role );
        }

        public String RoleManagerProvider { get; set; }

...

This is, I guess, because I was no longer using any of the aspnet compat stuff. Since I am flipping role manager depending on my authentication type then ho-hum.

John Landheer
  • 3,999
  • 4
  • 29
  • 53
stuartm9999
  • 139
  • 1
  • 8
  • Thanks this solved my intermittent WCF "Access is Denied" error caused by setting the thread principal to the HttpContext.Current.User in the WCF service constructor. – Dale Oct 02 '12 at 04:00
1

I have experienced this issue too and there is another report (and mine) here. http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/8f424d4f-2f47-4f85-a6b0-00f7e58871f1/

This thread points to the correct solution to be to create a custom Authorisation Policy (http://msdn.microsoft.com/en-us/library/ms729794.aspx) and this code project article (http://www.codeproject.com/Articles/304877/WCF-REST-4-0-Authorization-with-From-Based-authent) seems to explain exactly how to do this for FormsAuth - setting the evaluationContext.Properties["Principal"] = new CustomPrincipal(client) as per MS comments.

I have not yet implemented this - my "quick fix" was to simply revert to a plain old asmx service - but I will be "giving it a go" some time!

If you find another solution - please let me know.

stuartm9999
  • 139
  • 1
  • 8
  • thank you, I'm gonna check your solution when I have some time. My own "quick fix" was to drop the `PrincipalPermissionAttribute` and do the security checks 'manually' on `HttpContext.Curren.User` – Jonas Sourlier Jul 23 '12 at 08:43