This section contains information on how PortalGuard can be used in identity federation and Single Sign-On (SSO) scenarios.

Windows Identity Foundation with .NET Applications

If you have existing .NET-based web applications that you would like to include in a SSO initiative, this can be accomplished with minimal application changes using Windows Identity Foundation (WIF).WIF provides .NET applications with an easy way to transition from classic authentication methods (e.g. username/password, Integrated Windows Authentication) to claims-based.The requirements for the WIF SDK (link) are:

  • Microsoft .NET Framework 3.5 or 4.0

NOTE: WIF has been fully integrated into the .NET framework starting in v4.5

  • Windows Identity Foundation runtime (link)
  • Microsoft Internet Information Services 6.0 (Windows 2003) or later

Here are some excellent resources for WIF:

Identity Management in Active Directory

Microsoft Windows Identity Foundation (WIF) Whitepaper for Developers

A Guide to Claims-Based Identity and Access Control (2nd Edition)

Microsoft has gone to great lengths to ensure this transition to claims-based authentication can occur with minimal impact and effort.What follows is a tutorial on how to convert a simple application which uses forms-based authentication with a SQL membership provider to claims-based authentication.

Initial Application

File: web.config

<?xml version="1.0" encoding="utf-8"?>

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

<connectionStrings>

<add name="MyConn" connectionString="xxx" />

</connectionStrings>

<system.web>

<authentication mode="Forms" >

<forms loginUrl="login.aspx" />

</authentication>

<authorization>

<deny users="?" />

</authorization>

<membership defaultProvider="SqlProvider" userIsOnlineTimeWindow="15">

<providers>

<clear />

<add name="SqlProvider" type="System.Web.Security.SqlMembershipProvider"

connectionStringName="MyConn" />

</providers>

</membership>

</system.web>

</configuration>

File: login.aspx

<%@ Page Language="C#" AutoEventWireup="true" Inherits="System.Web.UI.Page" %>

<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">

<title>Login Page</title>

<style type="text/css">

body,input,table,tr,td {

font-family: Verdana, Arial, Helvetica;

font-size: 11px;

}

</style>

</head>

<body>

<form id="form1" runat="server">

<div><asp:Login ID="Login1" runat="server" /></div>

</form>

</body></html>

File: default.aspx

<%@ Page Language="C#" AutoEventWireup="true" Inherits="System.Web.UI.Page" %>

<script runat="server">

void OnLogOut(Object sender, EventArgs e) {

FormsAuthentication.SignOut();

FormsAuthentication.RedirectToLoginPage();

}

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">

<title>Welcome Page</title>

<style type="text/css">

body,input,table,tr,td {

font-family: Verdana, Arial, Helvetica;

font-size: 11px;

}

</style>

</head>

<body>

<form id="form1" runat="server">

<div>

<table><tr><td>Username: <%=User.Identity.Name%></td></tr>

<tr><td>Auth Type: <%=User.Identity.AuthenticationType%></td></tr>

<tr><td><asp:Button id="Logout" Text="Log Out" OnClick="OnLogOut" RunAt="server" /></td></tr>

</table>

</div>

</form>

</body></html>

Accessing the root of the website displays the standard .NET login form:

Logging in with correct credentials shows a bare page with information about the user:

Claims-based Application

With claims-based authentication, the target application no longer handles the user’s credentials.Instead, the identity provider completely handles the authentication (proving the user’s identity).The target application trusts the authentication decision rendered by the identity provider but does still handle authorization itself (e.g. access checks).

Here are the details for the example that follows:

Application root: http://test.pistolstar.com:82

Identity Provider SSO URL: https://malt.pistolstar.com:444/sso/go.ashx

One thing to note is that the application no longer needs a login.aspx page since the identity provider now handles authentication.New code is in bold red.

NOTE: In addition to the code changes below, the identity provider’s signing certificate must also be imported into both the “Trusted Root Certificate Authorities" and "Trusted People" containers in the Local Machine certificate store on the application server.

File: web.config

<?xml version="1.0" encoding="utf-8"?>

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

<configSections>

<section name="microsoft.identityModel"

type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel,

Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

</configSections>

<system.web>

<compilation>

<assemblies>

<add assembly="Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral,

PublicKeyToken=31bf3856ad364e35" />

</assemblies>

</compilation>

<!-- Uncommenting the following line will support applications targeting the .NET 4.0 framework -->

