donderdag 9 februari 2012

Azure webrole federated by ACS

Problem
If you deploy a Web Role secured with ACS, and you have two or more instances of your Web Role, you probably have seen this error message:


Key not valid for use in specified state


The problem is that the cookie that is created if you log in with ACS, is encrypted with the machine key by default. If the cookie is created by one of the instances, the cookie can not be read by the other instances because their machine key is different.


Solution
There is a solution to this: encrypt the cookie using a certificate that is known to all instances.


How to
I'll show you how a self-signed certificate can be used to encrypt the ACS cookie.


Create a certificate
First, on your dev machine open IIS and create a self-signed certificate. This certificate can be used on your Azure development environment as well as on the staging and production environment.


Give rights on certificate
To be able to use it on your development machine do the following:
Go to MMC to view the certificates in the Personal store of Local Computer. Right click on the created certificate and choose [All Tasks] [Manage Private Keys...]. Add the username 'Network Service' with read rights.
Network Service is the account under which the Azure simulation environment is executing. It now has rights to read the self-signed certificate.


Add certificate to config
Next we have to use this certificate in our Web Role. Open the ServiceDefinition.csdef file. Add the following code under the WebRole node:



<Certificates>
   <Certificate name="CookieEncryptionCertificate"
                storeLocation="LocalMachine"
                storeName="My" />
</Certificates>




Now your definition file should look something like this:



<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="AutofacTestAzure"
                   xmlns="http://schemas.micros[...]">
   <WebRole  name="[name]" vmsize="Small">
      <Certificates>
         <Certificate name="CookieEncryptionCertificate"
                      storeLocation="LocalMachine"
                      storeName="My/>
      </Certificates>
...
   </ WebRole >
< ServiceDefinition >




Add the following text to all cscfg-files where you want to use the certificate:



<Certificates>
   <Certificate name="Cookiethumbprint="[thumbprint] thumbprintAlgorith="sha1/>
</Certificates >

You can find the [thumbprint] value if you view the Details of the certificate in IIS. Make sure you remove the invisble space at the start of the thumbprint value, after you paste it into the xml.
Example thumbprint value "5a 0a e8 80 f4 ..."
At this position "(here)5a 0a e8 80 f4 ..." you'll notice an invisible space. Make sure you remove that.

Use certificate in code
We can now make some changes in code, so the certificate will be used to encrypt the cookie.
Add this code to the Application_Start event in the global.asax:

FederatedAuthentication.ServiceConfigurationCreated += FedAuthServiceConfigCreated;

So it will look like this:


protected void Application_Start()
{
   FederatedAuthentication.ServiceConfigurationCreated +=
      FedAuthServiceConfigCreated;
   ...
}

Add this method to the global.asax.cs:

private void FedAuthServiceConfigCreated(
   object sender, ServiceConfigurationCreatedEventArgs e)
{
   // Use the <serviceCertificate> to protect the cookies
   // that are sent to the client.
   var readonlyCookietransformList = new CookieTransform[]
   {
       new DeflateCookieTransform(),
       new RsaEncryptionCookieTransform(e.ServiceConfiguration.ServiceCertificate)
   }.ToList().AsReadOnly();


   var sessionSecurityTokenHandler =
       new SessionSecurityTokenHandler(readonlyCookietransformList);


   e.ServiceConfiguration.SecurityTokenHandlers
      .AddOrReplace(sessionSecurityTokenHandler);
}



If you press F5 now, you'll see everything works locally.


Time to get things working in the cloud
There's only one thing we have to do to get this working in the cloud, and that's to upload the certificate to our Hosted Service.
First, go to the created certificate in IIS and export it to a .pfx file. Next go to the management portal and import the certificate to the Certificates of the Hosted Service.




That's all. You can upload your Web Role to Azure and everything works.