Implementing Windows Identity Foundation (WIF) into Azure using AppFabric ACS

Requirements: Implement WIF Authentication on a per page basis with Identity and Role support.

Acknowledgement: Vittorio Bertocci – http://blogs.msdn.com/b/vbertocci/– read his eccellente/cool blog and purchase a copy or three of his book:
Programming Windows Identity Foundation – as there is no better resource available to begin with.  I just missed seeing Vittorio in Singapore a few years ago and that continues to be a huge disappointed.  Regarding the book, Programming WIF, I take personal exception to Hervey Wilson’s archaic stance on haircuts and professional exception to the excerpt that “Visual Studio will make (WIF) a breeze” – these statements appear to be more based in propaganda than fact.

Discordia: WIF integration into Azure has yet to mature based on the following observations:

1.  Currently the Windows Identity Foundation assemblies are not available in the Azure Global Assembly Cache (GAC); specifically the Microsoft.IdentityModel.dll and furthermore setting its reference “Copy Local to True” does NOT guarantee that the bits of assembly will be available to your application after cloud deployment.

2.  The SignOutAction FederatedPassiveSignOut of the FederatedPassiveSignInStatus control does not work; both WireShark and Fiddler were used during debugging sessions and no wsignout1.0 message was sent to the Identity Provider (IP) Security Token Service (STS) nor was the Session Token Cookie deleted or expired.

3.  Calls to Azure Storage Tables and Blobs during Page_Load which also contain WIF functions generates an “Unable to find Microsoft.IdentityModel assembly” exception/error and is related to the first Discordia issue.  However a solution is available by adding WIF assemblies to the Azure GAC with startup tasks – details to follow.

Industry Acceptance: Although WIF needs better integration into Azure it is without question the de facto standard for Authentication because it provides a thinner security profile to web sites and services, limits legal liabilities by not storing passwords within applications and associated databases, and may reduce successful “Main In The Middle” (MITM) attacks.

DarkSide: As a developer/entrepreneur/businessman I want to believe Vittorio when he says that there is no such thing as “token cracking” because that means cyberspace has become a safer environment for legitimate enterprises.  However the abundance of BlackHats seems to be increasing daily.  The current tools of choice are: “Yet Another Man in The Middle Automation Script” aka YAMAS, Ettercap, ARPspoof, and SSLstrip.  Here are some links so that you can learn more about potential adversaries:

http://comax.fr/yamas.php
http://ettercap.sourceforge.net/
http://arpspoof.sourceforge.net/
http://www.thoughtcrime.org/software/sslstrip/

Begin: The starting point we used is the Identity Developer Training Kit – April 2011; it can be downloaded from: http://www.microsoft.com/download/en/details.aspx?id=14347

Contained within Identity and the Windows Azure Platform section was two which we found to be quite useful: Federated Authentication in a Windows Azure Web Role Application and Introduction to the AppFabric Access Control Service 2.0.  Our solution contained portions from each lab plus the GAC bug fix.

In the global.asax.cs we add error handling to the OnServiceConfigurationCreated function:

void OnServiceConfigurationCreated(object sender, ServiceConfigurationCreatedEventArgs e)
{
//
// NEW STUFF
//
// Use the <serviceCertificate> to protect the cookies that are
// sent to the client.
//

if (e.ServiceConfiguration.ServiceCertificate == null)
{
return;

throw new ApplicationException(“No site certificate; is it set up in web.config?”);
// Make sure you’ve got the service certificate set up in the web.config:
// <serviceCertificate>
//   <certificateReference x509FindType=”FindByThumbprint” findValue=”<yourCorrespondingThumbprint>”/>
// </serviceCertificate>
}

// End NEW STUFF

List<CookieTransform> sessionTransforms =
new List<CookieTransform>(new CookieTransform[] {
new DeflateCookieTransform(),
new RsaEncryptionCookieTransform(e.ServiceConfiguration.ServiceCertificate),
new RsaSignatureCookieTransform(e.ServiceConfiguration.ServiceCertificate)  });
SessionSecurityTokenHandler sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());
e.ServiceConfiguration.SecurityTokenHandlers.AddOrReplace(sessionHandler);
}

The following web.config example shows modifications made to enable per page authentication rather than the entire site:

<?xml version=”1.0″?>
<configuration>
<configSections>
<section name=”microsoft.identityModel” type=”Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35″ />
</configSections>

<system.web>
<authentication mode=”None” />
<customErrors mode=”Off”/>
<compilation debug=”true” targetFramework=”4.0″>
<assemblies>
<add assembly=”Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35″ />
</assemblies>
</compilation>
<httpRuntime requestValidationType=”SampleRequestValidator” />
</system.web>

<location path=”FederationMetadata”>
<system.web>
<authorization>
<allow users=”*” />
</authorization>
</system.web>
</location>

<location path=”Default.aspx”>
<system.web>
<authorization>
<allow users=”*” />
</authorization>
</system.web>
</location>

<location path=”Members.aspx”>
<system.web>
<authorization>
<deny users=”?” /> <– Force Authentication Before Allowing Access –>
</authorization>
</system.web>
</location>

