Hello, Claims!

This a step-by-step guide for converting a ASP.NET web application to claims-based authentication using Windows Identity Foundation and ADFS 2.0. It assumes you have a development environment consisting of Visual Studio 2010 with ASP.NET MVC 3 and Windows Identity Foundation SDK. You also need ADFS 2.0  – in my case I installed it on a virtual machine (called testad1) which is also domain controller (domain test1.se).

(Depending on your network setup, you may have to enter the FQN of the virtual machine, testad1.test1.se in my case, in the host C:\Windows\System32\Drivers\etc\hosts file.)

Create a Web Application with Forms Authentication

Create an ASP.NET MVC 3 Web Application (HelloClaimsWeb) and choose the Internet Application template and the Razor view engine.

Make sure IIS Default Web Site has application pool ASP.NET 4.0.

Publish the application to IIS.

image

Test the web application (https://hostname/HelloClaimsWeb/). Notice that the user name is displayed in the upper right corner.

Enable the World Wide Web Services rules in Windows Firewall.

Test the web application from the test domain (virtual machine).

Convert to Claims Based Authentication

Run the Federation Utility:

Then, publish the application to IIS again.

Changes Made by the Federation Utility

The utility is not some kind of magic – it makes some changes to web.config. From top to bottom:

It adds a new section in configSections:

  <configSections>
      <section name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  </configSections>

(More on this later.)

It adds a new application setting which points to the STS federation metadata document location:

    <add key="FederationMetadataLocation" value="https://testad1.test1.se/FederationMetadata/2007-06/FederationMetadata.xml" />

Then there is an important change:

    <authentication mode="Forms" />

is changed to

    <authentication mode="None" />

This is because authentication is moved to a different part of the http pipeline. The following http modules are added in <system.web>:

    <httpModules>
         <add name="WSFederationAuthenticationModule" type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
         <add name="SessionAuthenticationModule" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    </httpModules>

The WSFederationAuthenticationModule redirects the user to the identity provider’s logon page. It also parses and validates the security token that is posted back. This module writes an encrypted cookie to avoid repeating the logon process. The SessionAuthenticationModule detects the logon cookie, decrypts it, and constructs the ClaimsPrincipal object.

These modules are also added to <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>

Then, there is the large microsoft.identitymodel section:

  <microsoft.identityModel>
     <service>
       <audienceUris>
         <add value="https://<hostname>/HelloClaimsWeb/" />
       </audienceUris>
       <federatedAuthentication>
         <wsFederation passiveRedirectEnabled="true" issuer="https://testad1.test1.se/adfs/ls/" realm="https://stc11118m1.softronic.se/HelloClaimsWeb/" requireHttps="true" />
         <cookieHandler requireSsl="true" />
       </federatedAuthentication>
       <applicationService>
         <claimTypeRequired>
           <!--Following are the claims offered by STS 'http://TestAD1.test1.se/adfs/services/trust'. Add or uncomment claims that you require by your application and then update the federation metadata of this application.—>
           <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/emailaddress" optional="true" />—>
           <!--<claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname" optional="true" />—>
           <!--<claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" optional="true" />—>
           <!--<claimType type="http://schemas.xmlsoap.org/claims/CommonName" optional="true" />—>
           <!--<claimType type="http://schemas.xmlsoap.org/claims/EmailAddress" optional="true" />—>
           <!--<claimType type="http://schemas.xmlsoap.org/claims/Group" optional="true" />—>
           <!--<claimType type="http://schemas.xmlsoap.org/claims/UPN" optional="true" />—>
           <!--<claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname" optional="true" />—>
           <!--<claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier" optional="true" />—>
           <!--<claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" optional="true" />—>
           <!--<claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant" optional="true" />—>
           <!--<claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod" optional="true" />—>
           <!--<claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/denyonlysid" optional="true" />—>
           <!--<claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/denyonlyprimarysid" optional="true" />—>
           <!--<claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/denyonlyprimarygroupsid" optional="true" />—>
           <!--<claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid" optional="true" />—>
           <!--<claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/primarygroupsid" optional="true" />—>
           <!--<claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid" optional="true" />—>
           <!--<claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname" optional="true" />—>
         </claimTypeRequired>
       </applicationService>
       <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
         <trustedIssuers>
           <add thumbprint="BA264CE398B297029466B7C547D4A2FB8B285991" name="http://TestAD1.test1.se/adfs/services/trust" />
         </trustedIssuers>
       </issuerNameRegistry>
       <certificateValidation certificateValidationMode="None" />
     </service>
   </microsoft.identityModel>

Configure ADFS 2.0

If you try to browse the web application now, you will get an error.

In the AD FS 2.0 Management console, add a new relying party trust.

  • Choose Import data about the relying party from a file and specify the file with path <web project folder>\FederationMetadata\2007-06\FederationMetadata.xml.
  • Type a display name, e.g. HelloClaimsWeb.

Add a new transform rule:

  • Select claim rule template Send LDAP Attributes as Claims.
  • As claim rule name, type LDAP attributes.
  • Select Active Directoryas attribute store.
  • Map from Display-Name to Name and from Token-Groups – Unqualified Names to Role.

Code Changes

Modify the home page to display the collection of claims as follows:

Add a reference to Microsoft.IdentityModel.

Change HomeController.Index with the following code:

            var principal = Thread.CurrentPrincipal;
            var identity = principal.Identity as IClaimsIdentity;
            var claims = identity.Claims;
            return View(claims);

Change the index view (Index.cshtml) as follows:

<p>    
    @{
        var grid = new WebGrid(Model);
        @grid.GetHtml(columns: grid.Columns(grid.Column("ClaimType"), grid.Column("Value")));
    }
</p>

Now if you run the web application from the virtual machine, you will get the following error:

A potentially dangerous Request.Form value was detected from the client (wresult=”<t:RequestSecurityTo…”).

This is described in detail in Request Validation – Preventing Script Attacks and the solution in this article: http://social.technet.microsoft.com/wiki/contents/articles/windows-identity-foundation-wif-a-potentially-dangerous-request-form-value-was-detected-from-the-client-wresult-quot-lt-t-requestsecurityto-quot.aspx

Moving the Web Application to the Cloud

Create Windows Azure Project

Add a new project of type Windows Azure and call it HelloClaimsCloud. Right-click on Roles and add Web Role Project in Solution.

Right-click on the newly added role and select Properties. Change VM size to Extra small.

Follow the following guide to add MVC dependencies: http://msdn.microsoft.com/en-us/library/hh369932.aspx#PublishMVC

image

You must now delete the four WebMatrix files in _bin_DeployableAssemblies; otherwise the web application tries to redirect to ~/Account/Login.

Then, set reference Microsoft.IdentityModel property Copy Local to True.

Create and Use SSL Certificate

Start a Visual Studio command prompt and create a certificate using makecert:
makecert -r -pe -n "CN=helloclaims.cloudapp.net" -b 01/01/2010 -e 01/01/2036 -eku 1.3.6.1.5.5.7.3.1 -ss my -sr localMachine -sky exchange -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12
Right-click the HelloClaimsWeb role and select properties. Select the Certificates tab. Add the newly created certificate. Then switch to the Endpoints tab and add a new endpoint with protocol https, port 443 and the certificate just added.

Then export the certificate, including private key, and upload it as a service certificate using the Windows Azure Platform Management Portal.

Reconfigure the Application

In web.config, there are a couple of entries that specifies the application URL. These must be changed. For that purpose, create a new project and solution configuration and call it e.g. Cloud. Right-click on web.config and select Add Config Transforms. Then, edit Web.Cloud.config and add the following before </configuration>:

  <microsoft.identityModel>
    <service>
      <audienceUris>
        <add value="https://helloclaims.cloudapp.net/" xdt:Transform="Replace"/>
      </audienceUris>
      <federatedAuthentication>
        <wsFederation realm="https://helloclaims.cloudapp.net/" xdt:Transform="SetAttributes(realm)"/>
      </federatedAuthentication>
    </service>
  </microsoft.identityModel>

Build and deploy this configuration to Windows Azure.

Configure ADFS 2.0

In the AD FS 2.0 Management console, add a new relying party trust.

  • Take a copy of the existing application federation metadata file (<web project folder>\FederationMetadata\2007-06\FederationMetadata.xml).
  • Edit the copy and change all three instances of the old URL (https://hostname/HelloClaimsWeb/) to the cloud URL (https://helloclaims.cloudapp.net/ in my case).
  • Choose Import data about the relying party from a file and specify the new modified file.
  • Type a display name, e.g. HelloClaimsCloud.

Add the same transform rule as before:

  • Select claim rule template Send LDAP Attributes as Claims.
  • As claim rule name, type LDAP attributes.
  • Select Active Directory as attribute store.
  • Map from Display-Name to Name and from Token-Groups – Unqualified Names to Role.

References

Claims-Based Identity for Windows

AD FS 2.0 Federation with a WIF Application Step-by-Step Guide

Single Sign-On from Active Directory to a Windows Azure Application

Securing a WCF Service in Windows Azure with SSL

It is easy to find articles on how to create https (SSL) endpoints in Windows Azure services, e.g. this one. But I didn’t find information on how to configure the actual service, so I had to experiment. Here is a summary of what is needed with .NET Framework 4.

Http Endpoint

With .NET Framework 4, you can skip declaring your service and endpoints in the configuration file. Default is an endpoint with basicHttpBinding. But you you probably want to enable metadata publishing using a service behavior:

    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
          <serviceMetadata httpGetEnabled="true" />
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>

Https Endpoint

To secure your service with SSL (https), you must create a binding configuration, a service definition and an endpoint using the binding configuration like this:

    <bindings>
      <basicHttpBinding>
        <binding name="SecureBasic">
          <security mode="Transport" />
        </binding>
      </basicHttpBinding>
    </bindings>
    <services>
      <service name="Namespace.TestService">
        <endpoint binding="basicHttpBinding" bindingConfiguration="SecureBasic" name="basicHttp" contract="Namespace.ITestService" />
      </service>
    </services>

To publish meta data, you would want to change httpGetEnabled=”true” to httpsGetEnabled=”true”:

<serviceMetadata httpsGetEnabled="true" />

Http and Https Endpoints

If you, for testing purposes, want to have both http and https bindings, you add two endpoints:

<endpoint binding="basicHttpBinding" bindingConfiguration="" name="basicHttp" contract="Namespace.ITestService" />
<endpoint binding="basicHttpBinding" bindingConfiguration="SecureBasic" name="basicHttpSecure" contract="Namespace.ITestService" />

To publish metadata on both endpoints, modify serviceMetadata like this:

<serviceMetadata httpsGetEnabled="true" httpGetEnabled="true" />