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. Bookmark and Share

                             

4 Comments

  • I tried the code but the CookieContext() is't serialized at the client so it's unknown @ clientside. Can you tell me how i can fix this? thanks!

  • Kees:
    Why should it be serializable?
    You must add a reference to the assembly containing it on you client project and execute the sample code I provided. Also, your services must have my CookieBehaviorAttribute applied at the interface level. That's all.
    Regards,
    RP

  • Hi Ricardo,

    I have a question regarding the use of ThreadStatic on CookieContext.Current.

    If I place the code from Program.cs in an aspx page,
    is it safe to assume that the same worker thread handling the aspx page will also handle the MessageInspector code?

    -Mehul

  • Mehul:

    No, I don't think so. Different web requests may come in different threads. I confess I hadn't thought about web applications.

Comments have been disabled for this content.