Question

Is there a way to require an API key in the URL / or some other way of passing the service a private key in order to grant access to the data?

I have this right now...

using System;
using System.Data.Services;
using System.Data.Services.Common;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Web;
using Numina.Framework;
using System.Web;
using System.Configuration;

[System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class odata : DataService {


    public static void InitializeService(DataServiceConfiguration config) {

        config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
        //config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
    }

    protected override void OnStartProcessingRequest(ProcessRequestArgs args) {

        HttpRequest Request = HttpContext.Current.Request;
        if(Request["apikey"] != ConfigurationManager.AppSettings["ApiKey"])
            throw new DataServiceException("ApiKey needed");

        base.OnStartProcessingRequest(args);
    }
} 

...This works but it's not perfect because you cannot get at the metadata and discover the service through the Add Service Reference explorer. I could check if $metadata is in the url but it seems like a hack. Is there a better way?

Was it helpful?

Solution

I would suggest using the authorization header to pass the apiKey instead of passing it in the query string. That's what it is there for and it help's to keep api keys out of log files.

I don't think there is anything really wrong with checking for the presence of '$metadata' in the url. You are writing the server side code, and the server owns the URI space, so making decisions based on text in the request url is what the server is all about. You could use something like,

  if (requestUrl.Segments.Last().Replace('/','') != '$metadata') 

instead of searching the entire uri string, if it makes it feel less icky!

OTHER TIPS

Looks like the technique presented in this video works well even in WCF Data Services. You create a custom subclass of ServiceAuthorizationManager (see MSDN), override CheckAccessCore(), and register it in web.config.

I got it to work by passing a key in a HTTP header of the request. The OperationContext passed to CheckAccessCore doesn't give you a way to grab the HTTP Request headers, but you can get them via HttpContext.Current.Request.Headers. You can then get the proper header out of that collection and check it however you need to.

Here is the necessary registration in web.config:

<system.serviceModel>
  <behaviors>
      <serviceBehaviors>
          <behavior>
              <serviceAuthorization serviceAuthorizationManagerType="FullyQualifiedTypeNameHere, ProjectNameHere" />
          </behavior>
      </serviceBehaviors>
  </behaviors>

UPDATE: I was wrong about being able to get headers out of HttpContext.Current.Request.Headers; HttpContext.Current is null when running in IIS (but interestingly not when debugging). Instead, use WebOperationContext.Current.IncomingRequest.Headers as per this question.

UPDATE 2: HttpContext.Current is only null when you're not running WCF in ASP.NET Compatibility mode. You can turn this on by adding the following line to web.config at the application level in the system.serviceModel node:

<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>

Also add this above the implementation of your service, if you have a vanilla WCF service running in addition to the ADO.NET service:

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]

Then you can get HttpContext.Current.Request.Headers and all the other stuff provided by the HttpRequest class.

You can check the request type and let wsdl calls go through with out the api key.

I am not sure what your api goals are but you could use a client certificate.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top