Wednesday, October 31, 2007

Finally! Usernames over Transport Authentication in WCF

Sometimes you have to wonder why the most basic features are missing in a v1 product...

Imagine this scenario: You have a public facing web service. You want the widest possible reach and compatibility - so the perfect technologies for that are HTTP, SSL and basic authentication (usernames and passwords) - and your customer accounts are no Windows accounts.

There was no sane way to pull this off in WCF v1 (besides fiddling around with the HTTP pipeline and simulating the basic auth handshake - but that tied you to IIS/ASP.NET).

You may say now - isn't that exactly what TransportWithMessageCredential is supposed to do? Not exactly - because this involves sending a basic WS-Security SOAP header inside of the message. I want simple HTTP basic auth....

(Christian and me had this very scenario at a customer just a few weeks ago...)

The good news is that WCF in .NET 3.5 will finally support this!

How does that work?

a) use transport security (e.g. basicHttpBinding)

<bindings>
  <basicHttpBinding>
    <binding name="secureBasic">
      <security mode="Transport">
        <transport clientCredentialType="Basic" />
      </security>
    </binding>
  </basicHttpBinding>
</bindings>

b) write a username/password validator

public class CustomUsernamePasswordValidator : UserNamePasswordValidator
{
    public override void Validate(string userName, string password)
    {
        if (!AuthenticateUser(userName, password))
            throw new SecurityTokenValidationException("...");
    }
}

c) hook up the validator in a serviceCredentials behavior

<behaviors>
  <serviceBehaviors>
    <behavior name="userName">
      <serviceCredentials>
        <userNameAuthentication customUserNamePasswordValidatorType="..."
                                userNamePasswordValidationMode="Custom" />
      </serviceCredentials>
    </behavior>
  </serviceBehaviors>
</behaviors>

d) send a username/password using the client proxy

ChannelFactory<IService> cf = new ChannelFactory<IService>("Service");
cf.Credentials.UserName.UserName = "bob";
cf.Credentials.UserName.Password = "foo";

IService proxy = cf.CreateChannel();
Console.WriteLine(proxy.Ping());

((IClientChannel)proxy).Close();

WCF | Work in Progress
10/31/2007 1:51:19 PM UTC  #  Comments [32]