Search code examples
wcfwcf-security

In WCF, custom Authentication, the Validate method, how to handle exception without stopping service when throwing fault exception?


I have a WCF Service that uses the custom authentication, inherit from UserNamePasswordValidator, And, I noticed that I have to throw Fault Exception for the authentication to work if user is not authenticate/invalid.

My problem is that I want the thrown exception only for client, not the server. Because, I don't want to stop the server.

I searched for answers, I found FaultContract, but that didn't work.

This is my code

 public override void Validate(string userName, string password)
    {
        if (HandleAuthentication(userName, password))
        {
            // Authenticated
            return;
        }
        else
        {
            throw new FaultException(
                "Invalid username or password.",
                new FaultCode("AUTHENTICATION_FAILURE"));
                
        }
        
    }

Solution

  • Here is my demo:

    Service.cs:

    using System;
    using System.IdentityModel.Selectors;
    using System.Security.Principal;
    using System.ServiceModel;
    
    namespace Microsoft.Samples.UserNamePasswordValidator
    {
        // Define a service contract.
        [ServiceContract(Namespace="http://Microsoft.Samples.UserNamePasswordValidator")]
        public interface ICalculator
        {
            [OperationContract]
            double Add(double n1, double n2);
            [OperationContract]
            double Subtract(double n1, double n2);
            [OperationContract]
            double Multiply(double n1, double n2);
            [OperationContract]
            double Divide(double n1, double n2);
        }
    
        // Service class which implements the service contract.
        // Added code to write output to the console window
        [ServiceBehavior(IncludeExceptionDetailInFaults=true)]
        
        public class CalculatorService : ICalculator
        {
            public double Add(double n1, double n2)
            {
                double result = n1 + n2;
                Console.WriteLine("Received Add({0},{1})", n1, n2);
                Console.WriteLine("Return: {0}", result);
                return result;
            }
    
    
            public double Subtract(double n1, double n2)
            {
                double result = n1 - n2;
                Console.WriteLine("Received Subtract({0},{1})", n1, n2);
                Console.WriteLine("Return: {0}", result);
                return result;
            }
    
    
            public double Multiply(double n1, double n2)
            {
                double result = n1 * n2;
                Console.WriteLine("Received Multiply({0},{1})", n1, n2);
                Console.WriteLine("Return: {0}", result);
                return result;
            }
    
            
            public double Divide(double n1, double n2)
            {
                double result = n1 / n2;
                Console.WriteLine("Received Divide({0},{1})", n1, n2);
                Console.WriteLine("Return: {0}", result);
                return result;
            }
    
            public class CustomUserNameValidator : System.IdentityModel.Selectors.UserNamePasswordValidator
            {
                // This method validates users. It allows in two users, test1 and test2 
                // with passwords 1tset and 2tset respectively.
                // This code is for illustration purposes only and 
                // MUST NOT be used in a production environment because it is NOT secure.   
                public override void Validate(string userName, string password)
                {
                    if (null == userName || null == password)
                    {
                        throw new ArgumentNullException();
                    }
    
                    if (!(userName == "test1" && password == "1tset") && !(userName == "test2" && password == "2tset"))
                    {
                        throw new FaultException("Unknown Username or Incorrect Password");
                    }
                }
            }
     
            // Host the service within this EXE console application.
            public static void Main()
            {
                // Create a ServiceHost for the CalculatorService type and provide the base address.
                using (ServiceHost serviceHost = new ServiceHost(typeof(CalculatorService)))
                {
                    // Open the ServiceHostBase to create listeners and start listening for messages.
                    serviceHost.Open();
                    
                    // The service can now be accessed.
                    Console.WriteLine("The service is ready.");
            Console.WriteLine("The service is running in the following account: {0}", WindowsIdentity.GetCurrent().Name);
                    Console.WriteLine("Press <ENTER> to terminate service.");
                    Console.WriteLine();
                    Console.ReadLine();
                }
            }
        }
    }
    

    App.config:

    <?xml version="1.0"?>
    <!--
       Copyright (c) Microsoft Corporation.  All rights reserved.
    -->
    <configuration>
    
      <system.serviceModel>
        <services>
          <service name="Microsoft.Samples.UserNamePasswordValidator.CalculatorService" behaviorConfiguration="CalculatorServiceBehavior">
            <host>
              <baseAddresses>
                <!-- configure base address for host -->
                <add baseAddress="http://localhost:8001/servicemodelsamples/service"/>
              </baseAddresses>
            </host>
            <!-- use base address provided by host, provide one endpoint -->
            <endpoint address="username" binding="wsHttpBinding" bindingConfiguration="Binding1" contract="Microsoft.Samples.UserNamePasswordValidator.ICalculator"/>
          </service>
        </services>
    
        <bindings>
          <wsHttpBinding>
            <!-- Username binding -->
            <binding name="Binding1">
              <security mode="Message">
                  <message clientCredentialType="UserName"/>
              </security>
            </binding>        
          </wsHttpBinding>
        </bindings>
    
        <behaviors>
          <serviceBehaviors>
            <behavior name="CalculatorServiceBehavior" includeExceptionDetailInFaults="True">
              <serviceCredentials>
                <!-- 
                The serviceCredentials behavior allows one to specify a custom validator for username/password combinations.                  
                -->
                <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Microsoft.Samples.UserNamePasswordValidator.CalculatorService+CustomUserNameValidator, service"/>
                <!-- 
                The serviceCredentials behavior allows one to define a service certificate.
                A service certificate is used by a client to authenticate the service and provide message protection.
                This configuration references the "localhost" certificate installed during the setup instructions.
                -->
                <serviceCertificate findValue="localhost" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName"/>
              </serviceCredentials>        
            </behavior>
          </serviceBehaviors>
        </behaviors>
        
      </system.serviceModel>
    
    <startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>
    

    Feel free to let me know if the problem persists.

    Here is a demo:

    enter image description here

    If you need this example, you can get it in the link below:

    https://www.microsoft.com/en-us/download/details.aspx?id=21459

    enter image description here