Apr 7 2008

WCF Security: Getting the password of the user

Category: .Net | My SoftwareRory Primrose @ 15:41

A common problem with service security is that username/password security is needed for authentication and authorization at the service boundary, but those same credentials are also required to consume other resources such as a database or underlying service. By default, username/password security will run the authentication and authorization of the credentials but only the username is available to the executing service code. This is typically made available through Thread.CurrentPrincipal.Identity.Name.

Storing username password credentials in a custom principal and identity against Thread.CurrentPrincipal is a really nice way of going. Thread.CurrentPrincipal returns IPrincipal which is a common framework type that will be available to all layers of a service executed by the thread. If Thread.CurrentPrincipal.Identity can return a custom IIdentity, then this is where the password can be made available. Using Thread.CurrentPrincipal frees up business and data access layers from relying on any security design that is tied up with a specific service implementation. The trick is how to get username password information into the thread that executes the service code.

One place that both the username and password is available is in the UserNamePasswordValidator.Validate() method. You could write a custom validator and hook it up in your service configuration, but this doesn't help you. There is no native facility to store the password from the validator. You can't store any credentials against Thread.CurrentPrincipal as the validator gets evaluated by WCF on a different thread than the thread that executes the service code. You could manually put it somewhere like in a static collection, but this would have potential security risks as your code will be handling the safety and security of multiple sets of credentials for users. This also means that somewhere else in the service implementation would require the credentials to be pulled out of the static and put into Thread.CurrentPrincipal. Using validators for this purpose is not the answer. Validators are for validating credentials, not storing them for later use.

Thankfully, there is a much more elegant way of passing these credentials around compared to the validator based solution. There is another place in WCF where there is access to both the username and the password and the security context of the service. Leveraging WCF extensibility is the answer.

Setting up the security context of the service is done using IAuthorizationPolicy implementations. The place in WCF where there is access to the username, password and IAuthorizationPolicy configuration is CustomUserNameSecurityTokenAuthenticator.ValidateUserNamePasswordCore. The ValidateUserNamePasswordCore method is passed the username and password parameters and returns a readonly collection of IAuthorizationPolicy objects. SecurityTokenAuthenticator, from which CustomUserNameSecurityTokenAuthenticator ultimately inherits from, is not configurable in WCF itself. Using Reflector to follow the calls against SecurityTokenAuthenticator, the place that is extensible in WCF such that a custom SecurityTokenAuthenticator can be used is ServiceCredentials. Creating a custom ServiceCredentials object, using custom objects between the ServiceCredentials and SecurityTokenAuthenticator call stack is the answer.

PasswordServiceCredentials

The custom ServiceCredentials class implementation returns a custom SecurityTokenManager if custom username password validation is enabled.

public class PasswordServiceCredentials : ServiceCredentials
{
    public PasswordServiceCredentials()
    {
    }

    private PasswordServiceCredentials(PasswordServiceCredentials clone)
        : base(clone)
    {
    }

    protected override ServiceCredentials CloneCore()
    {
        return new PasswordServiceCredentials(this);
    }

    public override SecurityTokenManager CreateSecurityTokenManager()
    {
        // Check if the current validation mode is for custom username password validation
        if (UserNameAuthentication.UserNamePasswordValidationMode == UserNamePasswordValidationMode.Custom)
        {
            return new PasswordSecurityTokenManager(this);
        }

        Trace.TraceWarning(Resources.CustomUserNamePasswordValidationNotEnabled);

        return base.CreateSecurityTokenManager();
    }
}

PasswordSecurityTokenManager

The custom SecurityTokenManager returns a custom SecurityTokenAuthenticator when it finds a SecurityTokenRequirement for username security. It also ensures that a default validator is available if one is not configured.

internal class PasswordSecurityTokenManager : ServiceCredentialsSecurityTokenManager
{
    public PasswordSecurityTokenManager(PasswordServiceCredentials credentials)
        : base(credentials)
    {
    }

