(Edited on 02/26/2012: WCF Web API is now ASP.NET Web API, which introduced some breaking changes. However, I’ve a new post describing how to enable HTTPS with self-hosting, on the ASP.NET Web API Beta)
(Edited on 09/24/2011: There is a new version of the code below, for the Preview 5 release. The major changes are on the configuration model.)
This is the third post on a series about the new WCF Web API – Preview 4.
In the last post, I described how to create self hosted services. In this post, I show how to use HTTPS and Basic Authentication for these services.
Self-hosting and HTTPS
Both self-hosting and IIS-hosting use the HTTP.sys windows component for HTTP request listening. This HTTP.sys component handles the transport level issues, such as the SSL protocol.
One of the configurations that must be done at the HTTP.sys level is the definition of the server certificate and private key, since the SSL protocol handshake is performed by this component.
When using IIS, the HTTP.sys configuration is done via the IIS manager. However, when self-hosting, the netsh command line utility must be used.
So, the first step is to register the server side certificate using the following command
netsh http add sslcert ipport=0.0.0.0:port certhash=thumbprint appid={app-guid}
where
- port is the listening port (e.g. 443); the special IP address 0.0.0.0 matches any IP address for the local machine;
- thumbprint is the certificate’s SHA-1 hash, represented in hexadecimal;
- app-guid is any GUID, used to identity the owning application.
This command allows for other optional parameters, such as clientcertnegotiation, that define complementary SSL aspects.
For more information about this subject, I recommend:
- http://www.leastprivilege.com/WalkthroughSettingUpASelfHostedWCFServiceWithSSL.aspx
- http://blogs.msdn.com/b/drnick/archive/2006/04/14/configuring-http.aspx
Endpoints and HTTPS
In order for a WCF endpoint to use HTTPS, instead of plain HTTP, the endpoint’s binding must be properly defined. The new HttpBinding has a constructor overload that receives an HttpBindingSecurityMode. The options are:
- None – no security;
- Transport –both authentication and transport level protection, i. e., HTTPS;
- TransportCredentialOnly – only authentication.
Endpoints and HTTP Basic Authentication
The authentication mechanism (e.g. HTTP Basic Authentication or client certificates) is also defined at the binding, via the Security.Transport.ClientCredentialType property.
The credential validation policy, username and password in the case of HTTP Basic Authentication, is defined at the service host, using the Credentials.UserNameAuthentication property.
The following code excerpt shows both the binding and host configuration.
using (var host = new HttpServiceHost(typeof(TheResourceClass), new string[0]))
{
var binding = new HttpBinding(HttpBindingSecurityMode.Transport);
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
var ep = host.AddServiceEndpoint(typeof(TheResourceClass),
binding,
"https://localhost:8435/greet");
ep.Behaviors.Add(new HttpBehavior()
{
OperationHandlerFactory = new MyOperationHandlerFactory()
});
host.Credentials.UserNameAuthentication.UserNamePasswordValidationMode =
UserNamePasswordValidationMode.Custom;
host.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new MyCustomValidator();
host.Open();
Console.WriteLine("Service is opened, press any key to continue");
Console.ReadKey();
}
The custom validator derives from UserNamePasswordValidator
class MyCustomValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if(!credentials are valid)
throw new FaultException("Unknown Username or Incorrect Password");
}
}
Accessing the user’s identity
The final issue regards the access to the user’s identity, obtained from the authentication process. It would be nice if the request handling operation could receive this information as a parameter.
[ServiceContract]
class TheResourceClass
{
[WebGet(UriTemplate = "")]
HttpResponseMessage GetGreetings(IPrincipal principal)
{
return new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("hello "+principal.Identity.Name)
};
}
}
In above example, the GetGreetings operation receives an IPrincipal with the user’s identity. This parameter is injected into the operation by an operation handler, which is a new WCF Web API concept. This new concept alone deserves one or more future posts, so here I will only present the required code.
class PrincipalFromBasicAuthenticationOperationHandler : HttpOperationHandler<HttpRequestMessage,IPrincipal>
{
public PrincipalFromBasicAuthenticationOperationHandler() : base("Principal") { }
public override IPrincipal OnHandle(HttpRequestMessage input)
{
if (input.Headers.Authorization == null || input.Headers.Authorization.Scheme != "Basic")
{
// If properly configured, this should never happen:
// this OperationHandler should only be used when
// Basic authorization is required
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}
var encoded = input.Headers.Authorization.Parameter;
var encoding = Encoding.GetEncoding("iso-8859-1");
var userPass = encoding.GetString(Convert.FromBase64String(encoded));
int sep = userPass.IndexOf(':');
var username = userPass.Substring(0, sep);
var identity = new GenericIdentity(username, "Basic");
return new GenericPrincipal(identity, new string[] { });
}
}
The above operation handler extracts the user name from the “Authorization” header, whose credentials were previously validated via the custom validator, and creates a GenericPrincipal with that name.
Next, an operation handler factory is required to register this new operation handler.
class MyOperationHandlerFactory : HttpOperationHandlerFactory
{
protected override Collection<HttpOperationHandler> OnCreateRequestHandlers(ServiceEndpoint endpoint, HttpOperationDescription operation)
{
var coll = base.OnCreateRequestHandlers(endpoint, operation);
if (operation.InputParameters.Any(p => p.Type.Equals(typeof(IPrincipal))))
{
var binding = endpoint.Binding as HttpBinding;
if (binding != null && binding.Security.Transport.ClientCredentialType == HttpClientCredentialType.Basic)
{
coll.Add(new PrincipalFromBasicAuthenticationOperationHandler());
}
}
return coll;
}
}
Notice that the PrincipalFromBasicAuthenticationOperationHandler is only inserted if the operation requires an IPrincipal and the endpoint is using HTTP Basic Authentication.
Finally, the factory is configured into the HttpBehavior used when defining the endpoint.
var ep = host.AddServiceEndpoint(typeof(TheResourceClass), binding, "https://localhost:8435/greet");
ep.Behaviors.Add(new HttpBehavior()
{
OperationHandlerFactory = new MyOperationHandlerFactory()
});
Concluding remarks
- Security is one of the WCF Web API where more changes are expected in the future. For example, the current sample uses the built in support for HTTP Basic Authentication provided by the “old” WCF HTTP transport channel. In the future, this functionality may be provided by other means (e. g. via a HttpMessageChannel/HttpMessageHandler).
- Configuration is another area expected to change in the future. In this sample, I configure the endpoint directly, via the HttpBehavior. In the future, this may not be the preferred or adequate way to do this.
- Unfortunately, the way Basic Authentication is currently implemented by the transport channel has the following issue: after the first invalid (username, password) pair, the response will be 403 and not 401. This means that the password would not be requested again by a typical user-agent. See http://blogs.msdn.com/b/drnick/archive/2010/02/02/fix-to-allow-customizing-the-status-code-when-validation-fails.aspx for more details.