|
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
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
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(); } } } |