WCF custom security over netTcp - wcf

I'm new to WCF and I'm making server/client application in which I need to have some user/password schema to maintain some customization settings for each client and to log who access the service, but "security" in the traffic going through the net is not really needed, since information is not sensitive.
So taking this into account I was searching for a SIMPLE way of accomplishing this but I couldn't find it.
I have a few constraints and conditions:
Windows security is not an option.
I'm using clickonce deployment, so everything should be contained
within the installation packet. I don't know the actual list of users
that are downloading it, so I don't have a way to distribute some
certificate to all users.
Also the client will be accesed within the LAN and through several
WANs. Another requirement has to be met is that the service should
have very good performance since a lot of data is flowing with each
response, so the questions is:
Does message security hurts performance notoriously?
The "manual" way would be to pass the username as a parameter for each method I'm exposing, but it seems like a very dirty solution.
It seems to me a lot of constrains to design this, so that's why I'm asking about this.
Which would be the simplest solution to accomplish this?

First of all we have to assume that all users consuming the service are in some way "registered" to use the service. Because if it is out in the open, anonymous, then there is simply no tracking. So my assumption is as follows:
The service is hosted in Windows Service/WinForms to support TCP
Endpoint. - With new versions of IIS(>6) this is not a required assumption anymore
There is a combination like "UserName/Password" to authenticate. This
is not in the active directory (not opting windows authentication)
but may be xml/database.
We are not willing to have methods like public int Add(string
User, string Password, int A, int B)
I have a service with a TCP endpoint which does something like this. I will share that. I don't claim it is the best practice.
Application name is MYAPP
I have provided
customUserNamePasswordValidatorType="MYAPPHost.Authenticate, MYAPPHost"
in
serviceCredentials > userNameAuthentication
section of web.config.
MYAPPHost is name of my windows service. Authenticate is the class which does the authentication from Database.
message clientCredentialType="UserName" is set for TCPBinding.
App.Config of my windows service:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.diagnostics>
<sources>
<source name="System.ServiceModel"
switchValue="Off" propagateActivity="true" >
<listeners>
<add name="SERVICE_MONITOR" type="System.Diagnostics.XmlWriterTraceListener"
initializeData="MYAPP_MONITOR.svclog" />
</listeners>
</source>
<source name="MYAPP_TRACE" switchValue="All" >
<listeners>
<add name="MYAPP_TRACE_LISTENER" type="System.Diagnostics.XmlWriterTraceListener"
initializeData="MYAPP_TRACE.svclog" />
</listeners>
</source>
</sources>
<trace autoflush="true" />
</system.diagnostics>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="OverAllServiceBehavior">
<serviceSecurityAudit
auditLogLocation="Application"
serviceAuthorizationAuditLevel="Failure"
messageAuthenticationAuditLevel="Failure"
suppressAuditFailure="true" />
<serviceDebug includeExceptionDetailInFaults="True" />
<serviceMetadata httpGetEnabled="True" httpsGetEnabled="True" />
<serviceThrottling maxConcurrentCalls="10000" maxConcurrentSessions="10000">
<dataContractSerializer maxItemsInObjectGraph="2147483647"/>
<serviceCredentials>
<userNameAuthentication
userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="MYAPPHost.Authenticate, MYAPPHost"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="OverAllEndPointBehavior" />
</endpointBehaviors>
</behaviors>
<bindings>
<netTcpBinding>
<binding name="ServiceTCPEndPointBinding" maxBufferSize="2147483647">
<readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647"
maxNameTableCharCount="2147483647" />
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="None" />
<message clientCredentialType="UserName" algorithmSuite="TripleDes"/>
</security>
</binding>
</netTcpBinding>
</bindings>
<services>
<service behaviorConfiguration="OverAllServiceBehavior"
name="MiddleWare.ServiceClasses.ServiceClass">
<host>
<baseAddresses>
<add baseAddress="net.tcp://127.0.0.1:15010/ServiceTCPEndPointMEX"/>
</baseAddresses>
</host>
<endpoint address="net.tcp://127.0.0.1:15020/ServiceTCPEndPoint" contract="MiddleWare.ServiceContracts.IServiceContract" />
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
</configuration>
Authenticate Class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IdentityModel.Selectors;
namespace MYAPPHost
{
public class Authenticate : UserNamePasswordValidator
{
public override void Validate(string UserName, string Password)
{
if (!CheckFromDB(UserName,Password))
throw new Exception("UNAUTHORIZED ACCESS!!!");
}
}
}
In Client Side,after adding reference to the WCF (SR)
SR.ServiceContractClient obj = new SR.ServiceContractClient("ServiceTCPEndPoint");
obj.ClientCredentials.UserName.UserName = "User1";
obj.ClientCredentials.UserName.Password = "Password1";
int I = obj.Add(1, 2);
If credentials are not provided, message security token error is thrown. For wrong credentials UNAUTHORIZED ACCESS occurs.