<!--<httpRuntimerequestValidationMode="2.0" />-->

<httpModules>

<add name="SessionAuthentication" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule,

Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

<add name="WSFederationAuthenticationModule"

type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel,

Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

</httpModules>

<pages>

<namespaces>

<add namespace="Microsoft.IdentityModel.Web" />

<add namespace="Microsoft.IdentityModel.Claims" />

</namespaces>

</pages>

<authentication mode="None" />

<authorization>

<deny users="?" />

</authorization>

</system.web>

<system.webServer>

<modules>

<add name="SessionAuthentication" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule,

Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"

preCondition="managedHandler" />

<add name="WSFederationAuthenticationModule"

type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel,

Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler" />

</modules>

<validation validateIntegratedModeConfiguration="false"/>

</system.webServer>

<microsoft.identityModel>

<service>

<securityTokenHandlers>

<add type="Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler, Microsoft.IdentityModel,

Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">

<samlSecurityTokenRequirementmapToWindows="false" useWindowsTokenService="false" />

</add>

</securityTokenHandlers>

<audienceUris>

<add value="http://test.pistolstar.com/" />

</audienceUris>

<federatedAuthentication>

<wsFederationpassiveRedirectEnabled="true" issuer="https://[YOUR.PORTALGUARD.SERVER]/sso/go.ashx"

realm="http://test.pistolstar.com/" requireHttps="false" />

<cookieHandlerrequireSsl="false" path="/" />

</federatedAuthentication>

<issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry,

Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">

<trustedIssuers>

<add thumbprint="1568F2F1654F45897B770BB16D7059FF55D2A80E" name="PortalGuardIdP" />

</trustedIssuers>

</issuerNameRegistry>

</service>

</microsoft.identityModel>

</configuration>

File: default.aspx

<%@ Page Language="C#" AutoEventWireup="true" Inherits="System.Web.UI.Page" %>

<script runat="server">

void Page_Load(object sender, EventArgs e) {

if (User.IsInRole("Cartoon Characters")) {

InGroup.Text = "Yes";

} else {

InGroup.Text = "No";

}

// Debug only: Show all claims for debug purposes

IClaimsPrincipalicp = System.Threading.Thread.CurrentPrincipal as IClaimsPrincipal;

IClaimsIdentityclaimsIdentity = (IClaimsIdentity)icp.Identity;

Table claimsTable = new Table();

TableRowheaderRow = new TableRow();

TableCellclaimTypeCell = new TableCell();

claimTypeCell.Text = "<b>Claim Type</b>";

TableCellclaimValueCell = new TableCell();

claimValueCell.Text = "<b>Claim Value</b>";

headerRow.Cells.Add(claimTypeCell);

headerRow.Cells.Add(claimValueCell);

claimsTable.Rows.Add(headerRow);

TableRownewRow;

TableCellnewClaimTypeCell, newClaimValueCell;

foreach (Claim claim in claimsIdentity.Claims) {

newRow = new TableRow();

newClaimTypeCell = new TableCell();

newClaimTypeCell.Text = claim.ClaimType;

newClaimValueCell = new TableCell();

newClaimValueCell.Text = claim.Value;

newRow.Cells.Add(newClaimTypeCell);

newRow.Cells.Add(newClaimValueCell);

claimsTable.Rows.Add(newRow);

}

phClaims.Controls.Add(claimsTable);

}

void OnLogOut(Object sender, EventArgs e) {

WSFederationAuthenticationModuleauthModule =

FederatedAuthentication.WSFederationAuthenticationModule;

WSFederationAuthenticationModule.FederatedSignOut(new Uri(authModule.Issuer),new

Uri(authModule.Realm));

}

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">

<title>Welcome Page</title>

<style type="text/css">

body,input,table,tr,td {

font-family: Verdana, Arial, Helvetica;

font-size: 11px;

}

</style>

</head>

<body>

<form id="form1" runat="server">

<div>

<table><tr><td>Username: <%=User.Identity.Name%></td></tr>

<tr><td>Auth Type: <%=User.Identity.AuthenticationType%></td></tr>

<tr><td>Cartoon Character?: <asp:literal id="InGroup" runat="Server"/></td></tr>

<tr><td><asp:Button id="Logout" Text="Log Out" OnClick="OnLogOut" RunAt="server" /></td></tr></table>

