none
Custom UserNamePasswordValidator.Validate never hit in Visual Studio RRS feed

  • Question

  • Hi,


    I'm trying to use a custom UserNamePasswordValidator to validate credentials when using a WCF service. However, the Validate method is never called in Visual Studio 2017 (.net framework 4.5.2).

    Here is my custom UserNamePasswordValidator class :

    public class CustomUserNameValidator : UserNamePasswordValidator
        {
            public CustomUserNameValidator()
            {
    
            }
    
            public override void Validate(string userName, string password)
            {
                ILoginDomainProvider loginDomainProvider = NinjectWebCommon.Kernel.Get<ILoginDomainProvider>();
                if (loginDomainProvider.IsLoginValid(new Login(userName, password)))
                    return;
                throw new SecurityTokenException("Account is invalid");
            } 
        }

    The LoginDomainProvider class just checks the presence of the username password pair in a MySql database.

    My web.config :

    <system.diagnostics>
        <sources>
          <source name="System.ServiceModel"
                switchValue="Information, ActivityTracing"
                propagateActivity="true">
            <listeners>
              <add name="traceListener"
                type="System.Diagnostics.XmlWriterTraceListener"
                initializeData= "c:\temp\log\Traces.svclog" />
            </listeners>
          
          </source>
        </sources>
      </system.diagnostics>

    <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="ServiceBehaviorUsernameValidator"> <dataContractSerializer maxItemsInObjectGraph="6553500"/> <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="true" /> <serviceCredentials> <!-- <clientCertificate> <authentication certificateValidationMode="None"/> </clientCertificate> --> <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Braille.WcfHost.CustomUserNameValidator,Braille.WcfHost"/> <serviceCertificate findValue="Braille.WcfHost" x509FindType="FindBySubjectName" storeName="My" storeLocation="CurrentUser" /> </serviceCredentials> <!-- <serviceAuthorization principalPermissionMode="UseAspNetRoles" roleProviderName="AspNetSqlRoleProvider" /> --> </behavior> <behavior> <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="true" /> </behavior> </serviceBehaviors> </behaviors> <protocolMapping> <add binding="basicHttpBinding" scheme="http" /> <add binding="basicHttpsBinding" scheme="https" /> </protocolMapping> <bindings> <wsHttpBinding> <binding name="WsHttpBindingConfig" maxBufferPoolSize="200000" maxReceivedMessageSize="200000" sendTimeout="00:01:00"> <readerQuotas maxStringContentLength="200000" /> <security mode="Message"> <message clientCredentialType="UserName"/> </security> </binding> </wsHttpBinding> </bindings> <services> <service name="Braille.Services.ExerciseService" behaviorConfiguration="ServiceBehaviorUsernameValidator"> <endpoint address="" binding="wsHttpBinding" bindingName="WsHttpBindingConfig" contract="Braille.Contracts.IExerciseService" /> </service> </services> <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" /> </system.serviceModel>

    My service :

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
        public class ExerciseService : IExerciseService
        {
            private readonly IDomainProviderFactory _domainProviderFactory;
    
            public ExerciseService(IDomainProviderFactory domainProviderFactory)
            {
                Contract.Requires(domainProviderFactory != null);
                _domainProviderFactory = domainProviderFactory;
            }
    
            #region IExerciseService
            public List<LocalizedLabel> GetLabels()
            {
                var exerciseDomainProvider = _domainProviderFactory.CreateExerciseDomainProvider();
                var labels = exerciseDomainProvider.GetLabels();
                return labels;
            }
    
            public string Test()
            {
                return "test";
            }
            #endregion
        }


    I've seen multiple posts related to custom UserNamePasswordValidator  being ignored, but they involved using security="Transport" in IIS, which is different from my case.

    When putting a breakpoint on the parameterless constructor of CustomUserNameValidator, it is actually hit when accessing the ExerciseService service. If I put invalid information (class name or assembly) in the<userNameAuthentication> section, an exception is thrown. So, it seems my class is somehow recognized. However, the breakpoint on the Validate method is never hit when calling methods from my service, and they return whatever results they are supposed to return, even when using WcfTestClient.exe and incorrect authentication data.

    I tried using different modes in the <security> section (my end goal for the security mode will be TransportWithMessageCredential) , replacing the code of my Validate method to just throwing a FaultException, or commenting the <serviceCertificate> section to try to shake things up, but nothing changes. My service methods still work with invalid authentication. I'm also unsure as to how (or if) the Traces.svclog can help me in this case.

    I'm at a loss here. I'm definitely not an expert when it comes to WCF security, so I'm probably missing something obvious, but I'd be grateful if someone could shed a bit of light in this case.

    Thanks!

    Kevin




    Thursday, August 31, 2017 10:00 AM

