WCF: Cannot find my custom validator specified in web.config - customUserNamePasswordValidatorType - - Could not load file or assembly ... - help?
Asked Answered
L

4

5

So I've basically got everything up and running with wsHttpBindings and my WCF service using custom authentication over HTTPS.

The issue I'm having is with the customUserNamePasswordValidatorType:

  <serviceCredentials>
    <!-- Use our own custom validation -->
    <userNameAuthentication userNamePasswordValidationMode="Custom"
                            customUserNamePasswordValidatorType="CustomValidator.CustomUserNameValidator, CustomValidator"/>
  </serviceCredentials>

Following directions found here I've created my custom class as well:

namespace CustomValidator
{
    public class CustomUserNameValidator : UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            if (null == userName || null == password)
            {
                throw new ArgumentNullException();
            }


            if (!AuthenticateUser(userName, password))
                throw new SecurityTokenValidationException("Invalid Credentials");

The error is "Could not load file or assembly 'CustomValidator' or one of its dependencies. The system cannot find the file specified.", and refers to the tail end of customUserNamePasswordValidatorType - "..., CustomValidator".

I didn't think it was a problem having my custom validator in its own namespace and class, but I can't see what else to do to make this work.

I've tried with/without the namespace at the beginning, swapping, etc - nothing.

Hoping another pair of eyes can pick this out.

Thanks.

EDIT system.serviceModel

  <system.serviceModel>
    <bindings>

      <!-- wsHttpBinding -->
      <wsHttpBinding>
        <binding name="wsHttpEndpointBinding">
          <security mode="TransportWithMessageCredential">
            <transport clientCredentialType="None" />
            <message clientCredentialType="UserName" />
          </security>
        </binding>
      </wsHttpBinding>

      <!-- webHttpBinding -->
      <webHttpBinding>
        <binding name="wsHttps" >
          <security mode="Transport"/>
        </binding>
      </webHttpBinding>

      <!-- Basic binding -->
      <basicHttpBinding>
        <binding name="TransportSecurity">
          <security mode="Transport">
            <message clientCredentialType="UserName"/>
            <!-- transport clientCredentialType="None"/-->
          </security>
        </binding>
      </basicHttpBinding>

      <!-- customBinding>
        <binding name="WebHttpBinding_IService">
          textMessageEncoding maxReadPoolSize="64" maxWritePoolSize="16"
              messageVersion="Soap12" writeEncoding="utf-8">
            <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          </textMessageEncoding>
          <httpsTransport manualAddressing="false"/>
        </binding>
      </customBinding -->
      <!-- Another custom binding -->
      <customBinding>
        <binding name="CustomMapper">
          <webMessageEncoding  webContentTypeMapperType=
                 "IndexingService.CustomContentTypeMapper, IndexingService" />
          <httpTransport manualAddressing="true" />
        </binding>
      </customBinding>
    </bindings>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="false" />
    <services>
      <service behaviorConfiguration="ServiceBehavior" name="Service">




        <!-- Service Endpoints -->
        <!-- since we're hosting in IIS, baseAddress is not required 
        <host>
          <baseAddresses>
            <add baseAddress="https://mysslserver.com/Service.svc"/>
          </baseAddresses>
        </host>
        -->
        <endpoint address="https://mysslserver.com/Service.svc"
                  binding="wsHttpBinding"
                  bindingConfiguration="wsHttpEndpointBinding" 
                  contract="IService"
                  name="wsHttpEndpoint">
          <!-- 
              Upon deployment, the following identity element should be removed or replaced to reflect the 
              identity under which the deployed service runs.  If removed, WCF will infer an appropriate identity 
              automatically.
          -->
          <!--identity>
            <dns value="https://mysslserver.com"/>
          </identity-->
        </endpoint>

        <!-- endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/ -->
      </service>
    </services>
    <behaviors>

      <endpointBehaviors>
        <behavior name="webBehavior">
          <webHttp />
        </behavior>
      </endpointBehaviors>

      <serviceBehaviors>
        <behavior name="ServiceBehavior">
          <!-- Setup Security/Error Auditing -->
          <serviceSecurityAudit auditLogLocation="Application"
                suppressAuditFailure="false"
                serviceAuthorizationAuditLevel="Failure"
                messageAuthenticationAuditLevel="Failure" />

