In an earlier article, I described how to call the Cognitive Services API from JavaScript. The key parts of the code (using the jQuery library) is shown below:

var subscriptionKey = "cd529ca0a97f48b8a1f3bc36ecd73600";
var imageUrl = $("#imageUrlTextbox").val();
var webSvcUrl = "https://api.projectoxford.ai/face/v1.0/detect?returnFaceId=true&returnFaceLandmarks=true&returnFaceAttributes=age,gender,smile,facialHair,headPose,glasses";
$.ajax({
    type: "POST",
    url: webSvcUrl,
    headers: { "Ocp-Apim-Subscription-Key": subscriptionKey },
    contentType: "application/json",
    data: '{ "Url": "' + imageUrl + '" }'
}).done(function (data) {
        // ... 
        // Code to run when web service returns data...
        // ...
        }); 

Do you spot the problem with this code?

Although it works very well from a functional point of view, it has a security flaw” As with all Cognitive Services APIs, the Face API requires you to pass a key in the header of your HTTP request. Because there is no easy way to hide this key, it is easy for a hacker to steal your key. Using your key, they can make calls and charge them to your account - either using up your quota of free calls or (worse) charging your credit card for their calls.

One way to hide the key is to make the call to Cognitive Services from a custom web service. This web service can be a simple pass-through, but it allows you to hide the key on the server, where it is much more difficult for a hacker to find it.

We can use Web API to create this custom web service. With Web API, is a simple matter to store the Subscription Key in a secure configuration file and retrieve it at run time. The relevant code goes in an ApiController class as shown below.

// POST: api/Face
public IEnumerable Post([FromBody]string imageUrl)
{
    return GetFaceData(imageUrl);
} 
 
public static IEnumerable GetFaceData(string imageUrl)
{
    var webSvcUrl = "https://api.projectoxford.ai/face/v1.0/detect?returnFaceId=true&returnFaceLandmarks=true&returnFaceAttributes=age,gender,smile,facialHair,headPose,glasses";
    string subscriptionKey = ConfigurationManager.AppSettings["SubscriptionKey"];
    if (subscriptionKey == null)
    {
        throw new ConfigurationErrorsException("Web Service is missing Subscription Key");
    }
    WebRequest Request = WebRequest.Create(webSvcUrl);
    Request.Method = "POST";
    Request.ContentType = "application / json";
    Request.Headers.Add("Ocp-Apim-Subscription-Key", subscriptionKey);
    using (var streamWriter = new StreamWriter(Request.GetRequestStream()))
    {
        string json = "{"
        + "\"url\": \"" + imageUrl + "\""
        + "}"; 
 
        streamWriter.Write(json);
        streamWriter.Flush();
        streamWriter.Close(); 
 
        var httpResponse = (HttpWebResponse)Request.GetResponse(); 
 
        string result = "";
        using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
        {
            result = streamReader.ReadToEnd();
        }
        httpResponse.Close(); 
 
        List faces = JsonConvert.DeserializeObject>(result);
        return faces;
    }
} 

Of course, we could also use the .NET library to abstract the call to the Face API, but I wanted to keep this code as close to the original JavaScript code as possible.  This method returns a List of Face objects. The Face object maps to the JSON data returned by the Face API. You can see this class below.

public class Face
{
    public string FaceId { get; set; }
    public FaceRectangle FaceRectangle { get; set; }
    public FaceLandmarks FaceLandmarks { get; set; }
    public FaceAttributes FaceAttributes { get; set; }
}

Now, we can modify our JavaScript code to call our custom API, instead of calling Face API directly, and we can eliminate the need to pass the key from the client-side JavaScript, as shown below:

var imageUrl = $("#imageUrlTextbox").val();
webSvcUrl = "api/face";
$.ajax({
    type: "POST",
    url: webSvcUrl,
    data: JSON.stringify(imageUrl),
    contentType: "application/json;charset=utf-8"
}).done(function (data) { 
// ... 
// Code to run when web service returns data...
// ...
}); 

In this listing, we have removed the Subscription-Key from the Header and we have changed the Web Service URL. This is JavaScript is calling a web service in its own domain, so it is trusted. The default Cross-Site scripting settings of most web servers will keep JavaScript outside this domain from accessing our custom web service.

This pattern works with any of the Cognitive Services APIs. In fact, it works with any API that requires the client to pass a secure key. We can abstract away the direct call to that API and hide the key on the server, making our code more secure.

Code

  • You can find the full code of the sample described above here.
  • You can find the code for the “unprotected” sample here.