WCF: authenticate user before executing some service operation

Question!

My problem is the following.

A client interacts with my WCF service via a web interface (HTTP). Some service operations require the client to authenticate by providing username and password. Let's suppose for simplicity that these info are passed via query string parameters (or in the Authorization header as in HTTP Basic Auth).

E.g., a service operation may be called via http://myhost.com/myservice/myop?user=xxx&password=yyy

As multiple service operations require such sort of authentication, I would like to factor the authentication code out of the single operations.

By looking around, I read about service behaviors and came up with the following code:

public class MyAuthBehaviorAttribute : Attribute, IServiceBehavior, IDispatchMessageInspector {


    /********************/
    /* IServiceBehavior */
    public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
        System.ServiceModel.ServiceHostBase serviceHostBase) {
        //  It’s called right after the runtime was initialized
            foreach (ChannelDispatcher chDisp in serviceHostBase.ChannelDispatchers) {
                foreach (EndpointDispatcher epDisp in chDisp.Endpoints) {
                    epDisp.DispatchRuntime.MessageInspectors.Add(new MyAuthBehaviorAttribute());
                }
            }
    }

    /*...*/

    /*****************************/
    /* IDispatchMessageInspector */
    public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, 
        System.ServiceModel.IClientChannel channel, 
        System.ServiceModel.InstanceContext instanceContext) {
            object correlationState = null;
            var prop = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];


            var parts = HttpUtility.ParseQueryString(prop.QueryString);
            string user = parts["user"];
            string password = parts["password"];
            if (AuthenticateUser(user,password)) {
                // ???????????????????????????
            }
            else {
                throw new Exception("...");
            }

            return correlationState;
    }

    /*...*/

}

Then, the service is annotated through

[MyAuthBehavior]
public class Service : IContract
{
    // implementation of the IContract interface
}

Now, I manage to execute my behavior before ANY service operation. However, I have the following issues:

  • How can I pass the result of authentication to the service operations?
  • How can I restrict the authentication to just a few service operations?

Regarding the last point, I looked at IOperationBehavior, but in that case I can just attach IParameterInspectors and not IDispatchMessageInspectors. It would be undesirable because I may need to look at the message headers, for example in case I decide to consider the Authorization HTTP header when supporting HTTP Basic Authentication.

As a related question, I would also ask what you think about my approach, and if there are better (non-overcomplicated) approaches.

By : metaphori


Answers

I would suggest isolating all of your methods that don't require authentication into their own service. For example:

IPublicService.cs and PublicService.svc

and the ones that require authentication:

IPrivateService.cs and PrivateService.svc

For authentication for PrivateService.svc, I'd suggest using MessageCredential using Username for that binding:

  
By : BlueSky


After some research, here is my current solution.

Issue 1: execute (part of) the service behavior only for certain service operations

First of all, I mark my service operations with a custom attribute:

public class RequiresAuthAttribute : Attribute { }

public partial class MyService { 

    [RequiresAuth]
    WebGet(UriTemplate = "...")]
    public Tresult MyServiceOperation(...){ ... }

Then I retrieve this information to decide if the behavior has to be executed or not

    public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, 
        System.ServiceModel.IClientChannel channel, 
        System.ServiceModel.InstanceContext instanceContext) {
        if(AuthenticationNeeded()){ ... }
    }

    public bool AuthenticationNeeded() {
        // 1) Get the current operation's description
        OperationDescription od = GetOperationDescription(OperationContext.Current);

        // 2) Check if the service operation is annotated with the [RequiresAuth] attribute
        Type contractType = od.DeclaringContract.ContractType;
        object[] attr = contractType.GetMethod(od.Name).GetCustomAttributes(typeof(RequiresAuthAttribute), false);

        if (attr == null || attr.Length == 0) return false;
        return true;
    }

    // See http://www.aspnet4you.com/wcf/index.php/2013/01/30/message-interception-auditing-and-logging-at-wcf-pipeline/ 
    private OperationDescription GetOperationDescription(OperationContext operationContext) {
        OperationDescription od = null;
        string bindingName = operationContext.EndpointDispatcher.ChannelDispatcher.BindingName;
        string methodName;
        if (bindingName.Contains("WebHttpBinding")) {
            //REST request
            methodName = (string)operationContext.IncomingMessageProperties["HttpOperationName"];
        }
        else {
            //SOAP request
            string action = operationContext.IncomingMessageHeaders.Action;
            methodName = operationContext.EndpointDispatcher.DispatchRuntime.Operations.FirstOrDefault(o =
By : metaphori


This video can help you solving your question :)
By: admin