    public override SecurityTokenAuthenticator CreateSecurityTokenAuthenticator(SecurityTokenRequirement tokenRequirement, out SecurityTokenResolver outOfBandTokenResolver)
    {
        if (tokenRequirement.TokenType == SecurityTokenTypes.UserName)
        {
            outOfBandTokenResolver = null;

            // Get the current validator
            UserNamePasswordValidator validator =
                ServiceCredentials.UserNameAuthentication.CustomUserNamePasswordValidator;

            // Ensure that a validator exists
            if (validator == null)
            {
                Trace.TraceWarning(Resources.NoCustomUserNamePasswordValidatorConfigured);
                validator = new DefaultPasswordValidator();
            }

            return new PasswordSecurityTokenAuthenticator(validator);
        }

        return base.CreateSecurityTokenAuthenticator(tokenRequirement, out outOfBandTokenResolver);
    }
}

PasswordSecurityTokenAuthenticator

The custom SecurityTokenAuthenticator is where the half of the magic happens. Here there is the opportunity to return custom a IAuthorizationPolicy implementation that will allow us to inject a custom IPrincipal and IIdentity on the thread the executes the service code.

internal class PasswordSecurityTokenAuthenticator : CustomUserNameSecurityTokenAuthenticator
{
    public PasswordSecurityTokenAuthenticator(UserNamePasswordValidator validator)
        : base(validator)
    {
    }

    protected override ReadOnlyCollection<IAuthorizationPolicy> ValidateUserNamePasswordCore(String userName, String password)
    {
        ReadOnlyCollection<IAuthorizationPolicy> currentPolicies = base.ValidateUserNamePasswordCore(
            userName, password);
        List<IAuthorizationPolicy> newPolicies = new List<IAuthorizationPolicy>(currentPolicies);

        newPolicies.Add(new PasswordAuthorizationPolicy(userName, password));

        return newPolicies.AsReadOnly();
    }
}

PasswordAuthorizationPolicy

The IAuthorizationPolicy implementation is where the other half of the magic happens. This policy gets passed the username and password so that it can store the password in the security context. The policy will return false until it finds a GenericIdentity in the evaluation context that has the same username as the one provided to the policy. It then creates a custom IIdentity that exposes both the username and password and stores it back into the collection of identities in the evaluation context and also stores it against the PrimaryIdentity property. PrimaryIdentity is then exposed through ServiceSecurityContext.PrimaryIdentity in the service implementation. A custom principal is then created (without roles) and stores it against the Principal property of the context. Principal is then injected into Thread.CurrentPrincipal by WCF (depending on configuration).

using System; 
using System.Collections.Generic; 
using System.IdentityModel.Claims; 
using System.IdentityModel.Policy; 
using System.Security.Principal; 
using System.ServiceModel; 
using System.Threading; 

namespace Neovolve.Framework.Communication.Security 
{ 
    internal class PasswordAuthorizationPolicy : IAuthorizationPolicy 
    { 
        public PasswordAuthorizationPolicy(String userName, String password) 
        { 
            const String UserNameParameterName = "userName"; 

            if (String.IsNullOrEmpty(userName)) 
            { 
                throw new ArgumentNullException(UserNameParameterName); 
            } 
  
            Id = Guid.NewGuid().ToString(); 
            Issuer = ClaimSet.System; 
            UserName = userName; 
            Password = password; 
        } 
  
