0

I am using a CustomUserNamePasswordValidator for my WCF web service. However, i am trying to add a IsAlive operation, which should be able to be called from clients, even when not authenticated.

For example, i want to be able to do a check, if a service is online and accessible on startup, so i can notify the user on missing inet connection or a not available service (due to maintenance).

I have code for all this already in place. What i am missing is how i can access the operation without passing a username and password.

I could probably just add a second service which allows anon access, but i'd really prefer to use the existing service.

The Validator is implemented like this (i ommited the actual checking code):

public sealed class MyCredentialValidator : UserNamePasswordValidator
{
    public MyCredentialValidator ()
    {

    }

    public override void Validate(string userName, string password)
    {
        Debug.WriteLine("MyCredentialValidator : Validate called.");

        // do some checks
        var isValid = CheckCredentials(userName, password)

        if(!isValid)
        {
            throw new FaultException(...);
        }
    }
}

It is registered in the web.config like so:

<system.serviceModel>
    <behaviors>
        <serviceBehaviors>
            <behavior name="SecureBehavior">
                <serviceMetadata httpsGetEnabled="false"/>
                <serviceDebug includeExceptionDetailInFaults="true"/>
                <serviceCredentials>
                    <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="MyCredentialValidator,..."/>
                </serviceCredentials>
            </behavior>
        </serviceBehaviors>         
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true"/>
    <bindings>
        <wsHttpBinding>
            <binding name="SecureBinding" closeTimeout="00:10:00" openTimeout="00:10:00" receiveTimeout="00:10:00" sendTimeout="00:10:00" maxReceivedMessageSize="2147483647">
                <security mode="TransportWithMessageCredential">
                    <message clientCredentialType="UserName"/>
                </security>
                <readerQuotas maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxStringContentLength="2147483647"/>
            </binding>
        </wsHttpBinding>
    </bindings>
    <services>
        <service name="my service" behaviorConfiguration="SecureBehavior">
            <endpoint address="" binding="wsHttpBinding" contract="my contract" bindingConfiguration="SecureBinding">
                <identity>
                    <dns value="localhost"/>
                </identity>
            </endpoint>
            <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange"/>
        </service>
    </services>
</system.serviceModel>

client side configuration:

<system.serviceModel>
<bindings>

    <wsHttpBinding>
        <binding name="SecureBinding"
                 closeTimeout="00:10:00"
                 openTimeout="00:10:00"
                 receiveTimeout="00:10:00"
                 sendTimeout="00:10:00"
                 maxReceivedMessageSize="2147483647">
            <security mode="TransportWithMessageCredential">
                <message clientCredentialType="UserName"/>
            </security>
            <readerQuotas maxArrayLength="2147483647"
                          maxBytesPerRead="2147483647"
                          maxStringContentLength="2147483647"/>
        </binding>
    </wsHttpBinding>

</bindings>

<client>

    <endpoint address="https://my service url"
              contract="my contract"
              binding="wsHttpBinding"
              bindingConfiguration="SecureBinding"
              name="secure" />
</client>

</system.serviceModel>

client side wcf call code:

