Wednesday, August 31, 2011

Adding Passwords to a WCF Service

I recently set up a WCF service that required password protection, with user accounts and passwords synchronized with an ASP.NET site using the standard ASP.NET Membership provider. I found several books and a number of blogs that gave helpful examples, but ultimately I felt that I never found the one, single, go-to example that just focused on the topic of adding username and password support in WCF. So this is my humble attempt at a stripped down, laser focused tutorial on the subject.

Prerequisites

Here is a prioritized checklist for your implementation:
  1. Understand that you will need SSL. WCF will not allow you to add username/password authentication to a service unless it is encrypted at the transport layer; this means that you must use the HTTPS protocol and you must obtain an SSL certificate. For those of you who are students or cheapskates, it is possible to generate a self signed SSL certificate. If you're a professional developer with control of your own DNS domain, I recommend that you think about just getting a real trusted certificate for your test server. Given the low cost of a certificate from a trusted authority, this can wind up saving time and therefore money in the long run. In any case, don't even think about proceeding until you've taken care of this issue.
  2. Use the basicHttpBinding. The default binding for a WCF service application in Visual Studio is the wsHttpBinding. If you use the ws binding and add passwords, you get a session based protocol in which the client and server first negotiate a shared secret, then use the shared secret to sign all subsequent messages. This is a triumph of technology and generally not a problem if you can be absolutely sure that only .NET programmers will access your service. However, the minute a PHP, Rails, Java, or Phython team tries to work with your service you'll get an earful: it can be very challenging to set up session based WS authentication with these frameworks. On the other hand, if you use the basic binding, you'll get stateless authentication that behaves much closer to expectations and is much easier to implement on other platforms.
  3. Make sure you understand how the ASP.NET membership service is configured. Both the membership and role providers should have configuration elements in your web.config file (don't rely on the default configuration). Take note of the applicationName configuration attribute and its function (short story: it is possible for multiple user name directories to coexist in the same database using different application names). The main point is: if you want your service to synchronize with a website, but the service is not directly a part of that website, you must make sure that both the membership connection string and the application names match.
Now that we've got the preliminaries out of the way, all we need is the configuration file, right? After all, can't all WCF issues be solved in the configuration file? With that in mind, I've prepared a stripped down configuration that contains only what you need to enable the ASP.NET membership passwords for a WCF service:
<?xml version="1.0"?>
<configuration>
  <connectionStrings>
    <add name="YOUR_CONNECTION_STRING"
         connectionString="ToDo: put a valid connection string here" />
  </connectionStrings>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
    <!-- this is the standard role and membership configuration
        that might already be present in your web.config if
        the local ASP.NET site is using the membership
        framework for page access-->
    <roleManager defaultProvider="AspNetRoleProvider" 
                 enabled="true">
      <providers>
        <clear />
        <add name="AspNetRoleProvider"
             type="System.Web.Security.SqlRoleProvider"
             connectionStringName="YOUR_CONNECTION_STRING"
             applicationName="YOUR_APPLICATION_NAME" />
      </providers>
    </roleManager>
    <membership defaultProvider="AspNetMembershipProvider">
      <providers>
        <clear />
        <add name="AspNetMembershipProvider"
             type="System.Web.Security.SqlMembershipProvider"
             connectionStringName="YOUR_CONNECTION_STRING"
             applicationName="YOUR_APPLICATION_NAME" />
      </providers>
    </membership>
  </system.web>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- no need for http get;
              but https get exposes endpoint over SSL/TLS-->
          <serviceMetadata httpGetEnabled="false" httpsGetEnabled="true"/>
          <!-- the authorization and credentials elements tie
            this behavior (defined as the default behavior) to
            the ASP.NET membership framework-->
          <serviceAuthorization
              principalPermissionMode="UseAspNetRoles"
              roleProviderName="AspNetRoleProvider" />
          <serviceCredentials>
            <userNameAuthentication
                userNamePasswordValidationMode="MembershipProvider"
                membershipProviderName="AspNetMembershipProvider" />
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <!-- this binding configuration stipulates that a
          user name and password are required-->
      <basicHttpBinding>
        <binding>
          <security mode="TransportWithMessageCredential">
            <message clientCredentialType="UserName"/>
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
    <services>
      <!-- in this very simple example we're relying on default 
        binding configuration, behavior, and endpoints-->
      <service name="YOUR_NAMESPACE.YOUR_SERVICE">
        <endpoint binding="basicHttpBinding" 
                  contract="YOUR_NAMESPACE.I_YOUR_SERVICE" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

There's lot's more to say about this topic, including how to use role assignments to authorize service access, but hopefully this will be enough to get you started.
Post a Comment