Built-in way to do Basic Authentication with WCF REST service?

RajeshKannan picture RajeshKannan · Dec 3, 2013 · Viewed 8k times · Source

I am using Basic Authentication in my WCF service. And also using ASP Membership provider for authentication.

Web.Config: For REST service:

<webHttpBinding>
    <binding name="webHttpBinding" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="200065536" maxBufferPoolSize="200065536" maxReceivedMessageSize="200065536"   transferMode="Buffered" useDefaultWebProxy="true">
      <readerQuotas maxDepth="202048000" maxStringContentLength="202048000" maxArrayLength="202048000"
                    maxBytesPerRead="202048000" maxNameTableCharCount="202048000"/>
      <security mode="Transport">  
      </security>
    </binding>
  </webHttpBinding>

Authentication type and Mode:

<serviceCredentials>
            <userNameAuthentication userNamePasswordValidationMode="MembershipProvider" membershipProviderName="CustomMemberShipProvider" />
</serviceCredentials>

My Custom class for BasicAuthentication before invoking any method. The code is shown below:

namespace BasicAuth.Service
{
    public class BasicAuthenticationInvoker : Attribute, IOperationBehavior, IOperationInvoker
    {
        #region Private Fields

        private IOperationInvoker _invoker;

        #endregion Private Fields

        #region IOperationBehavior Members

        public void ApplyDispatchBehavior(OperationDescription operationDescription,
                                          DispatchOperation dispatchOperation)
        {
            _invoker = dispatchOperation.Invoker;
            dispatchOperation.Invoker = this;
        }

        public void ApplyClientBehavior(OperationDescription operationDescription,
                                        ClientOperation clientOperation)
        {
        }

        public void AddBindingParameters(OperationDescription operationDescription,
                                         BindingParameterCollection bindingParameters)
        {
        }

        public void Validate(OperationDescription operationDescription)
        {
        }

        #endregion IOperationBehavior Members

        #region IOperationInvoker Members

        public object Invoke(object instance, object[] inputs, out object[] outputs)
        {
            System.Diagnostics.Debugger.Break();
            if (Authenticate())
                return _invoker.Invoke(instance, inputs, out outputs);
            else
            {
                outputs = null;
                return null;
            }
        }

        public object[] AllocateInputs()
        {
            return _invoker.AllocateInputs();
        }

        public IAsyncResult InvokeBegin(object instance, object[] inputs,
                                        AsyncCallback callback, object state)
        {
            throw new NotSupportedException();
        }

        public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
        {
            throw new NotSupportedException();
        }

        public bool IsSynchronous
        {
            get
            {
                return true;
            }
        }

        #endregion IOperationInvoker Members

        private bool Authenticate()
        {
            string[] credentials = GetCredentials(WebOperationContext.Current.IncomingRequest.Headers);

            if (credentials != null && credentials.Length == 2)
            {
                var username = credentials[0];
                var password = credentials[1];
                if (Membership.ValidateUser(username, password))  //if valid user
                {
                    //get the roles of the user
                    string[] roles = Roles.GetRolesForUser(username);
                    Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(username), roles);
                    return true;
                }
            }
            WebOperationContext.Current.OutgoingResponse.Headers["WWW-Authenticate"] = string.Format("Basic realm=\"{0}\"", string.Empty);
            WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized;
            return false;
        }

        private string[] GetCredentials(WebHeaderCollection headers)
        {
            string credentials = WebOperationContext.Current.IncomingRequest.Headers["Authorization"];
            if (credentials != null)
                credentials = credentials.Trim();

            if (!string.IsNullOrEmpty(credentials))
            {
                try
                {
                    string[] credentialParts = credentials.Split(new[] { ' ' });
                    if (credentialParts.Length == 2 && credentialParts[0].Equals("basic", StringComparison.OrdinalIgnoreCase))
                    {
                        credentials = Encoding.ASCII.GetString(Convert.FromBase64String(credentialParts[1]));
                        credentialParts = credentials.Split(new[] { ':' });
                        if (credentialParts.Length == 2)
                            return credentialParts;
                    }
                }
                catch (Exception ex)
                {

                }
            }

            return null;
        }
    }
}

My Iservice looks like below:

My Custom class is used as an attribute in Iservice contract

public interface IService1
    {
        [OperationContract]
        [BasicAuthenticationInvoker] //my custom class for authentication
        [WebGet(UriTemplate = "GetString?userID={userID}",
            ResponseFormat = WebMessageFormat.Json)]
        string GetString(string userID);
   }

When calling the WCF REST service using the AJAX call, I add the Authentication header to the Request and authenticate the user using the above custom class.

AJAX Call: Below is the Ajax call used to call service and used beforeSend to authenticte the user before accessing the service.

<script>
             $(function () {
                 alert("onload");
                 $.ajax
                     ({
                         type: "GET",
                         data:jsondata,
                         url: https://localhost:446/BasicAuthService.svc/rest/GetString',
                         cache: false,
                         async: true,
                         crossDomain:true,
                         dataType: "json",
                         contentType: "application/json; charset=utf-8",
                         beforeSend: function (xhr) {                             
                             xhr.setRequestHeader('Authorization', 'Basic plc2gxMjMk');
                         },
                         error: function(jqXHR, exception)
                         {
                             alert(jqXHR.status+" "+exception);
                         }
                     });
             });
  </script>

My Question is:

I hope you get the overall picture about how my code works.

So what I need is, Instead of using custom class for BasicAuthentication, how can I authenticate each request to the service ? Is there any In-build functionality in WCF for authenticating the incoming request ?

Thanks in advance.

Answer

Roy Dictus picture Roy Dictus · Dec 4, 2013

Your security mode should specify basic authentication:

<security mode="Transport">
    <transport clientCredentialType="Basic" />
</security>