var cf = new ChannelFactory<my contract>("secure");
using (IClientChannel channel = (IClientChannel)cf.CreateChannel())
{
    channel.OperationTimeout = TimeSpan.FromSeconds(3);
    bool success = false;
    try
    {
        channel.Open();
        result = ((my contract)channel).IsAlive();
        channel.Close();
        success = true;
    }
    finally
    {
        if (!success)
        {
            channel.Abort();
        }
    }
}
  • great stuff, while I do not understand why you are implementing the authentication client side, where you have the debug.writeline, right there you should be checking where the request is going to, if it for example contains IsAlive then return, so wrap it in a if else statement for example. – Louis Lewis Nov 10 '14 at 11:47
  • The authenticatoin is server side! The binding config in the client is needed so wcf knows how to build a correct and valid request. The auth is of course server side! :) However, please note the exception occurs on the client, when i resolve the channelfactory, build a new channel and call "open". I added the client side wcf code to the question. –  Nov 10 '14 at 11:49
  • haha, perfect you had me there for a second, all I saw was CheckCredentials. So before you start with the call CheckCredentials, you want to check your outgoing url, not the incoming one as I previously suggested, then if the outgoing url for example contains "IsAlive" you can simply call return at that point and not continue to the rest of the check credentials and so on – Louis Lewis Nov 10 '14 at 11:53
  • I would! However, the server never gets hit (and i need it to be hit, because i want to know if it "is alive"). The reason is that my binding, client and server side, requires a credential to be set. I can populate it with dummy values of course, but that seems "hacked together" –  Nov 10 '14 at 11:56
  • that would mean that you would need to implement the same or rather similar logic both server and client side, assuming of course that you are writing both. So in the client side validator, you would be checking if the outgoing request is going to the Isalive operation and if so, return and don't continue the rest of the authentication logic, then server side, check as I mentioned before, if the incoming request is going to the IsAlive operation and again simply return not performing the rest of the authentication logic – Louis Lewis Nov 10 '14 at 12:01
  • There is no client side validator! Please look closely, there is no validator registered client side, and i dont know if that would even work. Authentication is server side only. But the client side binding config needs to be configured to match the server side so WCF can communicate. This is how it should work: Regular call: Build the request, add the credentials -> server side validate, continue with operation if no ex IsAlive call: build the request, ommit credentials -> server side validate skipped, continue with operation –  Nov 10 '14 at 12:06
  • I think I follow you now, when you perform a call client side to the IsAlive operation because no credentials are set, when you call open an exception gets raised client side. if that is correct, it would be because on the endpoint named secure, its configuration requires client credential type username. If this is right so far, you could client side add a new endpoint but its configuration does not have the security mode set ie anonymous, that should allow you to open the channel and attempt the call, meaning now you should be left with server side only. How is this so far? – Louis Lewis Nov 10 '14 at 12:26
  • Yes, we are now on the same page :) However, i think for this to work i need a second binding on the server side too, because they need to match. BUT if the second binding targets the same service, that would mean, i have now introduced a way to access the service without credentials. So, either way, i think my best option right now is to indeed build a second service. –  Nov 10 '14 at 12:30
  • not quite that bad, it is not that you introduced a way to communicate with the server without credentials, as your server will still enforce its security policy, I am sure that adding the second binding client side, will work and you should before making changes to the server, receive a message back saying that you are not authenticated. that is exactly what you want. You will then in your server side validator, implement the initial proposal of mine, where you check where the request is going and then allow it if it is for IsAlive – Louis Lewis Nov 10 '14 at 12:51
  • I have not found a way to make this work. If you have a working example of what you propose, please add it to your answer. –  Nov 10 '14 at 13:12

1 Answers1

0

I have done something like this before, depending on how you have integrated your custom validator in the wcf pipleline, you could simply before you do the actual validation, which I guess returns something like true or false, you could check the incoming url or address and see if it is going to be going to your IsAlive operation, if that is the case, you could simply do a early return true.

Wcf has a few ways with which you can check what operation the client has called.

to be more accurate, I would need to know how you wrote your custom validator and where in the pipeline it integrates.

Louis Lewis
  • 1,298
  • 10
  • 25
  • The Validator is registered as a serviceBehavior in the web.config. It is called on every service call. –  Nov 10 '14 at 11:22
  • that sounds great, you could see this post http://stackoverflow.com/questions/9110397/get-original-request-url-in-wcf-rest-service as one example as how you could check the original request url. you can simply check if it is heading for your IsAlive operation and return true if it is, because that method would of course not require any authentication and would most likely return something simple like true or false – Louis Lewis Nov 10 '14 at 11:27
  • there are a few methods, I simply chose the above one because it is on stack overflow already. If it does not work for you, just let me know and I will help you find a work around – Louis Lewis Nov 10 '14 at 11:27
  • Thanks for your suggestion, however i get an exception on the client side because i did not provide a credential for the call to "IsAlive". It seems it explicitly requires credentials to be set so any call can be made at all. –  Nov 10 '14 at 11:32
  • could you share your custom validator, it is in there that we will have to make changes. It would be your server that is causing the exception, as I would imagine that the custom validator resides server side, and your client is simply making a call that is being intercepted by the custom validator server side which is seeing that there are no credentials that have been supplied or something along those lines. In my suggestion the very first code that should reside in your custom validator, would be a check to see if the request of for the IsAlive operation. – Louis Lewis Nov 10 '14 at 11:36
  • Well i set a debug point, attached to iis process and do not hit the debug point. The exception occurs on channel.Open(), before i fire the call, so i really think it is client side. I will add the client config to my question! –  Nov 10 '14 at 11:43