Exercise 1: Enhance an ASP.NET Membership Website with Identity Provider Capabilities and Use it from a Third Party Website

In this exercise you will enhance an existing ASP.NET website which uses the membership provider with a passive STS page so it can work as an identity provider for other websites. Then, you will configure a second website to take advantage of it (or, using the common security terminology, federate with it).

Figure 1 The general flow of the exercise’s solution

Task 1 - Preparing the Solutions

In this task you will take an existing website and configure it for using a pre-populated membership store file (ASPNETDB.mdf). In practical terms, this means giving write permissions to NETWORK SERVICE (or IIS_IUSRS for Windows 7 and Windows Server 2008 R2) for the App_Data folder in order to allow IIS to read and write the ASPNETDB.mdf database.

  1. Open Microsoft Visual Studio 2008 with administrator privileges. From Start | All Programs | Microsoft Visual Studio 2008, right click on Microsoft Visual Studio 2008 and choose Run as administrator.
  2. Open the MembershipSite.sln solution file located in the %YourInstallationFolder%\Labs\MembershipAndFederation\Source\Ex1-EnhancingWithFederation\Begin folder.
  3. Open a Windows Explorer instance and browse to the folder %YourInstallationFolder%\Labs\MembershipAndFederation\Source\Ex1-EnhancingWithFederation\Begin\MembershipSite.
  4. Assign Write permission to NETWORK SERVICE (or IIS_IUSRS for Windows 7 and Windows Server 2008 R2) user for the App_Data folder of the Begin solution. To do this, right-click the App_Data folder, select Properties, go to the Security tab and click Edit. On the Permissions for App_Data select the "NETWORK SERVICE" user and check the Write permission. Click OK on each dialog.

    Figure 2 Assigning NETWORK SERVICE write permissions for App_Data

    Note:
    If you want to run the End solution, follow the same steps in the %YourInstallationFolder%\Labs\MembershipAndFederation\Source\Ex1-EnhancingWithFederation\End\MembershipSite folder.

Task 2 - Inspecting the Initial Solution

  1. Go back to the solution in Visual Studio.
  2. Press F5 to run the Web site and accept to enable debugging in the Web site.
  3. When redirected to the login page enter the username John and the password p@ssw0rd

    Figure 3 Log in page

  4. Click Log In button.

    Figure 4 Default page showing a welcome message for the authenticated user

  5. Close the browser.
    Note:
    You can continue inspecting Web.config file. The web site is configured as any usual ASP.NET Web site to use Forms authentication method with the SQL membership provider. In the following task you will modify this website adding a passive STS page which will reuse the same membership mechanism.

