2

I have a self-hosted WCF RESTful API that exposes some functionality that I don't want exposed to unauthorized users. All administrators must be signed in using a custom ASP.NET membership provider to call the REST API. Currently I just send a API key which is unsecure as it can be seen by all. All calls to the REST API is done via jQuery. I'm not using TLS/SSL or other transport security mechanisms. All REST API calls are done against the same server/domain, so there are no cross-domain calls or JSONP stuff going on.

My question is, what is the best practice in my case for securing my REST API? Perhaps I should use OAuth for this - the more I read about OAuth the more it seems it is not for my scenario with jQuery.

IVeraCMS.cs:

[ServiceContract]
public interface IVeraCMS {

    [OperationContract]
    [WebInvoke(Method = "GET",
        BodyStyle = WebMessageBodyStyle.WrappedRequest,
        RequestFormat = WebMessageFormat.Json,
        ResponseFormat = WebMessageFormat.Json)]
    string PerformanceCounter(string API_Key);
}

VeraCMS.cs:

    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.PerCall,
         IncludeExceptionDetailInFaults = false, MaxItemsInObjectGraph = 1000)]
    public class VeraCMS : IVeraCMS
    {
        public string PerformanceCounter(string API_Key)
        {
            if (ConfigurationManager.AppSettings["API_key"] != API_Key)
                throw new SecurityException("Access denied");

            var procPercentage = new PerformanceCounter("Processor", "% Processor Time", "_Total");
            procPercentage.NextValue();

            var memPercentage = new PerformanceCounter("Memory", "Available MBytes");
            memPercentage.NextValue();

            const int samplingIntervalMs = 100;
            Thread.Sleep(samplingIntervalMs);

            var json = "{" + String.Format("\"ProcTime\":\"{0}%\",\"AvailMemory\":\"{1}MB\"" ,
                procPercentage.NextValue().ToString(), memPercentage.NextValue().ToString()
                ) + "}";

            return json;
        }
    }
}

Web.config:

  <system.serviceModel>

    <bindings>
      <webHttpBinding>
        <binding name="VeraWAF.WebPages.Interfaces.VeraCMS.Endpoint.Binding" maxReceivedMessageSize="4096" crossDomainScriptAccessEnabled="true" />
      </webHttpBinding>
    </bindings>

    <services>
      <service behaviorConfiguration="VeraWAF.WebPages.Interfaces.VeraCMS.Service.Behavior"
               name="VeraWAF.WebPages.Interfaces.VeraCMS">
        <endpoint address="" behaviorConfiguration="VeraWAF.WebPages.Interfaces.VeraCMS.Endpoint.Behavior"
          binding="webHttpBinding" bindingConfiguration="VeraWAF.WebPages.Interfaces.VeraCMS.Endpoint.Binding"
          contract="VeraWAF.WebPages.Interfaces.IVeraCMS" />
      </service>
    </services>

    <behaviors>
      <endpointBehaviors>
        <behavior name="VeraWAF.WebPages.Interfaces.VeraCMS.Endpoint.Behavior">
          <webHttp defaultOutgoingResponseFormat="Json" />
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="VeraWAF.WebPages.Interfaces.VeraCMS.Service.Behavior">
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>

1 Answer 1

1

You can do basic HTTP authentication like this:

WebServiceHost secureHost

secureHost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
secureHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new ClientValidator(username, password);


// Need to reference System.IdentityModel
public class ClientValidator : UserNamePasswordValidator
{
    private readonly string _password;
    private readonly string _username;

    public ClientValidator(string username, string password)
    {
        _password = password;
        _username = username;
    }

    public override void Validate(string userName, string password)
    {
        if (userName != _username || (password != _password))
        {
            WebFaultException rejectEx = new WebFaultException(HttpStatusCode.Unauthorized);
            rejectEx.Data.Add("HttpStatusCode", rejectEx.StatusCode);
            throw rejectEx;
        }
    }
}

Just keep in mind that your username and password can be easily sniffed if you are not using SSL. You can change the Validate method to fetch username and password from DB or some other service.

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

2 Comments

I don't have the user passord in plaintext, only the hashed password stored in the database. And I don't want the admin to re-authenticate when he/she visits a admin page that shows some info like live performance stats in my example.
Your validator can do whatever you want to check the username and password. How do you intend for your clients to use this service? If they use Chrome, they won't have to enter their username and password for each request

Your Answer

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