<system.webServer>
<modules runAllManagedModulesForAllRequests=”true”>
<add name=”WSFederationAuthenticationModule” type=”Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35″ preCondition=”managedHandler” />
<add name=”SessionAuthenticationModule” type=”Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35″ preCondition=”managedHandler” />
</modules>
</system.webServer>
<appSettings>
<add key=”FederationMetadataLocation” value=”https://contosoacs.accesscontrol.windows.net/FederationMetadata/2007-06/FederationMetadata.xml” />
</appSettings>
<microsoft.identityModel>
<service>
<audienceUris>
<add value=”https://www.contoso.com/” />
</audienceUris>
<federatedAuthentication>
<wsFederation passiveRedirectEnabled=”true” issuer=”https://contosoacs.accesscontrol.windows.net/v2/wsfederation” realm=”https://www.contoso.com/” requireHttps=”true” />
<cookieHandler requireSsl=”true” />
</federatedAuthentication>
<applicationService>
<claimTypeRequired>
<claimType type=”http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name” optional=”true” />
<claimType type=”http://schemas.microsoft.com/ws/2008/06/identity/claims/role” optional=”true” />
<claimType type=”http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier” optional=”true” />
</claimTypeRequired>
</applicationService>
<issuerNameRegistry type=”Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35″>
<trustedIssuers>
<add thumbprint=”<Find Service Certificate Thumbprint in ServiceConfiguration.cscfg>” name=”https://ContosoACS.accesscontrol.windows.net/” />
</trustedIssuers>
</issuerNameRegistry>
<serviceCertificate>
<certificateReference x509FindType=”FindByThumbprint” findValue=”<yourCorrespondingThumbprint>”/>
</serviceCertificate>
<certificateValidation certificateValidationMode=”None” />
</service>
</microsoft.identityModel>
</configuration>

Identity Providers:  Three IPs are supported out of the box: Microsoft, Yahoo, and Google.  All return a NameIdentifier though the formats seemed to vary.  Yahoo and Google also provide Name and Email but Microsoft does not.  We decided that if an Email address was available then we would use that as a reference point for User Identification and if Microsoft was the IP then we would set a flag and try to get a verifiable Email address from a user when they create a new account.  What follows is a code frag from Page_Load showing this simple technique:

if (Thread.CurrentPrincipal.IsInRole(“Administrator”))
{
this.SecretContent.Visible = true;
}
else
{
this.SecretContent.Visible = false;
}

string strEmailOrID = “”;
bool bIsMicrosoft = false;

IClaimsIdentity ICidentity = Thread.CurrentPrincipal.Identity as IClaimsIdentity;
strEmailOrID = (from c in ICidentity.Claims
where c.ClaimType == System.IdentityModel.Claims.ClaimTypes.Email
select c.Value).SingleOrDefault();

if (strEmailOrID == null) // Microsoft LiveID
{
strEmailOrID = (from c in ICidentity.Claims
where c.ClaimType == System.IdentityModel.Claims.ClaimTypes.NameIdentifier
select c.Value).SingleOrDefault();

bIsMicrosoft = true;
}

Azure GAC Finale: During this Adventure we arrived at the point where we wanted to integrate the new WIF Authentication code with the existing project codebase and kaboom!  Everything broke.  I happen to live in the tropics where the homes are constructed like haciendas with extremely hard internal walls and banging one’s head (or fist) against them can cause bones to fracture – after weighing the options I decided to meditate instead. Once my heart rate returned to normal I did a search for this problem and over the course of time happened upon a single sentence that made sense.  It was posted by a stackoverflow user named Symmetry and said (paraphrased) “The (Azure GAC) error only occurs on pages that access tablestorage”.  I correctly extrapolated it to mean Azure Storage. Here’s the link:

http://stackoverflow.com/questions/5495531/azure-sdk-1-4-wif-the-c-module-failed-to-load-while-attempting-to-initializ

This pointed us to the link which contained the ultimate solution from Erik Oppedijk:

http://blogs.infosupport.com/blogs/eriko/archive/2011/01/14/adding-assemblies-to-the-gac-in-windows-azure.aspx

We decided to build and run a script which uses the Windows Update packages (MSU).  Currently Windows Azure allows us to select
from two Operating Systems: Server 2008 SP2 (Default) or Server 2008 R2.  Download the appropriate MSU from this link:

http://www.microsoft.com/download/en/details.aspx?id=17331

The modifications made to the ServiceDefinition.csdef file are as follows:

<Startup>
<Task commandLine=”RegisterGAC.cmd” executionContext=”elevated” taskType=”simple”/>
</Startup>

The RegisterGAC.cmd script file contains the following:

@echo off
sc config wuauserv start= demand
wusa.exe “%~dp0Windows6.0-KB974405-x64.msu” /quiet /norestart
sc config wuauserv start= disabled

Both the RegisterGAC.cmd and correct MSU files should be copied to the Bin folder of the Web Role and set the build action to none, and copy to output directory to Copy Always for both files.

Conclusion: Once done and much to our surprise everything worked.  Unfortunately it required more effort than expected to get running and the sign out bug has yet to be resolved.

Leave a comment