Related

XAMARIN, WCF with Custom User Name/Password and Principal

I am developing a WCF service that will provide data to a Xamarin client. I am trying to utilize custom user name/password and custom principle so I can attach usable information for the service to the identity. After many tries I have not been able to get anything but different error messages on the client. I believe the problem is in the WCF configuration, but I can not figure out what the problem is. My web.config code is below. Any help or tips on where to go would be greatly appreciated!
<system.serviceModel>
<diagnostics wmiProviderEnabled="true">
<messageLogging
logEntireMessage="true"
logMalformedMessages="true"
logMessagesAtServiceLevel="true"
logMessagesAtTransportLevel="true"
maxSizeOfMessageToLog="65535000"
maxMessagesToLog="3000"
/>
</diagnostics>
<behaviors>
<serviceBehaviors>
<behavior name="Behavior">
<!-- To avoid disclosing metadata information, set the values below
to false before deployment -->
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<!-- 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"/>
<serviceCredentials>
<serviceCertificate findValue="Cert" storeLocation="LocalMachine"
storeName="My" x509FindType="FindBySubjectName"/>
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="Service.ServiceAuthenticator,
Service"/>
</serviceCredentials>
<serviceAuthorization
serviceAuthorizationManagerType="Service.CustomAuthorizationManager,
Service" principalPermissionMode="Custom">
<authorizationPolicies>
<add policyType="Service.AuthorizationPolicy, Service"/>
</authorizationPolicies>
</serviceAuthorization>
<serviceSecurityAudit
auditLogLocation="Application"
serviceAuthorizationAuditLevel="Failure"
messageAuthenticationAuditLevel="Failure"
suppressAuditFailure="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="Service" behaviorConfiguration="Behavior">
<endpoint address="" binding="basicHttpBinding"
contract="Service.IService" bindingConfiguration="Secure"/>
<endpoint address="mex" binding="mexHttpBinding"
contract="IMetadataExchange"/>
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="Secure">
<security mode="Transport">
<transport clientCredentialType="Basic"
proxyCredentialType="None" realm=""/>
<message clientCredentialType="UserName"
algorithmSuite="Default"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
<!--
To browse web app root directory during debugging, set the value
below to true.
Set to false before deployment to avoid disclosing web app folder
information.
-->
<directoryBrowse enabled="true"/>
<httpErrors errorMode="Detailed" />
</system.webServer>
I'm using the WFC service as a connected service in the main project of the Xamarin solution. The service is being called with the code below:
Service.ServiceClient _client;
_client = new Service.ServiceClient();
_client.ClientCredentials.UserName.UserName =
"username";
_client.ClientCredentials.UserName.Password = "password";
//To allow service to connect even though certificate was not
validated.
ServicePointManager.ServerCertificateValidationCallback =
MyRemoteCertificateValidationCallback;
lci = await _client.WCFTestMethod();
To update the post on the comments below. wsHttpBinding is not supported by Xamarin, so I do have to use basicHttpBinding. I have gotten the checkAccessCore procedure to execute when I set the authentication to Anonymous on the IIS site, but it throws this error "No Identity Found" when the AuthorizationPolicy is executing GetClientIdentity. Is there a way to assign an identity in checkAccessCore?
I was able to get the service to work the way I wanted it to, by making sure the authentication method on IIS was set to Anonymous and in the Evaluate method of the AuthorizationPolicy I created a GenericIdentity and used it (example code below) instead of calling the GetClientIdentity method. The service seems to be working now and is authenticating the user in the checkAccessCore method.
Dim client As IIdentity = New GenericIdentity("User")
Dim cp As CustomPrincipal = New CustomPrincipal(client)
evaluationContext.Properties("Identities") = l
evaluationContext.Properties("Principal") = cp