<!-- For debug/troubleshooting only -->

<br><asp:PlaceHolder id="phClaims" runat="server"/>

</div>

</form>

</body>

</html>

Accessing the root of the website now redirects the user to the identity provider where they are prompted to authenticate:

Logging in with correct credentials sends an authenticator/token back to the application.WIF completely handles the token parsing and validation.The new destination page shows information about the user including debugging information about all the identity claims contained within the token:

NOTE: The full claims listing should NOT be used in a production environment!

The following is a breakdown of the changes in the web.config file that enabled WIF:

1. <configSections> - Defines a new section for the settings specific to WIF.

<configSections>

<section name="microsoft.identityModel"

type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel,

Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

</configSections>

2. <assemblies> - Adds the WIF assembly to be referenced during compilation.

<compilation debug="true">

<assemblies>

<add assembly="Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral,

PublicKeyToken=31bf3856ad364e35" />

</assemblies>

</compilation>

3. <httpModules> - Registers the FederatedAuthenticationModule in IIS 6.0 and classic ASP.NET applications.

<!-- Uncommenting the following line will support applications targeting the .NET 4.0 framework -->

<!--<httpRuntimerequestValidationMode="2.0" />-->

<httpModules>

<add name="SessionAuthentication" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule,

Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

<add name="WSFederationAuthenticationModule"

type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel,

Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

</httpModules>

4. <namespaces> - (Optional) Allows the new WIF classes to be referenced with short names without adding new imports to each page in the project.

<pages>

<namespaces>

<add namespace="Microsoft.IdentityModel.Web" />

<add namespace="Microsoft.IdentityModel.Claims" />

</namespaces>

</pages>

5. <authentication> - Since the application no longer handles authentication, the mode has been changed to “None”.

<authentication mode="None" />

6. <system.webServer> - The <modules> element registers the FederatedAuthenticationModule in IIS 7.0 "integrated mode" applications.The <validation> element allows the use of this web.config in "classic" ASP.NET mode.

<system.webServer>

<modules>

<add name="SessionAuthentication" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule,

Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"

preCondition="managedHandler" />

<add name="WSFederationAuthenticationModule"

type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel,

Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler" />

</modules>

<validation validateIntegratedModeConfiguration="false"/>

</system.webServer>

7. <microsoft.identityModel> - The purposes of this configuration section are as follows:

  1. <securityTokenHandlers> - Specifies that this application can handle SAML v2.0 assertions
  2. <samlSecurityTokenRequirement> - The attributes in this element as configured tell WIF to NOT convert the incoming claims to a Windows token.If the application needs a Windows token, these can both be set to ‘true’ but the Claims To Windows Token Service (c2WTS) (link) must be installed and the assertion must contain a UPN claim.
  3. <audienceUris> - A list of the URLs that are acceptable identifiers for this site.
  4. <wsFederation> (link)
    1. passiveRedirectEnabled - Causes unauthorized requests to automatically redirect to the identity provider.
    2. issuer - The URL of identity provider where users will be sent to login. This must be the full URL to the “/sso/go.ashx” resource on your PortalGuard server as accessible by end users.For example:

https://portalguard.acme.com/sso/go.ashx

    1. realm - Sent to the identity provider as the realm or identifier of the site
    2. requireHttps - This should be set to “true” in a production environment to prevent session hijacking which is the default.It is only set to “false” here for demonstration purposes.
  1. <cookieHandler> - The “requireSsl” attribute should also be set to “true” in a production environment to prevent session hijacking which is the default.It is only set to “false” here for demonstration purposes.
  2. <trustedIssuers> (link) - A list of the signing certificates of the trusted identity providers.The “thumbprint” attribute is taken directly from the identity providers’ signing certificates and must be capitalized. The “name” attribute is an optional description.

<microsoft.identityModel>

<service>

<securityTokenHandlers>

<add type="Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler, Microsoft.IdentityModel,

Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">

<samlSecurityTokenRequirementmapToWindows="false" useWindowsTokenService="false" />

</add>

</securityTokenHandlers>

<audienceUris>

<add value="http://test.pistolstar.com/" />

</audienceUris>

<federatedAuthentication>

<wsFederationpassiveRedirectEnabled="true" issuer="https://portalguard.acme.com/sso/go.ashx"

realm="http://test.pistolstar.com/" requireHttps="false" />