        public bool Evaluate(EvaluationContext evaluationContext, ref object state) 
        { 
            const String IdentitiesKey = "Identities"; 
  
            // Check if the properties of the context has the identities list 
            if (evaluationContext.Properties.Count == 0 
                || evaluationContext.Properties.ContainsKey(IdentitiesKey) == false 
                || evaluationContext.Properties[IdentitiesKey] == null) 
            { 
                return false; 
            } 
  
            // Get the identities list 
            List<IIdentity> identities = evaluationContext.Properties[IdentitiesKey] as List<IIdentity>; 
  
            // Validate that the identities list is valid 
            if (identities == null) 
            { 
                return false; 
            } 
  
            // Get the current identity 
            IIdentity currentIdentity = 
                identities.Find( 
                    identityMatch => 
                    identityMatch is GenericIdentity 
                    && String.Equals(identityMatch.Name, UserName, StringComparison.OrdinalIgnoreCase)); 
  
            // Check if an identity was found 
            if (currentIdentity == null) 
            { 
                return false; 
            } 
  
            // Create new identity 
            PasswordIdentity newIdentity = new PasswordIdentity( 
                UserName, Password, currentIdentity.IsAuthenticated, currentIdentity.AuthenticationType); 
            const String PrimaryIdentityKey = "PrimaryIdentity"; 
  
            // Update the list and the context with the new identity 
            identities.Remove(currentIdentity); 
            identities.Add(newIdentity); 
            evaluationContext.Properties[PrimaryIdentityKey] = newIdentity; 
  
            // Create a new principal for this identity 
            PasswordPrincipal newPrincipal = new PasswordPrincipal(newIdentity, null); 
            const String PrincipalKey = "Principal"; 
  
            // Store the new principal in the context 
            evaluationContext.Properties[PrincipalKey] = newPrincipal; 
  
            // This policy has successfully been evaluated and doesn't need to be called again 
            return true; 
        } 

        public String Id 
        {
            get;
            private set;
        } 


        public ClaimSet Issuer 
        { 
            get; 
            private set; 
        } 
  
        private String Password 
        { 
            get; 
            set; 
        } 
  
        private String UserName 
        { 
            get; 
            set; 
        } 
    } 
} 

Configuration

As mentioned above, the custom ServiceCredentials needs to be configured so that the custom IAuthorizationPolicy is evaluated. Under serviceBehaviors, the serviceCredentials element allows a custom type to be defined. userNameAuthentication needs to be set to Custom, otherwise Windows authentication of the username password credentials will be used by default. Lastly, serviceAuthorization needs to set userNamePasswordValidationMode to custom. Without the validation mode being custom, the custom IPrincipal will not be assigned against Thread.CurrentPrincipal.

The other thing to note is that because username password credentials are being passed over to the wire to the service, transport security is required to protect the credentials. The security mode should be set to TransportWIthMessageCredentials with a message client credential type as UserName.

<?xml version="1.0" encoding="utf-8" ?> 
<configuration> 
  <system.web> 
    <compilation debug="true" /> 
  </system.web> 
  <system.serviceModel> 
    <bindings> 
      <netTcpBinding> 
        <binding name="netTcpBindingConfig"> 
          <security mode="TransportWithMessageCredential"> 
            <message clientCredentialType="UserName" /> 
          </security> 
        </binding> 
      </netTcpBinding> 
    </bindings> 
    <services> 
      <service behaviorConfiguration="Neovolve.Framework.Communication.SecurityTest.Service1Behavior" 
        name="Neovolve.Framework.Communication.SecurityHost.Service1"> 
        <endpoint address="net.tcp://localhost:8792/PasswordSecurityTest" 
          binding="netTcpBinding" bindingConfiguration="netTcpBindingConfig" 
          contract="Neovolve.Framework.Communication.SecurityHost.IService1" /> 
      </service> 
    </services> 
    <behaviors> 
      <serviceBehaviors> 
        <behavior name="Neovolve.Framework.Communication.SecurityTest.Service1Behavior"> 
          <serviceDebug includeExceptionDetailInFaults="true" /> 
          <serviceCredentials type="Neovolve.Framework.Communication.Security.PasswordServiceCredentials, Neovolve.Framework.Communication.Security"> 
            <serviceCertificate findValue="localhost" x509FindType="FindBySubjectName" /> 
            <userNameAuthentication userNamePasswordValidationMode="Custom" /> 
          </serviceCredentials> 
          <serviceAuthorization principalPermissionMode="Custom" /> 
        </behavior> 
      </serviceBehaviors> 
    </behaviors> 
  </system.serviceModel> 
</configuration> 

Download: Neovolve.Framework.Communication.Security.zip (78.91 kb)

Note: the unit test in the solution must be run in debug rather than normal unit test running because of the use of the WcfServiceHost.exe. With the configuration defined, an x509 certificate is required on the machine with localhost as the subject name.