Custom Message Encoder in WCF with support for ReaderQuotas

Found the answer here (last post): http://social.msdn.microsoft.com/Forums/eu/wcf/thread/f5c0ea22-1d45-484e-b2c0-e3bc9de20915
I'm having one last issue with the implementation of my custom (TextOrMtomEncoder), which is the implementation of ReaderQuotas.
I've searched the web a lot, but I can't figure out the final piece of the puzzle.
I've got a class, which contains my implementations of the 'BindingElementExtensionElement' and 'MessageEncodingBindingElement'.
The MessageEncodingBindingElement implementation contains an override for:
T GetProperty<T>(BindingContext context)
which I 'borrowed' from the default .NET MessageEncoding implementations, like the TextMessageEncoding.
This has to be the right implementation, because MSDN says so.
The configuration is loaded fine from the web.config, I can see the ReaderQuotas properties in both my classes are set correctly, but it looks like .NET isn't reading the ReaderQuotas config from my MessageEncodingBindingElement implementation.
My guess is .NET uses the GetProperty method to load the config, because MessageVersion is requested via this method. But the problem is, T is never equal to XmlDictionaryReaderQuotas, so the ReaderQuotas is never begin requested.
The root of my question is weird btw, I'm developing on a Windows 7 x64 machine with IIS7.5. Posting 'large' files (like 100 KB) works on my machine. But when I deploy the service to Windows Server 2008 R2 (tried 2 different servers), I get the following error:
The formatter threw an exception while trying to deserialize the
message: There was an error while trying to deserialize parameter
http://socialproxy.infocaster.net:argument. The InnerException message
was 'There was an error deserializing the object of type
System.Object. The maximum array length quota (16384) has been
exceeded while reading XML data. This quota may be increased by
changing the MaxArrayLength property on the XmlDictionaryReaderQuotas
object used when creating the XML reader. Line 1, position 1584.'.
Please see InnerException for more details.
And like I said, it works on my machine :-/
Could anybody tell me how I can resolve this?
Many thanks in advance!
The WCF service config:
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="wsdlExtensions" type="WCFExtras.Wsdl.WsdlExtensionsConfig, WCFExtras, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<add name="textOrMtomMessageBehavior" type="InfoCaster.SocialProxy.lib.TextOrMtom.TextOrMtomMessageBehavior, InfoCaster.SocialProxy" />
</behaviorExtensions>
<bindingElementExtensions>
<add name="textOrMtomEncoding" type="InfoCaster.SocialProxy.lib.TextOrMtom.TextOrMtomEncodingElement, InfoCaster.SocialProxy" />
</bindingElementExtensions>
</extensions>
<bindings>
<customBinding>
<binding name="TextOrMtomBinding">
<textOrMtomEncoding messageVersion="Soap11">
<readerQuotas maxDepth="32" maxStringContentLength="5242880" maxArrayLength="204800000" maxBytesPerRead="5242880" maxNameTableCharCount="5242880" />
</textOrMtomEncoding>
<httpTransport maxBufferSize="5242880" maxReceivedMessageSize="5242880" transferMode="Buffered" authenticationScheme="Anonymous" />
</binding>
</customBinding>
</bindings>
<services>
<clear />
<service name="InfoCaster.SocialProxy.SocialProxy" behaviorConfiguration="InfoCaster.SocialProxy.ISocialProxyServiceBehavior">
<endpoint name="SocialProxyServiceEndpoint" address="" binding="customBinding" bindingConfiguration="TextOrMtomBinding" contract="InfoCaster.SocialProxy.ISocialProxy" behaviorConfiguration="InfoCaster.SocialProxy.ISocialProxyEndpointBehavior" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<clear />
<behavior name="InfoCaster.SocialProxy.ISocialProxyServiceBehavior">
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true" />
<!-- 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>
</serviceBehaviors>
<endpointBehaviors>
<clear />
<behavior name="InfoCaster.SocialProxy.ISocialProxyEndpointBehavior">
<textOrMtomMessageBehavior />
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
Found the answer here (last post): http://social.msdn.microsoft.com/Forums/eu/wcf/thread/f5c0ea22-1d45-484e-b2c0-e3bc9de20915
Ok, I found it. You have to pass the readerquotes to the standard encoders. Unfortunantly there's no constructor for this so you have to set the property.
class TextOrMtomEncoder : MessageEncoder {
MessageEncoder _textEncoder;
MessageEncoder _mtomEncoder;
public TextOrMtomEncoder(MessageVersion messageVersion, XmlDictionaryReaderQuotas readerQuotas)
{
TextMessageEncodingBindingElement textEncoderBindingElement = new TextMessageEncodingBindingElement(messageVersion, Encoding.UTF8);
MtomMessageEncodingBindingElement mtomEncoderBindingElement = new MtomMessageEncodingBindingElement(messageVersion, Encoding.UTF8);
readerQuotas.CopyTo(mtomEncoderBindingElement.ReaderQuotas);
readerQuotas.CopyTo(textEncoderBindingElement.ReaderQuotas);
_mtomEncoder = mtomEncoderBindingElement.CreateMessageEncoderFactory().Encoder;
_textEncoder = textEncoderBindingElement.CreateMessageEncoderFactory().Encoder;
}

invalid mscorlib exception in custom security attribute c'tor

I'm trying to implement my custom security attribute. It's very simple for now
[Serializable]
[ComVisible(true)]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public class SecPermissionAttribute : CodeAccessSecurityAttribute
{
public SecPermissionAttribute(SecurityAction action) : base(action) { }
public override System.Security.IPermission CreatePermission()
{
IPermission perm = new PrincipalPermission(PermissionState.Unrestricted);
return perm;
}
}
For some reason I've got an exception in the attribute c'tor
System.IO.FileLoadException occurred
Message=The given assembly name or codebase, 'C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll', was invalid.
Source=WcfRoleProviderTestService
StackTrace:
at SecLib.SecPermissionAttribute..ctor(SecurityAction action)
at WcfRoleProviderTestService.Service1.GetData(Int32 value) in D:\TestProjects\WcfRoleProviderTestService\WcfRoleProviderTestService\Service1.svc.cs:line 19
InnerException:
The dll is signed. It seems to me like a security issue but I'm not sure. By the way I tried to use PrincipalPermissionAttribute and it works fine.
Forgot to say, I'm using VS 2010, FW 4.0, the attribute is concumed in the WCF service
I'll be very glad to get some help.
My service configuration is the following
<system.web>
<compilation debug="true" defaultLanguage="c#" targetFramework="4.0" />
<roleManager enabled="true" cacheRolesInCookie="true" cookieName=".ASPROLES"
defaultProvider="MyRoleProvider">
<providers>
<clear />
<add connectionStringName="Service1" applicationName="InfraTest"
writeExceptionsToEventLog="false" name="MyRoleProvider" type="SecLib.MyRoleProvider, SecLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=798c04e15cff851a" />
</providers>
</roleManager>
</system.web>
<system.serviceModel>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBindingConfiguration" closeTimeout="00:01:00"
sendTimeout="00:10:00" maxBufferSize="524288" maxReceivedMessageSize="524288">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service name="WcfRoleProviderTestService.Service1"
behaviorConfiguration="BasicHttpServiceBehavior" >
<endpoint name="BasicHttpEndpoint"
contract="WcfRoleProviderTestService.IService1"
address="WcfAuthenticationTest"
binding="basicHttpBinding"
bindingConfiguration="BasicHttpBindingConfiguration" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
<host>
<baseAddresses>
<add baseAddress="http://localhost/WcfRoleProviderTestService/" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="BasicHttpServiceBehavior">
<serviceAuthorization principalPermissionMode="UseAspNetRoles"
roleProviderName="MyRoleProvider" impersonateCallerForAllOperations="true" />
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
I've got the error both on Windows XP, IIS v5.1 and on Windows Server 2008 R2 IISV7.5 only if the WCF service is configured to use Windows Authentication (see the configuration above). On more interesting fact is that the error occured only if the attribute is used with the System.Security.Permissions.SecurityAction.Demand security action.
[OperationBehavior(Impersonation = ImpersonationOption.Allowed)]
[SecPermission(System.Security.Permissions.SecurityAction.Demand)]
public string GetData(int value)
{
string userName = ServiceSecurityContext.Current.WindowsIdentity.Name;
return string.Format("You entered: {0}, User {1}", value, userName);
}
Other options work fine.
Thanks.
With a help of one of my colleagues, the problem has been soleved. I'm not sure what the exact reason of the exception was but it seems to be a compilation issue. When I changed the project type from web application to web site wich is compiled at run time according it's pool definition (64 or 32 bit) it started to work fine.

WCF service authentication using iisexpress transport security and basic authentication always returning 401

I have a WCF service configured to use Transport security and basic authentication.
The service is hosted in iiexpress withing vs2010.
I am able to connect from my client code but always receive:
"The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'Basic realm=realm'."
And this has an inner exception of:
"The remote server returned an error: (401) Unauthorized."
Similar to Can not call web service with basic authentication using WCF although my client code already has the settings set out in the answer.
I also followed HTTP Basic Authentication against Non-Windows Accounts in IIS/ASP.NET (Part 3 - Adding WCF Support) and the previous blog to set up a Module and the IAuthorizationPolicy classes.
IISExpress is configed in classic mode with anonymous and windows authentication disabled and SSL enabled.
Client Config:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="NotificationHttpBinding">
<security mode="Transport">
<transport clientCredentialType="Basic" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://localhost/NotificationService.svc"
binding="basicHttpBinding" bindingConfiguration="NotificationHttpBinding"
contract="NotificationPortType" name="BasicHttpBinding_NotificationPortType" />
</client>
</system.serviceModel>
Service Config:
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<services>
<service name="Notification.NotificationService" behaviorConfiguration="NotificationServiceBehavior">
<endpoint binding="basicHttpBinding" contract="NotificationPortType" bindingConfiguration="NotificationHttpBinding" >
</endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="NotificationServiceBehavior">
<serviceMetadata />
<serviceDebug includeExceptionDetailInFaults="false"/>
<serviceAuthorization>
<authorizationPolicies>
<add policyType="Notification.HttpContextIdentityPolicy, Notification" />
</authorizationPolicies>
</serviceAuthorization>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="NotificationHttpBinding">
<security mode="Transport">
<transport clientCredentialType="Basic" />
</security>
</binding>
</basicHttpBinding>
</bindings>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
<system.web>
<httpModules>
<add name="CustomBasicAuthentication" type="Notification.CustomBasicAuthenticationModule, Notification"/>
</httpModules>
<membership defaultProvider="SampleProvider">
<providers>
<add name="SampleProvider" type="Notification.HardcodedSecurityProviders, Notification" />
</providers>
</membership>
</system.web>
Client Code is nothing major:
static void Main(string[] args)
{
ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => { return true; };
NotificationPortTypeClient client = new NotificationPortTypeClient("BasicHttpBinding_NotificationPortType");
client.ClientCredentials.UserName.UserName = "Test";
client.ClientCredentials.UserName.Password = "PWD";
client.sendNotification(new NotificationRequest());
}
Alternatively
If someone can show me an alternative of how to use IIS6 to host a service WCF which using basic http authentication while requiring SSL (https) I'll be happy with that!
UPDATE
Seems this was my culprit all along:
Avoid http 401 round trip
However, I found that my modules fired fine (in integrated mode) but I was then presented with a service error telling me that basic integration is required but not enabled on the host.
Opened up iisexpress applicationhost.config file and sure enough I found:
<section name="basicAuthentication" overrideModeDefault="Deny" />
followed by
<basicAuthentication enabled="false" />
further down
I've changed these to <section name="basicAuthentication" overrideModeDefault="Allow" />
and tried to enable in my web.config...no dice :(
You need to use WSHttpBinding.
There is a complete sample here.

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

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>
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"
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)
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!
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");