All replies

  • Hi Kevin,

    There is something wrong in your web.config.

    You should use “bindingConfiguration” for WsHttpBindingConfig instead of bindingName.

        <services>
          <service name="ExerciseService.Service1" behaviorConfiguration="ServiceBehaviorUsernameValidator">
            <endpoint address="" binding="wsHttpBinding"  bindingConfiguration="WsHttpBindingConfig" contract="ExerciseService.IService1" />
          </service>
        </services>
    

    Best Regards,

    Edward


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Friday, September 1, 2017 3:29 AM
  • Hi Edward,

    Thank you for your answer. Apparently, when I tried testing different things to make it work, I made a mistake when rewriting this attribute.

    Now that this is corrected, I'm getting a "The user name is not provided. Specify a user name in ClientCredentials" error when calling a method from my service in WcfTestClient.exe, which is at least in the right direction. However, the breakpoint on CustomUserNameValidator's Validate() method is still never called.

    Even when all code is removed from inside  the Validate() method, I'm still getting the above mentioned error when calling a method from my service. To my understanding, as long as an exception is not thrown inside Validate(), the user should be considered authenticated.

    Am I still missing something?

    Thank you!

    Kevin

    Friday, September 1, 2017 6:57 AM
  • Hi Kevin,

    >> as long as an exception is not thrown inside Validate(), the user should be considered authenticated

    Not all right. Before checking Validate method, the Security Mode from Binding is checked first. Since you specify UserName security, UserName and password is required and you need to provide them at client side.

    For checking Validate, you need to consume your service by generating client code and providing UserName and password. It could not be tested in wcftestclient.

    Since your previous issue related with wrong web.config has been resolved, I would suggest you mark the helpful reply as answer to close previous issue.

    If you have any issue related with Validate method, I would suggest you post a new thread and then we could focus on this specific issue.

    Best Regards,

    Edward


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    • Proposed as answer by Kevin B 02 Monday, September 4, 2017 7:26 AM
    Monday, September 4, 2017 3:06 AM
  • Hi Edward,

    >>Not all right. Before checking Validate method, the Security Mode from Binding is checked first. Since you specify UserName security, UserName and password is required and you need to provide them at client side.

    This actually managed to put me in the right track, and after a few trial and errors with my self-signed X509 certificate, I finally got to hit the breakpoint on Validate method and test authentication.

    Here is my proxy class on the client side :

    public class ExerciseProxy : ClientBase<IExerciseService>, IExerciseService
        {
            #region Members
            private readonly ICredentialsProvider<ClientCredentials> _credentialsProvider;
            #endregion
    
            #region CTors
            public ExerciseProxy(ICredentialsProvider<ClientCredentials> credentialsProvider)
            {
                Contract.Requires(credentialsProvider != null);
                _credentialsProvider = credentialsProvider;
            } 
            #endregion
    
            protected override IExerciseService CreateChannel()
            {
                ChannelFactory<IExerciseService> channelFactory = new ChannelFactory<IExerciseService>("ExerciseService_Endpoint");
                
                var defaultCredentials = channelFactory.Endpoint.Behaviors.Find<ClientCredentials>();
                if (defaultCredentials != null)
                    channelFactory.Endpoint.Behaviors.Remove(defaultCredentials);
    
                _credentialsProvider.SetCredentials("username", "password");
                channelFactory.Endpoint.Behaviors.Add(_credentialsProvider.Credentials);
                channelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;
                return channelFactory.CreateChannel();
            }
    
            #region IExerciseService
            public List<LocalizedLabel> GetLabels()
            {
                return Channel.GetLabels();
            }
    
            public string Test()
            {
                return Channel.Test();
            } 
            #endregion

    , my app_config on the client side :

    <system.serviceModel>
        <bindings>
          <wsHttpBinding>
            <binding name="WsHttpBinding_DefaultBinding">
              <security>
                <message clientCredentialType="UserName" />
              </security>
            </binding>
          </wsHttpBinding>
        </bindings>
        <client>
          <endpoint name="ExerciseService_Endpoint"
                    address="http://localhost:50077/ExerciseService.svc"
                    binding="wsHttpBinding"
                    bindingConfiguration="WsHttpBinding_DefaultBinding"
                    contract="Braille.Contracts.IExerciseService">
            <identity>
              <dns value="Braille.WcfHost"/>
            </identity>
          </endpoint>
        </client>
      </system.serviceModel>

    , and change the serviceCredentials section on the server's web config with the following :

    <serviceCredentials>
    
                <clientCertificate>
                  <authentication certificateValidationMode="None"  />
                </clientCertificate>
    
                <userNameAuthentication 
                  userNamePasswordValidationMode="Custom" 
                  customUserNamePasswordValidatorType="Braille.WcfHost.CustomUserNameValidator,Braille.WcfHost" />
                <serviceCertificate 
                  findValue="Braille.WcfHost" 
                  x509FindType="FindBySubjectName"
                  storeName="My"
                  storeLocation="LocalMachine" />
              </serviceCredentials>

    Thank you very much for your help!

    Kevin

    Monday, September 4, 2017 7:26 AM
  • Hi Kevin,

    I am glad your issue has been resolved, and you could suggest you click "Mark as answer" to close this thread.

    Best Regards,

    Edward


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Monday, September 4, 2017 7:43 AM