Task 3 - Creating the Passive STS Page

  1. On the Solution Explorer, right-click the project (https://localhost/MembershipSiteEx01) and select Add New Item.
  2. Add a new Web Form and use the name PassiveSTS.aspx.
  3. Add a reference to the Microsoft.IdentityModel.dll assembly to the partner Web site. To do this, right-click on the https://localhost/MembershipSiteEx01 project, select Add Reference and add the Microsoft.IdentityModel component in the .NET tab.
    Note:
    If you did not find the Microsoft.IdentityModel.dll assembly on the Add Reference dialog, you can add the reference including the following line in the configuration/system.web/compilation/assemblies section of the Web.config file: <add assembly="Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />

  4. Open the PassiveSTS.aspx.cs page.
  5. Add the following code to the PassiveSTS class
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using
     System.Web.UI.WebControls;
    using Microsoft.IdentityModel.Protocols.WSFederation;
    using Microsoft.IdentityModel.Web;
    using Microsoft.IdentityModel.SecurityTokenService;
    using System.Globalization;
    
    public
     partial class PassiveSTS : System.Web.UI.Page
    {
     protected void Page_Load(object sender, EventArgs e)
     {
    
     }
    
     protected void Page_PreRender(object sender, EventArgs e)
    
     {
     string action = Request.QueryString[WSFederationConstants.Parameters.Action];
    
     try
     {
     if (action == WSFederationConstants.Actions.SignIn)
     {
     // Process signin request.
    
     SignInRequestMessage requestMessage = (SignInRequestMessage)WSFederationMessage.CreateFromUri(Request.Url);
     if (User != null && User.Identity != null && User.Identity.IsAuthenticated)
     {
     SecurityTokenService sts = new
     MembershipSTS(MembershipSTSConfiguration.Current);
     SignInResponseMessage responseMessage = FederatedPassiveSecurityTokenServiceOperations.ProcessSignInRequest(requestMessage, User, sts);
     FederatedPassiveSecurityTokenServiceOperations.ProcessSignInResponse(responseMessage,
     Response);
     }
     else
     {
     throw new UnauthorizedAccessException();
     }
     }
     else if (action == WSFederationConstants.Actions.SignOut)
     {
     // Process signout
     request.
     SignOutRequestMessage requestMessage = (SignOutRequestMessage)WSFederationMessage.CreateFromUri(Request.Url);
     FederatedPassiveSecurityTokenServiceOperations.ProcessSignOutRequest(requestMessage, User, requestMessage.Reply,
     Response);
     }
     else
     {
     throw new InvalidOperationException(
     String.Format(CultureInfo.InvariantCulture,
     "The action '{0}' (Request.QueryString['{1}']) is unexpected. Expected actions
     are: '{2}' or '{3}'.",
     String.IsNullOrEmpty(action) ? "<EMPTY>" : action,
     WSFederationConstants.Parameters.Action,
     WSFederationConstants.Actions.SignIn,
     WSFederationConstants.Actions.SignOut));
    
     }
     }
     catch (Exception exception)
     {
     throw new Exception("An unexpected error occurred when processing the request. See inner exception for details.", exception);
     }
     }
    }
    
    
    Note:
     This code is the same code created by the ASP.NET Security Token Service Web Site Visual Studio template. It provides the basic functionality of a passive token issuer that supports sign-in and sign-out. The solution will not compile right now, but in the following steps you will add a couple of assets that will make it work.

  6. Open a windows explorer window and browse to %YourInstallationFolder%\Labs\MembershipAndFederation\Source\Assets.
  7. Copy the App_Code and FederationMetadata folders and its content to the Web site folder (right-click the project https://localhost/MembershipSiteEx01/ and select Open Folder in Windows Explorer, then paste the files in the project folder).
  8. Switch back to Visual Studio and click Refresh button on the Solution Explorer window.

    Figure 5 App_Code and FederationMetadata folders added

    Note:
    The code you just added is a simplified version of the code generated by the Windows Identity Foundation Visual Studio templates. The STS will issue claims with the user name and the roles he has. For further understanding you can inspect the files just added and take a look at the GetOutputClaimsIdentity method in the MembershipSTS class. This is the method that we can use for customizing the identity information in the token that we will send to the third party website.

Task 4 - Creating the Partner Web Site

  1. On Visual Studio go to File | Add | New Web Site.
  2. On the New Web Site dialog select ASP.Net Web Site project type, choose HTTP for the location and make sure language is set to Visual C#, and then provide the following name for the web site: https://localhost/MembershipSitePartnerEx01/ and click OK.
  3. Add a reference to the Microsoft.IdentityModel.dll assembly to the partner Web site. To do this, right-click on the https://localhost/MembershipSitePartnerEx01/ project, select Add Reference and add the Microsoft.IdentityModel component in the .NET tab.
    Note:
    If you did not find the Microsoft.IdentityModel.dll assembly on the Add Reference dialog, you can add the reference including the following line in the configuration/system.web/compilation/assemblies section of the Web.config file: <add assembly="Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />

  4. Update the Default.aspx page to add a FederatedPassiveSignInStatus control and a welcome message and set the body background color to #AFC1CE to differentiate the partner site from the membership site.
    <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
    <%@ Register assembly="Microsoft.IdentityModel,
     Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" namespace="Microsoft.IdentityModel.Web.Controls" tagprefix="idfx" %>
    
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html
     xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
     <title>Membership and Federation - Exercise 1</title>
    </head>
    <body style="background:#AFC1CE">
     <form id="form1" runat="server">
    
     <div>
     <h1>Welcome to your partner site</h1>
     <idfx:FederatedPassiveSignInStatus ID="SignInStatus1" runat="server" /> 
     </div>
     </form>
    </body>
    </html>
    
  5. On the Solution Explorer, right-click the https://localhost/MembershipSitePartnerEx01/ project and select Add STS reference.

    Figure 6 Add STS reference option

  6. When the Federation Utility window shows up perform the following tasks for each step in the wizard.
    1. On the Welcome page click Next to continue using the pre-populated fields.

      Figure 7 Welcome page

    2. On the STS options page, select the third option button ("Use an existing STS"), set the STS metadata location to https://localhost/MembershipSiteEx01/FederationMetadata/2007-06/FederationMetadata.xml and click Next.

      Figure 8 Selecting a STS option

    3. Click Next. The following warning message will popup saying that there is a mismatch between the DNS name of the machine and the certificate subject name in the federation metadata. If the existing STS would have been a production deployed STS, then this could be a hint that you shouldn’t trust on it, but in a development environment it’s ok to continue.

      Figure 9

      Warning message because of certificate and DNS name mismatch on the development environment

    4. Click Yes
    5. Select the Enable encryption option and then select the Select an existing certificate from store option.

      Figure 10 Using encryption with localhost certificate

    6. Click Select Certificate and select the certificate for localhost.

      Figure 11 Selecting the certificate

    7. Click Next.
    8. In the Offered Claims page, click Next.

      Figure 12 Offered claims window

    9. On the Summary page, review the changes and click Finish.

      Figure 13 Summary

Task 5 - Validating the STS Issues Tokens to a Specific Relying Party

To add additional security to the STS, you can limit the issuance of tokens to specific relaying parties only. In this task, you will configure the STS to issue tokens for the partner site only.

  1. Open the MembershipSTS.cs file from the https://localhost/MembershipSiteEx01/ project inside the App_Code folder.
  2. Update the GetScope method to validate the AppliesTo property and throw an exception is the URI is not from the partner site. To do this, update the GetScope method as follows.

    (Code Snippet - Membership And Federation Lab - Ex 1 AppliesTo Validation)

    protected override Scope GetScope(IClaimsPrincipal principal, RequestSecurityToken request)
    {
     Uri rpUri = new Uri("https://localhost/MembershipSitePartnerEx01/");
    
    
     if (!request.AppliesTo.Uri.Equals(rpUri))
     {
     throw new InvalidRequestException(String.Format("The AppliesTo address {0} is not valid.", request.AppliesTo.Uri.AbsoluteUri));
     }
    
     Scope scope = new
     Scope(request, SecurityTokenServiceConfiguration.SigningCredentials);
     scope.EncryptingCredentials = new X509EncryptingCredentials(CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, "CN=localhost"));
     scope.ReplyToAddress
     = scope.AppliesToAddress + "/Default.aspx";
     return scope;
    }
    
    Note:
    The AppliesTo will return the URL for the Relying Party that is requesting for tokens to the STS. In the previous code you are checking that the Relying Party that is requesting the token is a valid one.

Task 6 - Customizing Authorization with Different Roles

  1. Add a new Web Forms to the https://localhost/MembershipSitePartnerEx01 project and name it SecretPage.aspx.
  2. Add the following message to the SecretPage.aspx.
    <%@ Page Language="C#" %>
    
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    
    <html
     xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
     <title></title>
    </head>
    <body style="background:#AFC1CE">
     <form id="form1" runat="server">
     <div>
     <p>This is
     a secret page; you can get here only if you are a manager.</p>
     </div>
     </form>
    </body>
    </html>
    
  3. Open the Web.config file from the https://localhost/MembershipSitePartnerEx01 project.
  4. Add the following settings under the configuration element to enable access to SecretPage.aspx only for users in Manager role.

    (Code Snippet - Membership And Federation Lab - Ex 1 SecretPage.aspx Authorization Settings)

    <configuration>
     ...
     <connectionStrings/>
     <location path="SecretPage.aspx">
     <system.web>
     <authorization>
     <allow roles="Manager"/>
    
     <deny users="*"/>
     </authorization>
     </system.web>
     </location>
     <location path="FederationMetadata">
     ...
    </configuration>
    
    Note:
    this is plain old ASP.NET configuration. It will work because the token contains role claims (http://schemas.microsoft.com/ws/2008/06/identity/claims/role) and the ClaimsPrincipal implements IsInRole by checking the list of claims where claim type is the one defined above.

  5. Build the solution, to do this press CTRL+SHIFT+B