Reusing Cookies in Different WCF Web Services
You probably know that to have cookie-based sessions in your WCF service, you have to use one of the *ContextBinding bindings, such as BasicHttpContextBinding. But what if you want to share cookies (such as authentication) among different services? Enter CookieContext and CookieBehaviorAttribute.
First, I created a simple behavior attribute, CookieBehaviorAttribute. that you should apply to on your contract interface. All operations to services with that attribute that are placed inside a CookieContext will share the its cookies. Here's the code:
//sample service interface declaration [CookieBehavior(true)] [ServiceContract(Name = "AuthenticationService", Namespace = @"http://asp.net/ApplicationServices/v20")] public interface IAuthenticationService { [OperationContract] Boolean IsLoggedIn(); [OperationContract] Boolean Login(String username, String password, String customCredential, Boolean isPersistent); [OperationContract] void Logout(); [OperationContract] Boolean ValidateUser(String username, String password, String customCredential); } //behavior attribute [Serializable] [AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = false)] public sealed class CookieBehaviorAttribute : Attribute, IContractBehavior, IEndpointBehavior, IClientMessageInspector { #region Private fields private CookieContainer cookieContainer = new CookieContainer(); #endregion #region Public constructors public CookieBehaviorAttribute() : this(false) { } public CookieBehaviorAttribute(Boolean shared) { this.Shared = shared; } #endregion #region Public properties public Boolean Shared { get; private set; } #endregion #region Private methods private void getCookies(HttpResponseMessageProperty prop, CookieContainer cookieContainer) { if (prop != null) { String header = prop.Headers [ HttpResponseHeader.SetCookie ]; if (header != null) { cookieContainer.SetCookies(new Uri(@"http://someuri.tld"), header); } } } private void setCookies(HttpRequestMessageProperty prop, CookieContainer cookieContainer) { if (prop != null) { prop.Headers.Add(HttpRequestHeader.Cookie, cookieContainer.GetCookieHeader(new Uri(@"http://someuri.tld"))); } } #endregion #region IClientMessageInspector Members void IClientMessageInspector.AfterReceiveReply(ref Message reply, Object correlationState) { HttpResponseMessageProperty prop = reply.Properties [ HttpResponseMessageProperty.Name.ToString() ] as HttpResponseMessageProperty; this.getCookies(prop, (this.Shared == true) ? CookieContext.Current.cookieContainer : this.cookieContainer); } Object IClientMessageInspector.BeforeSendRequest(ref Message request, IClientChannel channel) { HttpRequestMessageProperty prop = request.Properties [ HttpRequestMessageProperty.Name.ToString() ] as HttpRequestMessageProperty; this.setCookies(prop, (this.Shared == true) ? CookieContext.Current.cookieContainer : this.cookieContainer); return (null); } #endregion #region IEndpointBehavior Members void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { clientRuntime.MessageInspectors.Add(this); } void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } void IEndpointBehavior.Validate(ServiceEndpoint endpoint) { } #endregion #region IContractBehavior Members void IContractBehavior.AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } void IContractBehavior.ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime) { endpoint.Behaviors.Add(this); } void IContractBehavior.ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime) { } void IContractBehavior.Validate(ContractDescription contractDescription, ServiceEndpoint endpoint) { } #endregion } //cookie context public class CookieContext: IDisposable { #region Internal fields internal CookieContainer cookieContainer = new CookieContainer(); #endregion #region Private static fields [ThreadStatic] private static CookieContext current = null; #endregion #region Public static constructor public CookieContext() { current = this; } #endregion #region Public static properties public static CookieContext Current { get { return (current); } } #endregion #region IDisposable Members void IDisposable.Dispose() { this.cookieContainer.SetCookies(new Uri(@"http://someuri.tld"), String.Empty); this.cookieContainer = null; current = null; } #endregion }
And some sample code:
IAuthenticationService auth1 = ChannelFactory<IAuthenticationService>.CreateChannel(new BasicHttpBinding/*BasicHttpContextBinding*/(), new EndpointAddress(@"http://localhost:34744/AuthenticationService.svc")); IAuthenticationService auth2 = ChannelFactory<IAuthenticationService>.CreateChannel(new BasicHttpBinding/*BasicHttpContextBinding*/(), new EndpointAddress(@"http://localhost:34744/AuthenticationService.svc")); using (new CookieContext()) { Boolean authenticated = auth1.Login("username", "password", String.Empty, true); Boolean loggedIn1 = auth1.IsLoggedIn(); //true if the username and password are valid Boolean loggedIn2 = auth2.IsLoggedIn(); //also true }
Note that, in order to share cookies, the CookieBehaviorAttribute.Shared property must be true and all calls must be inside a CookieContext scope. Otherwise, this can be used to support cookies in otherwise cookie-ignorant bindings, such as BasicHttpBinding.