Monday, October 26, 2009

Flowing ASP.NET Forms authentication cookie to WCF

This being my first blog,I would like to start off with something that I love. WCF!!!This is about flowing ASP.NET Forms authentication to WCF service. Once successfully authenticated, forms authentication data is stored as an encrypted cookie. And since the encryption is machine dependent its important to have the validationKey and decryptionKey same in both the web.configs(web app and WCF app).

Check this MSDN link to know more about configuring machineKey for asp.net forms authentication.

1.)Create a validationkey and decryptionkey. Run the below program two times. The value generated first time can be used as the validationKey and the second one machineKey in the web.configs.

static void Main(string[] args)
{
int len = 48;

byte[] buff = new byte[len / 2];
RNGCryptoServiceProvider rng = new
RNGCryptoServiceProvider();
rng.GetBytes(buff);
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < buff.Length; i++)
sb.Append(string.Format("{0:X2}", buff[i]));
Console.WriteLine(sb);

}

2.)Configure the web.configs(wcf and web app) to use the generated keys as follows.

<system.web/>
<span style="font-weight:bold;"/> <machineKey validationKey="D4E763ABFD89DAAD50620E89E11FFAB7AD94AF9EDE264526" decryptionKey="5F0A4D7396E35CF534F0B404F481CA70C2C5A43071729BE4" decryption="3DES"/>
</span>

3.)Configure the WCF service to run in ASP NET compatibility mode.
This is to be done in two place. One in wcf service web.config as follows

<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>

And

in the service behaviour
[AspNetCompatibilityRequirements(RequirementsMode=AspNetCompatibilityRequirementsMode.Allowed)]
public class Service1 : IService1
{

4.)Configure forms authentication in asp.net config

<authentication mode="Forms">
<forms name="appNameAuth" path="/" loginUrl="login.aspx" protection="All" timeout="30">
<credentials passwordFormat="Clear">
<user name="jeff" password="test" />
<user name="mike" password="test" />
</credentials>
</forms>
</authentication>
<authorization>
<deny users="?" />
</authorization>
5.)In login.aspx

if (FormsAuthentication.Authenticate(txtUser.Text, txtPassword.Text))
FormsAuthentication.RedirectFromLoginPage(txtUser.Text, chkPersistLogin.Checked);
else
ErrorMessage.InnerHtml = "Something went wrong... please re-enter your credentials...";
This sets the authentication cookie on successful login.

6.)Now send the cookie in WCF request http header

HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers.Add(HttpRequestHeader.Cookie, FormsAuthentication.GetAuthCookie(User.Identity.Name, false).Value);

ServiceReference1.Service1Client serviceClient = new WebApplication1.ServiceReference1.Service1Client();

using (OperationContextScope scope = new OperationContextScope(serviceClient.InnerChannel))
{
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
string s =serviceClient.GetData(2);
}

7.)At the service side you can read the cookie inside the operation as follows

HttpRequestMessageProperty g = OperationContext.Current.IncomingMessageProperties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
string s = g.Headers.Get(HttpRequestHeader.Cookie.ToString());
FormsAuthenticationTicket tick = FormsAuthentication.Decrypt(s);


Working sample can be downloaded from here

Let me know on any issues. Happy coding :)

1 comment: