About this time last year, I wrote a couple of posts about how to use the “Interceptors” from the REST starker kit for implementing several authentication mechanisms like “SAML”, “Basic Authentication” or “OAuth” in the WCF Web programming model. The things have changed a lot since then, and Glenn finally put on our hands a new version of the Web programming model that deserves some attention and I believe will help us a lot to build more Http oriented services in the .NET stack. What you can get today from wcf.codeplex.com is a preview with some cool features like Http Processors (which I already discussed here), a new and improved version of the HttpClient library, Dependency injection and better TDD support among others.
However, the framework still does not support an standard way of doing client authentication on the services (This is something planned for the upcoming releases I believe). For that reason, moving the existing authentication interceptors to this new programming model was one of the things I did in the last few days.
In order to make authentication simple and easy to extend, I first came up with a model based on what I called “Authentication Interceptors”. An authentication interceptor maps to an existing Http authentication mechanism and implements the following interface,
public interface IAuthenticationInterceptor{ string Scheme { get; } bool DoAuthentication(HttpRequestMessage request, HttpResponseMessage response, out IPrincipal principal);}
An authentication interceptors basically needs to returns the http authentication schema that implements in the property “Scheme”, and implements the authentication mechanism in the method “DoAuthentication”. As you can see, this last method “DoAuthentication” only relies on the HttpRequestMessage and HttpResponseMessage classes, making the testing of this interceptor very simple (There is no need to do some black magic with the WCF context or messages).
After this, I implemented a couple of interceptors for supporting basic authentication and brokered authentication with SAML (using WIF) in my services. The following code illustrates how the basic authentication interceptors looks like.
public class BasicAuthenticationInterceptor : IAuthenticationInterceptor{ Func<UsernameAndPassword, bool> userValidation; string realm; public BasicAuthenticationInterceptor(Func<UsernameAndPassword, bool> userValidation, string realm) { if (userValidation == null) throw new ArgumentNullException("userValidation"); if (string.IsNullOrEmpty(realm)) throw new ArgumentNullException("realm"); this.userValidation = userValidation; this.realm = realm; } public string Scheme { get { return "Basic"; } } public bool DoAuthentication(HttpRequestMessage request, HttpResponseMessage response, out IPrincipal principal) { string[] credentials = ExtractCredentials(request); if (credentials.Length == 0 || !AuthenticateUser(credentials[0], credentials[1])) { response.StatusCode = HttpStatusCode.Unauthorized; response.Content = new StringContent("Access denied"); response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Basic", "realm=" + this.realm)); principal = null; return false; } else { principal = new GenericPrincipal(new GenericIdentity(credentials[0]), new string[] {}); return true; } } private string[] ExtractCredentials(HttpRequestMessage request) { if (request.Headers.Authorization != null && request.Headers.Authorization.Scheme.StartsWith("Basic")) { string encodedUserPass = request.Headers.Authorization.Parameter.Trim(); Encoding encoding = Encoding.GetEncoding("iso-8859-1"); string userPass = encoding.GetString(Convert.FromBase64String(encodedUserPass)); int separator = userPass.IndexOf(':'); string[] credentials = new string[2]; credentials[0] = userPass.Substring(0, separator); credentials[1] = userPass.Substring(separator + 1); return credentials; } return new string[] { }; } private bool AuthenticateUser(string username, string password) { var usernameAndPassword = new UsernameAndPassword { Username = username, Password = password }; if (this.userValidation(usernameAndPassword)) { return true; } return false; }}
This interceptor receives in the constructor a callback in the form of a Func delegate for authenticating the user and the “realm”, which is required as part of the implementation. The rest is a general implementation of the basic authentication mechanism using standard http request and response messages.
I also implemented another interceptor for authenticating a SAML token with WIF.
public class SamlAuthenticationInterceptor : IAuthenticationInterceptor{ SecurityTokenHandlerCollection handlers = null; public SamlAuthenticationInterceptor(SecurityTokenHandlerCollection handlers) { if (handlers == null) throw new ArgumentNullException("handlers"); this.handlers = handlers; } public string Scheme { get { return "saml"; } } public bool DoAuthentication(HttpRequestMessage request, HttpResponseMessage response, out IPrincipal principal) { SecurityToken token = ExtractCredentials(request); if (token != null) { ClaimsIdentityCollection claims = handlers.ValidateToken(token); principal = new ClaimsPrincipal(claims); return true; } else { response.StatusCode = HttpStatusCode.Unauthorized; response.Content = new StringContent("Access denied"); principal = null; return false; } } private SecurityToken ExtractCredentials(HttpRequestMessage request) { if (request.Headers.Authorization != null && request.Headers.Authorization.Scheme == "saml") { XmlTextReader xmlReader = new XmlTextReader(new StringReader(request.Headers.Authorization.Parameter)); var col = SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection(); SecurityToken token = col.ReadToken(xmlReader); return token; } return null; }}This implementation receives a “SecurityTokenHandlerCollection” instance as part of the constructor. This class is part of WIF, and basically represents a collection of token managers to know how to handle specific xml authentication tokens (SAML is one of them).
I also created a set of extension methods for injecting these interceptors as part of a service route when the service is initialized.
var basicAuthentication = new BasicAuthenticationInterceptor((u) => true, "ContactManager");var samlAuthentication = new SamlAuthenticationInterceptor(serviceConfiguration.SecurityTokenHandlers); // use MEF for providing instancesvar catalog = new AssemblyCatalog(typeof(Global).Assembly);var container = new CompositionContainer(catalog);var configuration = new ContactManagerConfiguration(container); RouteTable.Routes.AddServiceRoute<ContactResource>("contact", configuration, basicAuthentication, samlAuthentication);RouteTable.Routes.AddServiceRoute<ContactsResource>("contacts", configuration, basicAuthentication, samlAuthentication);
In the code above, I am injecting the basic authentication and saml authentication interceptors in the “contact” and “contacts” resource implementations that come as samples in the code preview.
I will use another post to discuss more in detail how the brokered authentication with SAML model works with this new WCF Http bits.
The code is available to download in this location.