Exercise 2: Customizing the Credentials Accepted by a Local STS
This is an optional exercise. Its purpose is to understand a bit more the structure of an STS project: however it is very probable that most of the times you will rely on existing STSes rather than having to write one, hence feel free to skip to exercise three if you are in a hurry.
In Exercise 1 we have shown how to generate a development STS in order to keep our application free from authentication-specific code and for feeding it with the claims we needed. The default structure of the auto generated STS does not really perform any authentication step, and just takes care of feeding the application with claims by following the proper protocols: the credential gathering page does not really validate the credentials you enter, and just goes ahead with the issuing process. For the purpose of developing our RP application this is enough, however if you’d want to use that STS as a starting point for writing your own there are a number of aspects that require to be expanded.
In this exercise we will modify an automatically generated development STS so that it will actually authenticate incoming users against an ASP.NET membership provider store, and it will source claim values from an ASP.NET profile and a Role manager store.
You should always take into account that the exercises in this lab are didactic tools meant to demonstrate how to use the technology, and the code you see here is not ready for production use.
.png)
Figure 25The redirects & claims flow, showing credential check and claims sourcing in the STS
Task 1 - Configuring the STS
In this task you will configure the STS to use ASP.NET authentication mechanisms.
- Open Microsoft Visual Studio 2010 with administrator privileges. From Start | All Programs | Microsoft Visual Studio 2010, right-click Microsoft Visual Studio 2010 and select Run as administrator.
- Open the ClaimsEnableWebSite.sln solution file located in the %YourInstallationFolder%\Labs\WebSitesAndIdentity\Source\Ex2-ClaimsEnabledDifferentCredentials\Begin folder.
While it is not mandatory for the successful execution of this exercise, it can be interesting to gain some understanding of the structure of one development STS automatically generated by the Windows Identity Foundation. If you are a “pure” ASP.NET application developer, with no interest in security topics, you can safely skip this note and go on with the exercise steps.
Expand the
ClaimsEnableWebSiteEx02_STS project in the Solution Explorer and observe its structure:
.png)
This is a very typical Forms authentication based website structure.
The
Login.aspx page is where the credential gathering happens.
The
Default.aspx page represents the entry point for applications to invoke the STS: there are protocols (in this case
WS-Federation) that will redirect the user to this page whenever one application requires a token from our STS. This is how Default.aspx looks like in the designer:
.png)
From this brief explanation of the structure of the STS project you can already guess which steps will be necessary for transforming the default development STS into one STS which leverages an ASP.NET membership store:
- Modify the ASP.NET structure of the project: add the membership store and modify the
login.aspx page so that it will use the appropriate credential gathering and session management methods
- Modify
CustomSecurityTokenService.cs, namely the
GetOutputClaimsIdentity method, so that it will retrieve the claim values from the profile and roles stores.
- Open a Windows Explorer instance and browse to the folder %YourInstalletionFolder%\Labs\WebSitesAndIdentity\Source\Assets.
- Copy the membership database to the STS Web site. To do this, copy the App_Data folder from the Assets folder to % YourInstalletionFolder %\Labs\WebSitesAndIdentity\ Source\Ex2-ClaimsEnabledDifferentCredentials\Begin\ClaimsEnableWebSite_STS (if you continue working from previous exercise copy the App_Data folder to the correct STS Web site folder)
- Assign Write permission to NETWORK SERVICE (or IIS_IUSRS for Windows 7 and Windows Server 2008 R2) user for the % YourInstalletionFolder %\Labs\WebSitesAndIdentity\ Source\Ex2-ClaimsEnabledDifferentCredentials\Begin\ClaimsEnableWebSite_STS \App_Data folder. 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.
.png)
Figure 26Assigning NETWORK SERVICE write permissions for App_Data
- Open the Web.Config file from the STS project (https://localhost/ClaimsEnableWebSiteEx02_STS)
- Add the following configuration under <system.web> to enable role and profile management with the BirthDate custom property.
(Code Snippet – Web Sites And Identity Lab - Ex02 Membership Configurations)
<system.web>
<roleManager enabled="true" />
<profile enabled="true" >
<providers />
<properties>
<add name="BirthDate" type="System.DateTime"/>
</properties>
</profile>
...
</system.web>
The STS project is already configured to use Forms authentication. In the following steps you will modify the login.aspx page so that it will leverage the standard ASP.NET Login control, which in turn will authenticate users against the membership provider store we added to the project.
- Open the Login.aspx file from the https://localhost/ClaimsEnableWebSiteEx02_STS project.
- Replace the HTML controls (table and p elements) with an ASP.NET Login control.
...
</head>
<body>
<form id="form1" runat="server">
<div class="style3">
Windows Identity Foundation - Security token service (STS) ASP.NET Site</div>
<p class="style3">
</p>
<asp:Label ID="Label3" runat="server" style="font-weight: 700"
Text="Login to the STS"></asp:Label>
<br />
<table class="style1">
<tr>
<td class="style2">
<asp:Label ID="Label1" runat="server" Text="User name"></asp:Label>
</td>
<td>
<asp:TextBox ID="txtUserName" runat="server"></asp:TextBox>
</td>
</tr>
<tr>
<td class="style2">
<asp:Label ID="Label2" runat="server" Text="Password"></asp:Label>
</td>
<td>
<asp:TextBox ID="txtPassword" runat="server" TextMode="Password" Width="149px">password</asp:TextBox>
</td>
</tr>
</table>
<p>
<asp:Button ID="btnSubmit" runat="server" Text="Submit" />
</p>
<asp:Login ID="loginPanel" runat="server"></asp:Login>
</form>
<p>
Note: You can enter any non-empty user name. The password field
is optional. To modify claims issued by this STS, make changes to
CustomSecurityTokenService.GetOutputClaimsIdentity.</p>
</body>
</html>
- Open Login.aspx.cs.
- Comment the Page_Load method.
Up to this point you have configured nothing related to Windows Identity Foundation. These are the usual changes done to use Membership provider in any website. In the following task you will take advantage of using SQL as the backing store and the Membership, Role and Profile providers to issue claims about the user as opposed to the hardcoded values we used in Exercise 1.
Task 2 - Using the Roles Principal
In this task you will change the STS to issue claims about the user coming from the principal and the Profile.
- Open CustomSecurityTokenService.cs on the STS project (https://localhost/ClaimsEnableWebSiteEx02_STS) under App_Code folder.
- Add the following using statements shown in bold.
(Code Snippet - Web Sites And Identity Lab - Ex02 Add Using Statements)
using System;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.Web.Configuration;
using Microsoft.IdentityModel.Claims;
using Microsoft.IdentityModel.Configuration;
using Microsoft.IdentityModel.Protocols.WSTrust;
using Microsoft.IdentityModel.SecurityTokenService;
using System.Collections.Generic;
using System.Linq;
using System.Web;
- Remove the fixed roles “Manager” and “Sales” claims and add the role claims from the ClaimsPrincipal. To do this, update the method GetOutputClaimsIdentity with the following code shown in bold.
(Code Snippet - Web Sites And Identity Lab - Ex02 Adding Role Claim)
protected override IClaimsIdentity GetOutputClaimsIdentity(IClaimsPrincipal principal, RequestSecurityToken request, Scope scope)
{ ClaimsIdentity outputIdentity = new ClaimsIdentity();
if (null == principal)
{ throw new InvalidRequestException("The caller's principal is null."); }
// TODO: Change the claims below to issue claims required by your Relying party
outputIdentity.Claims.Add(new Claim(System.IdentityModel.Claims.ClaimTypes.Name, principal.Identity.Name));
//outputIdentity.Claims.Add(new Claim(ClaimTypes.Role, "Manager"));
outputIdentity.Claims.Add(new Claim(ClaimTypes.Role, "Sales"));
IEnumerable<Claim> roleClaims = from claim in principal.Identities[0].Claims
where claim.ClaimType == ClaimTypes.Role
select claim;
foreach (Claim roleClaim in roleClaims)
{
outputIdentity.Claims.Add(new Claim(roleClaim.ClaimType, roleClaim.Value));
}
outputIdentity.Claims.Add(new Claim(System.IdentityModel.Claims.ClaimTypes.DateOfBirth, "5/5/1955"));
return outputIdentity;
}
- Comment the fixed "Birthday" claim in the GetOutputClaimsIdentity method.
protected override IClaimsIdentity GetOutputClaimsIdentity(IClaimsPrincipal principal, RequestSecurityToken request, Scope scope)
{ ...
// outputIdentity.Claims.Add(new Claim(System.IdentityModel.Claims.ClaimTypes.DateOfBirth, "5/5/1955"));
return outputIdentity;
}
- Add the BirthDateProfile property. To do this, update the method GetOutputClaimsIdentity with the following code shown in bold.
(Code Snippet - Web Sites And Identity Lab - Ex02 Adding Birth Date claims)
protected override IClaimsIdentity GetOutputClaimsIdentity(IClaimsPrincipal principal, RequestSecurityToken request, Scope scope)
{ ClaimsIdentity outputIdentity = new ClaimsIdentity();
if (null == principal)
{ throw new InvalidRequestException("The caller's principal is null."); }
// TODO: Change the claims below to issue claims required by your Relying party
outputIdentity.Claims.Add(new Claim(System.IdentityModel.Claims.ClaimTypes.Name, principal.Identity.Name));
IEnumerable<Claim> roleClaims = from claim in principal.Identities[0].Claims
where claim.ClaimType == ClaimTypes.Role
select claim;
foreach (Claim roleClaim in roleClaims)
{ outputIdentity.Claims.Add(new Claim(roleClaim.ClaimType, roleClaim.Value));
}
// outputIdentity.Claims.Add(new Claim(System.IdentityModel.Claims.ClaimTypes.DateOfBirth, "5/5/1955"));
DateTime birthdate = (DateTime)HttpContext.Current.Profile.GetPropertyValue("BirthDate");
Claim birthdateClaim = new Claim(System.IdentityModel.Claims.ClaimTypes.DateOfBirth, birthdate.ToShortDateString());
outputIdentity.Claims.Add(birthdateClaim);
return outputIdentity;
}
Exercise 2: Verification
In order to verify that you have correctly performed all steps in exercise two, proceed as follows:
If you are using the End solution to test the results without performing the exercise tasks, first, you have to give write permissions to NETWORK SERVICE (or IIS_IUSRS for Windows 7 and Windows Server 2008 R2) user for the App_Data folder in the STS project as detailed on the first steps of Task 1.
- Press Ctrl+F5 to run the solution (https://localhost/ClaimsEnableWebSiteEx02 project).
- When prompted for credentials, insert "invalid_user" in the User Name and Password fields.
.png)
If your SQL Express instance name is other than
SQLEXPRESS, add the following configuration to the
web.config under the Web Site
https://localhost/ClaimsEnableWebSiteEx02 _STS (show in
bold):<connectionStrings>
<remove name="LocalSqlServer"/><add name="LocalSqlServer" connectionString="data source=.\<YOURINSTANCESQLEXPRESSNAME>;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true" providerName="System.Data.SqlClient"/></connectionStrings>
- Now, log in with the John credentials (which is in Reader role).
- User Name: john
- Password: p@ssw0rd
.png)
Figure 28Logging in with an existing user
.png)
Figure 29Showing information based on name and dateOfBirth claims for john user.
- Browse to the secret page and you should get an Access Denied, because the user John does not have a claim Role with value Manager.
.png)
Figure 30Access denied error page
- Close the Web browser.
- Open a new Web browser instance and navigate to the Web site again (https://localhost/ClaimsEnableWebSiteEx02).
- Now, log in with the Paul credentials (which is in Manager role).
- User Name: paul
- Password: p@ssw0rd
.png)
Figure 31Showing information based on Name and dateOfBirth claims from Paul user
- Click on Go to secret page. Now you should be able to browse to the secret page.
.png)
Figure 32Showing the secret page