I've implemented my custom userNamePasswordValidationMode
in my WCF app like so:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding>
<security mode ="Message">
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True"/>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="MyProject.Validator.MyValidator, MyProject" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true" />
</system.serviceModel>
This throws no errors, but when I reference my service in my client and set the username/password credentials, my methods are still called, even if I enter in the wrong password:
Testing.myAPIClient.client = new Testing.myAPIClient();
client.ClientCredentials.UserName.UserName = "test";
client.ClientCredentials.UserName.Password = "wrongpassword";
Console.WriteLine(client.StockQuery("123"));
Console.ReadLine();
The method StockQuery
still gets called and the code in MyValidator
doesn't even get called:
public class MyValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
using (var ax = new AXConnector())
{
if (!(bool)ax.CallStaticClassMethod("OnlineUsers", "validateLogon", userName, password))
{
throw new UnauthorizedAccessException("Not Authorised");
}
}
}
}
Here is my app.config:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IMyAPI" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://myServer:89/MyAPI.svc" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IMyAPI" contract="Testing.IMyAPI"
name="BasicHttpBinding_IMyAPI" />
</client>
</system.serviceModel>
Service Interface:
[ServiceContract]
public interface IMyAPI
{
string UserName { [OperationContract] get; [OperationContract] set; }
string Password { [OperationContract] get; [OperationContract] set; }
[OperationContract]
bool StockQuery(string partNo);
[OperationContract]
decimal PriceQuery(string partNo, string accNo);
}
Service Class:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required),
ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Single)]
public class MyAPI : IMyAPI
{
public string UserName { get; set; }
public string Password { get; set; }
public MyAPI()
{
this.CheckSecurity();
}
private void CheckSecurity()
{
if (this.UserName != "test" && this.Password != "123")
{
throw new UnauthorizedAccessException("Not Authorised");
}
}
// StockQuery and PriceQuery methods...
}
Your client Security.Mode
is set to "None"
which it should be "Message"
.
<security mode ="Message">
<message clientCredentialType="UserName"/>
</security>
Edit: You might need a certificate to use that. You can follow this walkthrough, but it is not recommended to use it in production.
[1]Another option is to implement your own security. Here's a basic example.
In your service, change it's ServiceBehavior
's InstanceContextMode
to PerSession
and ConcurrencyMode
to Single
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Single)]
public class SomeService : ISomeService
{
// ...
}
Add a Username
and Password
property in your service.
public string UserName { [OperationContract] get; [OperationContract] set; }
public string Password { [OperationContract] get; [OperationContract] set; }
Add a private method for checking a security.
public void CheckSecurity()
{
if ((this.UserName == null || this.Password == null) ||
this.UserName == "username" && this.Password == "password"))
{
throw new FaultException("Unknown username or incorrect password.");
}
}
Then call the CheckSecurity
method in each of your service [2]class constructor method.
public void SomeServiceMethod()
{
this.CheckSecurity();
// some method code
}
In your client application code, set the service username and password for every instance, or create a static class that will do this for you.
You might also try to use encryption in the username and password to add security.
[1] Taken from my answer in How to put a password on a WCF Service?
[2] You need to call the CheckSecuirty
method in the beginning of each of your service method(s).