Share via


2 auth modes on 1 endpoint - Using Multiple Authentication Schemes with ServiceAuthorizationManager

Here’s the general idea: I want a single service endpoint with 2 operations: one can be accessed anonymously, the other requires authentication.  Simple, right?  It should be.  But the trick is that you need multiple authentication schemes specified in IIS, and on your endpoint.  That’s currently only possible with the developer preview, so I’ll use that to demonstrate.

 

First, I create a new WCF Service Application in Visual Studio.  I completely rewrote the contract and implementation to better demonstrate this feature.  Here’s the contract:

 

using System.ServiceModel;

using System.ServiceModel.Web;

namespace MultiAuthWithServiceAuthorizationManager

{

    [ServiceContract]

    public interface IService1

    {

        [WebGet]

        [OperationContract]

  string GetData();

        // Not really pretty; using a GET to POST something, but I want this to be easy to test with a browser.

        [WebGet(UriTemplate="AddData?data={data}")]

        [OperationContract]

        string AddData(string data);

  }

}

The implementation isn’t anything special:

 using System;

namespace MultiAuthWithServiceAuthorizationManager

{

    public class Service1 : IService1

    {

        public string GetData()

        {

            return DateTime.Now + "Some Data.";

   }

        public string AddData(string data)

        {

            return string.Format("{0} data added = \"{1}\"", DateTime.Now, data);

        }

    }

}

The magic happens first with a custom ServiceAuthorizationManager implementation.  I added this class:

using System.ServiceModel;

using System.ServiceModel.Channels;

using System.Web;

namespace MultiAuthWithServiceAuthorizationManager

{

    public class MyServiceAuthorizationManager : ServiceAuthorizationManager

    {

        protected override bool CheckAccessCore(OperationContext operationContext)

        {

            bool operationRequiresAuth = (string)operationContext.RequestContext.RequestMessage.Properties["HttpOperationName"] == "AddData";

            if (operationRequiresAuth)

            {

                if (HttpContext.Current.Request.LogonUserIdentity.IsAuthenticated)

                {

                    return true;

                }

                // If we haven't returned yet, it's unauthorized and it should be authorized.

                HttpResponseMessageProperty messageProp = new HttpResponseMessageProperty();

                messageProp.StatusCode = System.Net.HttpStatusCode.Unauthorized;

                operationContext.OutgoingMessageProperties.Add(HttpResponseMessageProperty.Name, messageProp);

                return false;

            }

            else

            {

                // Don't check auth for the operations that don't require it.

                return true;

            }

        }

    }

}

 

I then have to wire it up in config.  It’s a bit odd - I have to specify the assembly explicitly, but here’s the config.  See the service behaviors for where the manager is hooked up.  (And don’t use this kind of thing in production – security mode should be Transport, not TransportCredentialOnly!  I just wanted to simplify things and not configure https.)

<?xml version="1.0"?>

<configuration>

  <system.web>

    <compilation debug="true" targetFramework="4.5" />

  </system.web>

  <system.serviceModel>

    <behaviors>

      <serviceBehaviors>

        <behavior name="serviceAuthMan">

          <serviceAuthorization serviceAuthorizationManagerType="MultiAuthWithServiceAuthorizationManager.MyServiceAuthorizationManager, MultiAuthWithServiceAuthorizationManager"/>

        </behavior>

      </serviceBehaviors>

    </behaviors>

    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"

        multipleSiteBindingsEnabled="true" />

    <bindings>

      <webHttpBinding>

        <binding name="MyBinding">

       <security mode="TransportCredentialOnly">

            <transport clientCredentialType="InheritedFromHost"/>

          </security>

        </binding>

      </webHttpBinding>

    </bindings>

    <services>

      <service name="MultiAuthWithServiceAuthorizationManager.Service1" behaviorConfiguration="serviceAuthMan">

        <endpoint address="" contract="MultiAuthWithServiceAuthorizationManager.IService1" binding="webHttpBinding" bindingConfiguration="MyBinding"/>

      </service>

    </services>

  </system.serviceModel>

  <system.webServer>

    <modules runAllManagedModulesForAllRequests="true"/>

  </system.webServer>

</configuration>

 

As you can see, I explicitly added the endpoint as well.  So, with this all hooked up, I will create a vdir by going to Properties – Web – Unchecked the “Use IIS Express” and created the virtual directory.  Last thing to do is open up the Authentication feature in IIS for my Application and enable other auth modes besides anonymous.  I’ll just use Basic and Anonymous. 

 

Now, when I browse to https://localhost/MultiAuthWithServiceAuthorizationManager/Service1.svc/GetData, I see some xml like this:

<string xmlns="https://schemas.microsoft.com/2003/10/Serialization/">11/4/2011 4:32:35 PMSome Data.</string>

And when I browse to https://localhost/MultiAuthWithServiceAuthorizationManager/Service1.svc/AddData?data=Some%20new%20data, I am faced with a dialog box:

 

 

Adding my credentials allows the request to go through and I see the response:

<string xmlns="https://schemas.microsoft.com/2003/10/Serialization/">11/4/2011 4:36:02 PM data added = "Some new data"</string>

 

I tried to keep this example simple to demonstrate how a single endpoint can require authentication on some methods but not all.  How you define what methods to require auth on is up to your implementation of the ServiceAuthorizationManager.  There’s a lot going on here, but this is the general idea.