          <serviceMetadata httpGetEnabled="false" httpsGetEnabled="true"
                           httpsGetUrl="https://mysslserver.com/Service.svc"/>
          <serviceDebug includeExceptionDetailInFaults="false" />
          <serviceCredentials>
            <!-- Use our own custom validation -->
            <userNameAuthentication userNamePasswordValidationMode="Custom"
                                    customUserNamePasswordValidatorType="CustomValidator.CustomUserNameValidator, CustomValidator"/>
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>

      <!-- serviceBehaviors>
        <behavior name="ServiceBehavior">
        <serviceMetadata httpsGetEnabled="true" 
                           httpsGetUrl="https://mysslserver.com/Service.svc" />
          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="true"/>
        </behavior-->
    </behaviors>
  </system.serviceModel>
Luffa answered 26/11, 2009 at 21:20 Comment(2)
can you show the whole server side config inside the <system.serviceModel> section? I don't see anything wrong with those two lines you posted... but that's not telling the whole story...Thermel
I've updated the post to include the whole serviceModel section.Luffa
L
15

I decided to give it another stab, and didn't like having my custom validator in another lib.

So I created a new class in App_Code, and went at it...

The following is what actually fixed it,

="CustomValidator.CustomUserNameValidator, App_Code"
Luffa answered 12/12, 2009 at 0:46 Comment(3)
+1 Thankyou, spent a while staring at that trying to figure out what assembly to select :)Dall
Things like this are why I prefer 'Convention over Configuration'. The amount of arcane configuration settings in .NET is going to put me in a psych ward someday.Quennie
@Luffa how do you mean new class in App_Code? did you make the usernamevalidator where you call the service from?Lading
C
11

When you refer to the custom validator with the values

="CustomValidator.CustomUserNameValidator, CustomValidator"

The first value is the type name and the second is the name of the assembly in which to find the type. So I would suggest that in your first instance your service is actually in some other assembly such as MyService In that case you really needed your config file to say

="CustomValidator.CustomUserNameValidator, MyService"

I suspect that when you have created your new class library for your validator, you have called your project CustomValidator (which will output an assembly called CustomValidator.dll), and hence now your config will work (i.e. it has nothing to do with being in a separate class library - it just happens that the naming of your assembly reference in the web.config is now valid)

Chobot answered 27/11, 2009 at 2:48 Comment(1)
I think this should be marked as answer instead. There is no need to create a library or to use app_code.Dyaus
L
2

Seems a bit strange, but the solution was to create a separate class library and make reference to its DLL in my WCF service.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.ServiceModel;

/// <summary>
/// Summary description for CustomUsernamePasswordValidator
/// </summary>
namespace CustomValidator
{
    public class CustomUserNameValidator : UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            if (null == userName || null == password)
            {
                throw new ArgumentNullException();
            }


            if (!AuthenticateUser(userName, password))
                throw new SecurityTokenValidationException("Invalid Credentials");
            else
            {
                // do nothing - they're good
            }
        }

        public bool AuthenticateUser(string userName, string password)
        {
            if (userName != "userbill" || password != "passwordbill")
            {
                return false;
            }
            else
            {
                return true;
            }
        }
    }
}

I then made added a reference to System.IdentityModel and System.ServiceModel.

The serviceCredentials section for the WCF service is now changed to this:

<serviceCredentials>
    <!-- Use our own custom validation -->
    <userNameAuthentication userNamePasswordValidationMode="Custom"
                            customUserNamePasswordValidatorType="CustomValidator.CustomUserNameValidator, CustomValidator"/>
</serviceCredentials>

Hope that helps someone.

I tried this with invalid credentials, and was expecting to see my "Invalid Credentials" message. Instead I'm getting "At least one security token in the message could not be validated."

Other than that this thing is finally up and running!

Luffa answered 26/11, 2009 at 22:28 Comment(0)
C
0

Just reading this as it was helpful for a POC I had to get going quickly. In response to ELHaix above...this should work to ensure your descriptive custom error is returned back to the client:

using System.ServiceModel
...    
throw new FaultException("Invalid Credentials - Custom Error");
Cachepot answered 4/2, 2017 at 0:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.