Tags: , , ,

Comments

1.
Dave Dave says:

This is a very slick solution! I'd prefer not to send username and password to services, but when required, this will work great.

2.
trackback Rory Primrose says:

Trackback from Rory Primrose

WCF service contract design

3.
Mark Mark United Kingdom says:

Hi Rory,
Thsnks for this great post. It highlights an elegant solution to a difficult problem.
Cheers
Mark

4.
evereq evereq Israel says:

Exist solution (not so elegant) to extract username / password from any thread:

UserNameSecurityToken securityToken = OperationContext.Current.IncomingMessageProperties.Security.IncomingSupportingTokens[0].SecurityToken as System.IdentityModel.Tokens.UserNameSecurityToken;

string username = securityToken.UserName;
string password = securityToken.Password;

In any case - thanks for elegant solution!

5.
Wout Wout Netherlands says:

Ugh, that looks painful! Isn't it easier to change the interface and send user/password as parameters to each method? Won't win the beauty contest, but it's easy, and you can do all your work (including authentication) against a db in a single db connection.

Wout

6.
Rory Primrose Rory Primrose Australia says:

Hi Wout,

Yes that would be easier, but it couples your security model to your application desin. If you move from username/password to Kerberos or claims based credentials then you have a major refactoring task on your hands. The other issue is that you may want to use the same service design for internet and intranet implementations that run on different security models.

Ideally you want to decouple your security model from your application design as much as possible.

7.
Wout Wout Netherlands says:

Hi Rory,

Ah yes, I see your point, and I see the value of this orthogonal nature of the design. But, here comes a real world use case, which I'm struggling to fit into the mold. For a service action, first I want to authenticate the user, this is easy. Then the service does some modification to the database, and the modification needs to be linked to the user. This is a very common scenario, because often you want to do some kind of auditing of who has done what and when (who dunnit!?!?). So I need the user id, typically the database row id of the authenticated user. I could already have gotten this user id during authentication. And secondly, I now have to connect to the database twice instead of once, because I can't reuse the connection that was used during authentication. Today I explored other options, and it's also possible to stuff username/password into System.ServiceModel.OperationContext.Current. This might be a nice compromise, because on the client side you won't see much difference, the IService interface remains the same. On the service side, you'll have to authenticate explicitly on each method, which is ofcourse a disadvantage. But the amount of refactoring in case of changing authentication would not be huge. Instead of calling one utility method to check your OperationContext, you'd call another, so that would be acceptable to me (though yours would ofcourse leave less of a footprint on the service implementation).

Wout

8.
Rory Primrose Rory Primrose Australia says:

You can reference OperationContext, however I don't like my backend layers having a dependency on unrelated technologies (WCF in this case). What happens when the business or data access layer is used in a scenario where WCF isn't involved? Just running unit tests against the DL or DAL now becomes more complex because of this dependency.

This is the reason why this implementatino puts the credentials against the running thread because thread information is part of the CLR and available anywhere in .Net.

I understand your scenario. My prefered design is a trusted sub-system model. Keep in mind that I am not a security expert though.

In this model the database connection will be using integrated auth (Kerberos) of the service account and the procedures on the database will have the user name of the client as a parameter. This also allows you to lock down the database to just the service app pool account, but the procedures still knows who is related to the call.

9.
Wout Wout Netherlands says:

Hey, where did my previous comment go? Anyways, correct observation about the backend layers vs WCF dependency. My approach is somewhat less puristic. My service layer is quite thin, it would just tie together authentication/authorization and DAL calls (DAL layer is separate). So there would be more coupling, but in return I gain easy code (easy access to everything I need), easy debugging, better performance. If I ever change to something else than WCF, I'll just create another service implementation against the same IService interface.

Thanks for sharing your views, always good to see different points of view.

Wout

10.
Rory Rory Australia says:

Sorry Wout, I kept forgetting to release the comments from moderation. Thanks for your input. Smile

It's always a balancing act between good design, time, effort and money. I tend to be a purist probably much more than I should be.

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading