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.