View  Edit  Attributes  History  Attach  Print  Search

Secure WCF Services with Authentication Service

In .Net Framework 3.5, ASP.NET comes with WCF authentication service [1]

The Windows Communication Foundation (WCF) authentication service enables you to use ASP.NET membership to authenticate users from any application that can send and consume a SOAP message.

You can enable the service [2] to make use of the custom membership provider. However, there are some missing pieces if you want a single service to serve your .Net client and Silverlight client. It is because

  1. WCF service authentication is different from ASP.NET forms authentication
  2. Silverlight does not have the ClientCredentials class
  3. .Net WCF does not manage cookies

If you host all your services in IIS with HTTP bindings, the easiest way is to make use of the HttpContext in ASP.NET.

Global.asax.cs (Web Application)

If you do not have customize authentication, all you need to do is filling in the Application_AuthenticateRequest by setting the HttpContext.Current.User

public class Global : System.Web.HttpApplication
    {
        // other methods snipped...
        protected void Application_AuthenticateRequest(object sender, EventArgs e)
        {
            HttpCookie ticketCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
            if (null == ticketCookie)
            {
                return;
            }

            FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(ticketCookie.Value);
            if (null != ticket)
            {
                HttpContext.Current.User = new GenericPrincipal(new FormsIdentity(ticket), null);
            }
        }
    }

PrimeService.cs (Web Application)

Then in your protected service, you can check to see if the user is authenticated

// Enabling the authentication service, all other WCF services in your web application will have to compatible with ASP.NET
    // http://msdn.microsoft.com/en-us/library/aa702682.aspx
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class PrimeService : IPrimeService
    {
        public int NextPrime(int n)
        {
            // HttpContext.Current.User.Identity.IsAuthenticated is true because
            // Application_AuthenticateRequest set the authenticated user into the context
            if (!HttpContext.Current.User.Identity.IsAuthenticated)
            {
                throw new FaultException<SecurityAccessDeniedException>(new SecurityAccessDeniedException());
            }
            // calculate prime number
        }
    }

Program.cs (WCF Client)

Unlike Silverlight, .Net application does not handle the cookie for you. You will need to

  1. Get the Set-Cookie headers from the authentication service
  2. Construct the cookies from the headers
  3. Set the cookies to the secured service
class Program
    {
        static void Main(string[] args)
        {
                AuthenticationServiceClient asc = new AuthenticationServiceClient();

                string cookies = "";
                using (OperationContextScope scope = new OperationContextScope(asc.InnerChannel))
                {
                    asc.Login(username, password, null, false);
                    var p = OperationContext.Current.IncomingMessageProperties;
                    var responseProperties = p[HttpResponseMessageProperty.Name] as HttpResponseMessageProperty;
                    cookies = responseProperties.Headers[HttpResponseHeader.SetCookie];
                }

                cookies = ConstructCookies(cookies);

                PrimeServiceClient psc = new PrimeServiceClient();
                using (OperationContextScope scope = new OperationContextScope(psc.InnerChannel))
                {
                    var p = new HttpRequestMessageProperty();
                    OperationContext.Current.OutgoingMessageProperties.Add(HttpRequestMessageProperty.Name, p);
                    p.Headers.Add(HttpRequestHeader.Cookie, cookies);
                    Console.WriteLine(psc.NextPrime(3));
                }

                using (OperationContextScope scope = new OperationContextScope(asc.InnerChannel))
                {
                    var p = new HttpRequestMessageProperty();
                    OperationContext.Current.OutgoingMessageProperties.Add(HttpRequestMessageProperty.Name, p);
                    p.Headers.Add(HttpRequestHeader.Cookie, cookies);
                    asc.Logout();
                }
        }

        private static string ConstructCookies(string cookie)
        {
            // snipped... we will use the extension methods below
        }
    }

Extension methods (WCF Client)

Handling all those WCF properties is a bit tedious. The following extension class will simplify the process

public static class Extensions
    {
        public static string GetOutgoingCookies(this OperationContext context)
        {
            var props = context.IncomingMessageProperties;
            var prop = props[HttpResponseMessageProperty.Name] as HttpResponseMessageProperty;
            var cookies = prop.Headers[HttpResponseHeader.SetCookie];
            return ConstructCookies(cookies);
        }

        public static void SetOutgoingCookies(this OperationContext context, string cookies)
        {
            var prop = new HttpRequestMessageProperty();
            prop.Headers.Add(HttpRequestHeader.Cookie, cookies);
            context.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = prop;
        }

        private static string ConstructCookies(string incomingCookies)
        {
            var cookies = incomingCookies.Split(new char[] { ',', ';' });
            StringBuilder buffer = new StringBuilder(incomingCookies.Length * 10);
            foreach (var entry in cookies)
            {
                if (entry.IndexOf("=") > 0 && !entry.Trim().StartsWith("path") && !entry.Trim().StartsWith("expires"))
                {
                    buffer.Append(entry).Append("; ");
                }
            }
            if (buffer.Length > 0)
            {
                buffer.Remove(buffer.Length - 2, 2);
            }
            return buffer.ToString();
        }
    }

Revised - Program.cs (WCF Client)

Now we have cleaner code and the extension methods can be reused as many time as you wanted. We cannot get rid of the operation context scope, however.

class Program
    {
        static void Main(string[] args)
        {
                AuthenticationServiceClient asc = new AuthenticationServiceClient();

                string cookies = "";
                using (OperationContextScope scope = new OperationContextScope(asc.InnerChannel))
                {
                    asc.Login(username, password, null, false);
                    cookies = OperationContext.Current.GetOutgoingCookies();
                }

                PrimeServiceClient psc = new PrimeServiceClient();
                using (OperationContextScope scope = new OperationContextScope(psc.InnerChannel))
                {
                    OperationContext.Current.SetOutgoingCookies(cookies);
                    Console.WriteLine(psc.NextPrime(3));
                }

                using (OperationContextScope scope = new OperationContextScope(asc.InnerChannel))
                {
                    OperationContext.Current.SetOutgoingCookies(cookies);
                    asc.Logout();
                }
        }
    }