Search code examples
wcfwcf-security

WCF Security Using Windows Authentication


What is the correct WCF security implementation/configuration that allows:

  • Using existing Windows accounts to authenticate with the service
  • Allow adding of a Service Reference from another project without providing credentials
  • Limiting the users that can call the service

Solution

  • Using existing Windows accounts to authenticate with the service

    To do this, you should set the transport clientCredentialType attribute of the binding configuration to Windows.

    <bindings>
       <wsHttpBinding>
          <binding>
             <security mode="Message">
                <transport clientCredentialType="Windows" />
             </security>
          </binding>
       </wsHttpBinding>
    </bindings>
    

    Allow adding of a Service Reference from another project without providing credentials

    To do this, create a mex endpoint for your service endpoint.

    <services>
       <service name="Services.SampleService" behaviorConfiguration="wsDefaultBehavior">
          <endpoint address="mex"  binding="mexHttpBinding" contract="IMetadataExchange" />
       </service>
    </services>
    

    Limiting the users that can call the service

    This one is a little more involved. The way I found to secure a service on a per-user basis requires a custom authorization policy. The class that performs the authorization must implement the IAuthorizationPolicy interface. This is the complete code of my authorization class:

    namespace Services.SampleService.Authorization
    {
        /// <summary>
        /// Handles the default authorization for access to the service
        /// <para>Works in conjunction with the AuthorizedUsersDefault setting</para>
        /// </summary>
        public class DefaultAuthorization: IAuthorizationPolicy
        {
    
            string _Id;
    
            public DefaultAuthorization()
            {
                this._Id = Guid.NewGuid().ToString();
            }
    
            public bool Evaluate(EvaluationContext evaluationContext, ref object state)
            {
                bool isAuthorized = false;
                try
                {
                    //get the identity of the authenticated user
                    IIdentity userIdentity = ((IIdentity)((System.Collections.Generic.List<System.Security.Principal.IIdentity>)evaluationContext.Properties["Identities"])[0]);
                    //verify that the user is authorized to access the service
                    isAuthorized = Properties.Settings.Default.AuthorizedUsersDefault.Contains(userIdentity.Name, StringComparison.OrdinalIgnoreCase);
                    if (isAuthorized)
                    {
                        //add the authorized identity to the current context
                        GenericPrincipal principal = new GenericPrincipal(userIdentity, null);
                        evaluationContext.Properties["Principal"] = principal;
                    }
                }
                catch (Exception e)
                {
                    Logging.Log(Severity.Error, "There was an error authorizing a user", e);
                    isAuthorized = false;
                }
                return isAuthorized;
            }
    
            public ClaimSet Issuer
            {
                get { return ClaimSet.System; }
            }
    
            public string Id
            {
                get { return this._Id; }
            }
        }
    }
    

    The "magic" happens in the Evaluate method. In my case, the list of authorized users is maintained in a Properties.Settings variable (of type ArrayOfString) named AuthorizedUsersDefault. This way, I can maintain the user list without having to redeploy the entire project.

    And then, to use this authorization policy on a per-service basis, set the following in the ServiceBehaviors node:

    <behaviors>
       <serviceBehaviors>
          <behavior name="wsDefaultBehavior">
             <serviceAuthorization principalPermissionMode="Custom">
            <authorizationPolicies>
               <add policyType="Services.SampleService.Authorization.DefaultAuthorization, MyAssemblyName" />
            </authorizationPolicies>
         </serviceAuthorization>
          </behavior>
       </serviceBehaviors>
    </behaviors>