3

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

Edit

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>

Edit 2

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...
}
3
  • Can you validate that configuration of your client created from Add service reference contains same security settings as on your service side? Is it whole configuration of your service? Commented Apr 18, 2012 at 8:02
  • Is your assembly of where the class MyValidator is named MyProject? Commented Apr 18, 2012 at 8:06
  • Yea I believe so, its the name of my project? Commented Apr 18, 2012 at 8:08

2 Answers 2

1

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.

WCF Service

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
}

Client Application

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).

Sign up to request clarification or add additional context in comments.

16 Comments

How can I set the security mode to be message on the client by default? Is that possible from within my WCF app? Because this means that they just have to set it to None to get around authentication then?
I also get the following error: BasicHttp binding requires that BasicHttpBinding.Security.Message.ClientCredentialType be equivalent to the BasicHttpMessageCredentialType.Certificate credential type for secure messages. Select Transport or TransportWithMessageCredential security for UserName credentials. If I use either of those I need to use Https, which I dont have.
Do I put those properties in my service interface or the service? I've put them in the service and it throws an exception because I dont have the ServiceContract attribute above my service, its above my interface tho.
Put that in the interface string UserName { [OperationContract] get; [OperationContract] set; } and the service public string UserName { get; set; }. To access that in the client you need to do it like this: myServiceClient.set_Username("Username").
Ok, I've done this but when I debug, my username and password are both null as the constructor is called when I call client.set_Username("test")?
|
0

We ran into the problem where everything worked fine for years, but now we're moving to an environment where everything is SSL-offloaded behind a load balancer, so we're no longer able to rely on transport. I was running into all the same problems as described by OP.

We're still using regular-degular basicHttpBinding. Got it working this way:

Created a class that would parse the header XML:

[DataContract(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", Name = "Security")]
public partial class SecurityHeaderType
{
    [XmlElement(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")]
    [DataMember]
    public UsernameToken UsernameToken { get; set; }
}

public class UsernameToken : IXmlSerializable
{
    public string Username { get; set; }
    public string Password { get; set; }

    public XmlSchema GetSchema()
    {
        throw new NotImplementedException();
    }

    public void ReadXml(XmlReader reader)
    {
        Dictionary<string, string> secDictionary;
        string xml = reader.ReadOuterXml();

        using (var s = GenerateStreamFromString(xml))
        {
            secDictionary =
                XElement.Load(s).Elements()
                    .ToDictionary(e => e.Name.LocalName, e => e.Value);
        }

        Username = secDictionary["Username"];
        Password = secDictionary["Password"];

    }

    public Stream GenerateStreamFromString(string s)
    {
        var stream = new MemoryStream();
        var writer = new StreamWriter(stream);
        writer.Write(s);
        writer.Flush();
        stream.Position = 0;
        return stream;
    }

    public void WriteXml(XmlWriter writer)
    {
        throw new NotImplementedException();
    }
}

I followed @john-isaiah-carmona 's example for the Username/Password fields, then write to them like so:

public void CheckSecurity()
{
    if (OperationContext.Current.IncomingMessageHeaders.FindHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd") != -1)
    {
        var securityHeader = OperationContext.Current.IncomingMessageHeaders.GetHeader<SecurityHeaderType>("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
        Username = securityHeader.UsernameToken.Username;
        Password = securityHeader.UsernameToken.Password;
    }
    else
        throw new FaultException("Unknown username or incorrect password.");
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.