Development With A Dot

Blog on development in general, and specifically on .NET

Sponsors

News

My Friends

My Links

Permanent Posts

Portuguese Communities

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

Comments

Kees Schouten said:

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!

# February 12, 2011 2:31 PM

Ricardo Peres said:

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

# February 13, 2011 3:58 AM

Mehul Patel said:

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

# May 9, 2013 1:32 PM

Ricardo Peres said:

Mehul:

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

# May 10, 2013 4:38 AM