Custom Basic Authentication for RESTful services

It’s very common when developing RESTful services to authenticate users against a proprietary user database. This is generally done with a combination of username and password through http basic authentication. Unfortunately, basic authentication is tied to windows accounts in IIS, which leads us to find out some alternatives or workarounds to support this scenario. WCF 3.5 made possible to authenticate transport credentials with one of the existing UsernamePasswordValidator extensions, however, this approach does not work for IIS hosted services.

Dominick solved this problem with a module plugged directly in the ASP.NET pipeline that works like a charm, but it requires some additional WCF settings and a custom IAuthorization policy to flow the user principal to the WCF service instance. His solution works for ASP.NET applications as well.

This problem can be also solved at a deeper level in the WCF transport model using a message interceptor. The message interceptor can receive a traditional Membership provider in the constructor class, and use it later for authenticating the users. In addition, this message interceptor can also automatically pass the user credentials to the WCF service instance.

As any other message interceptor, it can be configured directly in the WCF service factory with no need of having additional configuration. This can be easily done in the “svc” file hosted in IIS.

<%@ ServiceHost Language="C#" Debug="true" Service="Service" Factory="AppServiceHostFactory" %>

class AppServiceHostFactory : ServiceHostFactory

{

   protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)

   {

     WebServiceHost2 result = new WebServiceHost2(serviceType, true, baseAddresses);

     result.Interceptors.Add(new BasicAuthenticationInterceptor(

                System.Web.Security.Membership.Provider, "foo"));

     return result;

   }

}

BasicAuthenticationInterceptor is the message interceptor I built for this post, it receives the MembershipProvider and a default realm that will be returned together with an 401 http error (Unauthorized) in case the user was not correctly authenticated.

The example is available to download from here.

11 Comments

  • Is there an easy way to consume this service using JQuery?

  • I wish this entry was done in October back when I had to do the same! :)

    Great write up though. Its an interesting fight to get through custom authentication in REST Services.

  • Is There one way config the interceptor in the app or web.config?
    Thank's.

  • Thanks Pablo, This is exactly what I needed to get Basic Auth going. You are the man!

  • I am getting the following error when I run the download in Visual Studio 2010:

    XML Parsing Error: no element found
    Location: http://localhost:7397/RestServices/Service.svc
    Line Number 1, Column 1:

  • Your article is very helpful!
    Could I ask a small question?
    I want my REST service return XML format when I type wrong user/password. like this: https://api.del.icio.us/v1/posts/get
    Thanks a lots.

  • sory, when I click "Cancel" button on alert box.

  • Hi,

    You need to modify the BasicAuthenticationInterceptor code to return a Message when the authentication fails, so that message should have the xml content.

    Thanks
    Pablo.

  • I'm using your implementation effectively however, if you aren't using default binding values for reader quotas and such you need to set those again. It gets complicated, and the behavior value for the datacontractserializer maxItems changes but doesn't have the desired effect. Any thoughts?

    using System;
    using System.ServiceModel;
    using System.ServiceModel.Activation;
    using Microsoft.ServiceModel.Web;
    using Zywave.BKB.Components.Provider.OutlookAddin;
    using Microsoft.ServiceModel.Web.SpecializedServices;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Description;

    namespace App_Code
    {
    //Factory="App_Code.AppServiceHostFactory"
    public class AppServiceHostFactory : ServiceHostFactory
    {
    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
    LargeServiceHost result = new LargeServiceHost(serviceType, baseAddresses);
    result.MaxMessageSize = 2147483647;

    result.Interceptors.Add(new BasicAuthenticationInterceptor(System.Web.Security.Membership.Provider, "Realm"));

    return result;
    }
    }

    public class LargeServiceHost : WebServiceHost2
    {

    public LargeServiceHost(Type serviceType, params Uri[] baseAddresses)

    : base(serviceType, false, baseAddresses)
    {

    }

    protected override void OnOpening()
    {

    var readerQuotas = new System.Xml.XmlDictionaryReaderQuotas();

    readerQuotas.MaxStringContentLength = int.MaxValue;
    readerQuotas.MaxArrayLength = 2147483647;
    readerQuotas.MaxBytesPerRead = 2147483647;
    readerQuotas.MaxDepth = 2147483647;
    readerQuotas.MaxNameTableCharCount = 2147483647;
    readerQuotas.MaxStringContentLength = 2147483647;

    base.OnOpening();


    //change object graph - changes underlying value but doesn't work
    ((ServiceBehaviorAttribute)this.Description.Behaviors[typeof(ServiceBehaviorAttribute)]).MaxItemsInObjectGraph = Int32.MaxValue;

    // change readerQuotas
    foreach (var endpoint in this.Description.Endpoints)
    {
    var binding = new CustomBinding(endpoint.Binding);

    readerQuotas.CopyTo(binding.Elements.Find().ReaderQuotas);

    //this doesn't seem to work either
    foreach (OperationDescription operation in endpoint.Contract.Operations)
    {
    operation.Behaviors.Find().MaxItemsInObjectGraph = Int32.MaxValue;
    }

    endpoint.Binding = binding;

    }

    }

    }

    }

  • Just to follow up, the code I posted is fine, I noticed the error was happening at the client.
    Turns out this doesn't work:





    the dataContractSerializer needs to be first:





  • I've spent a week digging around for something like this.

    Thanks Mate.

Comments have been disabled for this content.