<cookieHandlerrequireSsl="false" path="/" />

</federatedAuthentication>

<issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry,

Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">

<trustedIssuers>

<add thumbprint="1568F2F1654F45897B770BB16D7059FF55D2A80D" name="PortalGuardIdP" />

</trustedIssuers>

</issuerNameRegistry>

</service>

</microsoft.identityModel>

The following is a breakdown of the changes in the default.aspx file that is now claims-enabled:

1. Use of IsInRole() (1 of 2) – This code just illustrates that IsInRole() still works with groups coming from claims instead of a role provider.

void Page_Load(object sender, EventArgs e) {

if (User.IsInRole("Cartoon Characters")) {

InGroup.Text = "Yes";

} else {

InGroup.Text = "No";

}

2. Debug Only - Dump of Identity Claims (1 of 2) – (Optional) This code only shows the identity claims coming from the identity provider by formatting them as a table on the page.It should NOT be used in production!

// Debug only: Show all claims for debug purposes

IClaimsPrincipalicp = System.Threading.Thread.CurrentPrincipal as IClaimsPrincipal;

IClaimsIdentityclaimsIdentity = (IClaimsIdentity)icp.Identity;

Table claimsTable = new Table();

TableRowheaderRow = new TableRow();

TableCellclaimTypeCell = new TableCell();

claimTypeCell.Text = "<b>Claim Type</b>";

TableCellclaimValueCell = new TableCell();

claimValueCell.Text = "<b>Claim Value</b>";

headerRow.Cells.Add(claimTypeCell);

headerRow.Cells.Add(claimValueCell);

claimsTable.Rows.Add(headerRow);

TableRownewRow;

TableCellnewClaimTypeCell, newClaimValueCell;

foreach (Claim claim in claimsIdentity.Claims) {

newRow = new TableRow();

newClaimTypeCell = new TableCell();

newClaimTypeCell.Text = claim.ClaimType;

newClaimValueCell = new TableCell();

newClaimValueCell.Text = claim.Value;

newRow.Cells.Add(newClaimTypeCell);

newRow.Cells.Add(newClaimValueCell);

claimsTable.Rows.Add(newRow);

}

phClaims.Controls.Add(claimsTable);

3. LogOut – When WIF authenticates a user, it creates at least one FedAuth HTTP cookie that prevents the need to redirect to the identity provider for each subsequent request.These cookies are session-based cookies that are automatically destroyed when the browser shuts down, but if the user elects to manually logout, there is a different mechanism to do so for WIF than from normal Forms-Based authentication.

WSFederationAuthenticationModuleauthModule =

FederatedAuthentication.WSFederationAuthenticationModule;

WSFederationAuthenticationModule.FederatedSignOut(new Uri(authModule.Issuer),new

Uri(authModule.Realm));

4. Use of IsInRole() (2 of 2) – (Optional) Just used to display results of IsInRole() check.

<tr><td>Cartoon Character?: <asp:literal id="InGroup" runat="Server"/></td></tr>

5. Debug Only - Dump of Identity Claims (2 of 2) – (Optional) Second part of displaying all claims from the identity provider.

<!-- For debug/troubleshooting only -->

<br><asp:PlaceHolder id="phClaims" runat="server"/>

Clearly, the required changes in the application code itself are minimal.The logout code can be shared across pages by just modifying global.asax.The user’s name can continue to be retrieved using User.Identity.Name and access checks can still use IsInRole() as is typical without claims.

Which claims from the identity provider are treated as the username and the roles is configurable.The default NameClaimType is http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name.The default RoleClaimType is http://schemas.microsoft.com/ws/2008/06/identity/claims/role.These defaults can be overridden in the web.config using the nameClaimType and roleClaimType elements in the <samlSecurityTokenRequirement> element (link).For full details on the WIF configuration options in web.config see the following article:

A Hidden Gem: The WIF Config Schema

Another strong benefit of claims-based authentication is you no longer need to provision user accounts ahead of time.The identity provider can be configured to pass all required user data (first name, last name, phone number, etc.) in the token.However, you may still want to store per-user information persistently.This can be done using a standard .NET profile provider (link).

The debug code above can be used to see all claims coming from the identity provider for troubleshooting purposes.And to see the interplay between the application, identity provider and the user’s browser, simply fire up a local web proxy like Fiddler (link) to see it in full detail.