Why Thread.CurrentPrincipal.Identity.IsAuthenticated is always false?

3.7k views Asked by At

I have this WCF service and I'm trying to apply authentication and authorization mechanism in it.
It's my first time to do this, what I have is this web.config serviceModel tag for the service:

  <system.serviceModel>
<services>
  <service name="RoleBasedServices.SecureServiceExternal" behaviorConfiguration="externalServiceBehavior">
    <endpoint contract="AuthService.IService1" binding="wsHttpBinding" bindingConfiguration="wsHttpUsername" />
  </service>
</services>
<bindings>
  <wsHttpBinding>
    <binding name="wsHttpUsername">
      <security mode="Message">
        <message clientCredentialType="UserName" negotiateServiceCredential="false" establishSecurityContext="false" />
      </security>
    </binding>
  </wsHttpBinding>
</bindings>
<behaviors>
  <serviceBehaviors>
    <behavior>
       <!--To avoid disclosing metadata information, set the values below to false before deployment--> 
      <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
       <!--To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information--> 
      <serviceDebug includeExceptionDetailInFaults="false"/>
    </behavior>
    <behavior name="externalServiceBehavior">
      <serviceAuthorization principalPermissionMode="UseAspNetRoles" />
      <serviceCredentials>
        <userNameAuthentication userNamePasswordValidationMode="MembershipProvider" />
        <serviceCertificate findValue="RPKey" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="My"/>
      </serviceCredentials>
    </behavior>
  </serviceBehaviors>
</behaviors>
<protocolMapping>
    <add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>    
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />

What I want to do is very simple, I don't know if I need all this tags I'm just trying. What I want to do is from the client side to add reference for the service and first call the MyLogin:

    AuthService.Service1Client s = new AuthService.Service1Client();
    s.Login();

Then call the other restricted method and let it be GetData:

s.GetData()  

At service side in Login method, and only for test purposes, I'm doing this:

public void Login()
{
    Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity("Bob"), new[] { "Admin" });
    FormsAuthentication.SetAuthCookie("BobUserName", false);
}

An the restricted method will be:

[PrincipalPermission(SecurityAction.Demand, Role = "Admin")]
public void GetData()
{
    return "Hello";
}

That all I have in service and client, what I'm missing? Every time ,in debug, I check Thread.CurrentPrincipal in Login method I found Thread.CurrentPrincipal.Identity.IsAuthenticated equals true but even though when the client calls the GetData() method it's Access Denied.
PS: I'm using console application to do my tests does it make any difference ?
Thanks

2

There are 2 answers

0
pdessev On

Here is a very nice article that could possibly lead to a solution.

The general idea is that you have 2 object for the Principal. HttpContext.Current.User and Thread.CurrentPrincipal. You are setting the Thread.CurrentPrincipal at the time HttpContext.Current.User is already instantiated and the role of it is left to default.
You may want to try something like:

HttpContext.Current.User = new GenericPrincipal(new GenericIdentity("Bob"), new[] { "Admin" });
0
MattSull On

The reason calls to GetData() are denied is because WCF doesn't know anything about the Forms Authentication cookie that was set during Login().

It doesn't make a difference that you're a using console app. You could try the following approach.

Set the cookie in Login():

var cookie = FormsAuthentication.GetAuthCookie(username, true);
var ticket = FormsAuthentication.Decrypt(cookie.Value);

HttpContext.Current.User = new GenericPrincipal(new FormsIdentity(ticket), null);
FormsAuthentication.SetAuthCookie(HttpContext.Current.User.Identity.Name, true);

Then in your console app:

public static void TestLoginAndGetData()
{
    var sharedCookie = string.Empty;

    using (var client = new YourClient())
    using (new OperationContextScope(client.InnerChannel))
    {
        client.Login("username", "password");

        // get the cookie from the response
        HttpResponseMessageProperty response = (HttpResponseMessageProperty)
            OperationContext.Current.IncomingMessageProperties[
            HttpResponseMessageProperty.Name];
        sharedCookie = response.Headers["Set-Cookie"];

        // add it to the request
        HttpRequestMessageProperty request = new HttpRequestMessageProperty();
        request.Headers["Cookie"] = sharedCookie;
        OperationContext.Current.OutgoingMessageProperties[
            HttpRequestMessageProperty.Name] = request;

        var result = client.GetData();

        Console.WriteLine(result);
    }
}

You might also consider changing the return type of GetData() to string.