Claims-Aware Applications
This topic describes Active Directory Federation Services (ADFS) claims-aware applications and provides code for an example application.
Understanding Claims
A claim is a statement about a user that is used for authorization purposes in an application. ADFS brokers trust between disparate entities by allowing the trusted exchange of arbitrary claims that contain arbitrary values. The receiving party uses these claims to make authorization decisions.
ADFS supports three types of claims:
Identity claim. There are three types of identity claims:
User Principal Name (UPN). Indicates a Kerberos-style user UPN, such as
user@realm
.E-mail. Indicates RFC 2822–style e-mail names of the form
user@domain
.Common name. Indicates an arbitrary string that is used for personalization. Examples include
John Smith
orTailspin Toys Employee
. Note that you cannot guarantee the uniqueness of the common name claim. Therefore, use caution when you are using this claim type for authorization decisions.
Group claim. Indicates a user's membership in a group or role.
Custom claim. Contains custom information about a user, such as an employee ID number.
An organization claim is a claim in intermediate or normalized form in an organization's namespace.
Claims-Aware Applications
There are two ways to control access to Web-based applications: using a claims-aware application, or using a Windows NT token-based application.
A claims-aware application is an ASP.NET application that uses the ADFS library. A claims-aware application accepts claims that the Federation Service sends in ADFS security tokens, and can use ADFS claims to make authorization decisions directly.
A Windows NT token-based application, by contrast, is an Internet Information Services (IIS) application that uses Windows native authorization mechanisms instead of ADFS claims.
Claims-Aware Authorization
Claims-aware authorization consists of a HTTP module and objects for querying the claims that are carried in an ADFS security token. The HTTP module processes ADFS protocol messages based on configuration settings in the claims-aware application's web.config file. The HTTP module also authenticates cookies and obtains claims from the cookies. The application performs authentication and authorization tasks.
Claim Mapping
Claim mapping is the act of mapping, removing or filtering, or passing inbound claims into outbound claims. Claim mapping does not occur when claims are sent to an application. Instead, the Federation Service administrator specifies the organization claims that are sent to the application.
Auditing
When auditing is enabled, the audit allows the name of the claim to be exposed in the security event log, but not the value of the claim. Also, the claim value is not audited when the claim is produced or mapped.
An example of an auditable claim is a Social Security Number. The claim name Social Security Number
is exposed by an audit, but the actual number value that is stored in that claim is not exposed.
Note
Identity claim types are always auditable. Group and custom claims can be designated as auditable.
By default, organization claims that are marked as auditable are not sent to an application, whereas nonauditable claims are sent to the application. If the Federation Service administrator wants auditable claims to be sent to the application, or nonauditable claims not to be sent to the application, they must specify this.
Example
The following code listings show an example claims-aware application. This application lists all claims that are contained in the current user's ADFS security token.
Note
The web.config file for your application must contain the following <machineKey>
section in the <system.web>
section:
<machineKey validationKey="AutoGenerate,IsolateApps" decryptionKey="AutoGenerate,IsolateApps" validation="3DES" decryption="3DES"/>
For more information, see KB Article 911722.
web.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="system.web">
<!-- This assembly hosts the HTTP module that queries the
claims that are carried in the user's ADFS security token. -->
<section name="websso" type="System.Web.Security.SingleSignOn.WebSsoConfigurationHandler, System.Web.Security.SingleSignOn, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null" />
</sectionGroup>
</configSections>
<system.web>
<machineKey validationKey="AutoGenerate,IsolateApps" decryptionKey="AutoGenerate,IsolateApps" validation="3DES" decryption="3DES"/>
<sessionState mode="Off" />
<compilation defaultLanguage="c#" debug="true">
<assemblies>
<add assembly="System.Web.Security.SingleSignOn, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null"/>
<add assembly="System.Web.Security.SingleSignOn.ClaimTransforms, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null"/>
</assemblies>
</compilation>
<customErrors mode="Off"/>
<authentication mode="None" />
<!-- Add the HTTP module that queries the claims that are
carried in the user's ADFS security token. -->
<httpModules>
<add
name="Identity Federation Services Application Authentication Module"
type="System.Web.Security.SingleSignOn.WebSsoAuthenticationModule, System.Web.Security.SingleSignOn, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null" />
</httpModules>
<websso>
<authenticationrequired />
<eventloglevel>55</eventloglevel>
<auditsuccess>2</auditsuccess>
<!-- The final redirect for the application. -->
<urls>
<returnurl>https://adfsweb.treyresearch.net:8081/claimapp/</returnurl>
</urls>
<cookies writecookies="true">
<path>/claimapp</path>
<lifetime>240</lifetime>
</cookies>
<!-- The Federation Server that hosts the application. -->
<fs>https://adfsresource.treyresearch.net/adfs/fs/federationserverservice.asmx</fs>
</websso>
</system.web>
<!-- This section configures the logging for the HTTP module that
queries the claims that are carried in the user's ADFS security
token. -->
<system.diagnostics>
<switches>
<add name="WebSsoDebugLevel" value="0" /> <!-- Change to 255 to enable full debug logging -->
</switches>
<trace autoflush="true" indentsize="3">
<listeners>
<add name="LSLogListener" type="System.Web.Security.SingleSignOn.BoundedSizeLogFileTraceListener, System.Web.Security.SingleSignOn, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null"
initializeData="c:\logdir\claimapp.log" />
</listeners>
</trace>
</system.diagnostics>
</configuration>
default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ OutputCache Location="None" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html >
<head>
<meta http-equiv="Content-Language" content="en-us">
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<title>Claims-aware Sample Application</title>
<style>
<!--
.pagetitle { font-family: Verdana; font-size: 18pt; font-weight: bold;}
.propertyTable td { border: 1px solid; padding: 0px 4px 0px 4px}
.propertyTable th { border: 1px solid; padding: 0px 4px 0px 4px; font-weight: bold; background-color: #cccccc ; text-align: left }
.propertyTable { border-collapse: collapse;}
td.l{ width: 200px }
tr.s{ background-color: #eeeeee }
.banner { margin-bottom: 18px }
.propertyHead { margin-top: 18px; font-size: 12pt; font-family: Arial; font-weight: bold; margin-top: 18}
.abbrev { color: #0066FF; font-style: italic }
-->
</style>
</head>
<body>
<form ID="Form1" runat=server>
<div class=banner>
<div class=pagetitle>SSO Sample</div>
[ <asp:HyperLink ID=SignOutUrl runat=server>Sign Out</asp:HyperLink> | <a href="<%=Context.Request.Url.GetLeftPart(UriPartial.Path)%>
">Refresh without viewstate data</a>]
</div>
<div class=propertyHead>Page Information</div>
<div style="padding-left: 10px; padding-top: 10px">
<asp:Table runat=server ID=PageTable CssClass=propertyTable>
<asp:TableHeaderRow>
<asp:TableHeaderCell>Name</asp:TableHeaderCell>
<asp:TableHeaderCell>Value</asp:TableHeaderCell>
<asp:TableHeaderCell>Type</asp:TableHeaderCell>
</asp:TableHeaderRow>
</asp:Table>
</div>
<div class=propertyHead>User.Identity</div>
<div style="padding-left: 10px; padding-top: 10px">
<asp:Table CssClass="propertyTable" ID=IdentityTable runat=server>
<asp:TableHeaderRow>
<asp:TableHeaderCell>Name</asp:TableHeaderCell>
<asp:TableHeaderCell>Value</asp:TableHeaderCell>
<asp:TableHeaderCell>Type</asp:TableHeaderCell>
</asp:TableHeaderRow>
</asp:Table>
</div>
<div class=propertyHead>(IIdentity)User.Identity</div>
<div style="padding-left: 10px; padding-top: 10px">
<asp:Table CssClass="propertyTable" ID=BaseIdentityTable runat=server>
<asp:TableHeaderRow>
<asp:TableHeaderCell>Name</asp:TableHeaderCell>
<asp:TableHeaderCell>Value</asp:TableHeaderCell>
<asp:TableHeaderCell>Type</asp:TableHeaderCell>
</asp:TableHeaderRow>
</asp:Table>
</div>
<div class=propertyHead>(SingleSignOnIdentity)User.Identity</div>
<div style="padding-left: 10px; padding-top: 10px">
<asp:Table CssClass="propertyTable" ID=SSOIdentityTable runat=server>
<asp:TableHeaderRow>
<asp:TableHeaderCell>Name</asp:TableHeaderCell>
<asp:TableHeaderCell>Value</asp:TableHeaderCell>
<asp:TableHeaderCell>Type</asp:TableHeaderCell>
</asp:TableHeaderRow>
</asp:Table>
</div>
<!-- This table lists the claims for the current user, queried
from their ADFS security token. -->
<div class=propertyHead>SingleSignOnIdentity.SecurityPropertyCollection</div>
<div style="padding-left: 10px; padding-top: 10px">
<asp:Table CssClass="propertyTable" ID=SecurityPropertyTable runat=server>
<asp:TableHeaderRow>
<asp:TableHeaderCell>Uri</asp:TableHeaderCell>
<asp:TableHeaderCell>Claim Type</asp:TableHeaderCell>
<asp:TableHeaderCell>Claim Value</asp:TableHeaderCell>
</asp:TableHeaderRow>
</asp:Table>
</div>
<div class=propertyHead>(IPrincipal)User.IsInRole(...)</div>
<div style="padding-left: 10px; padding-top: 10px">
<asp:Table CssClass="propertyTable" ID=RolesTable runat=server>
</asp:Table>
<div style="padding-top: 10px">
<table>
<tr><td>Roles to check (semicolon separated):</td></tr>
<tr><td><asp:TextBox ID=Roles Columns=55 runat=server/></td><td align=right><asp:Button UseSubmitBehavior=true ID=GetRoles runat=server Text="Check Roles" OnClick="GoGetRoles"/></td></tr>
</table>
</div>
</div>
</form>
</body>
</html>
default.aspx.cs
using System;
using System.Data;
using System.Collections.Generic;
using System.Configuration;
using System.Reflection;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Security;
using System.Security.Principal;
using System.Web.Security.SingleSignOn;
using System.Web.Security.SingleSignOn.Authorization;
public partial class _Default : System.Web.UI.Page
{
const string NullValue = "<span class=\"abbrev\" title=\"Null Reference, or not applicable\"><b>null</b></span>";
static Dictionary<string, string> s_abbreviationMap;
static _Default()
{
s_abbreviationMap = new Dictionary<string, string>();
//
// Add any abbreviations here. Make sure that prefixes of
// replacements occur *after* the longer replacement key.
//
s_abbreviationMap.Add("System.Web.Security.SingleSignOn.Authorization", "SSO.Auth");
s_abbreviationMap.Add("System.Web.Security.SingleSignOn", "SSO");
s_abbreviationMap.Add("System", "S");
}
protected void Page_Load(object sender, EventArgs e)
{
//
// Get the user's identity from their ADFS token.
//
SingleSignOnIdentity ssoId = User.Identity as SingleSignOnIdentity;
//
// Get some property tables initialized.
//
PagePropertyLoad();
IdentityLoad();
BaseIdentityLoad();
SSOIdentityLoad(ssoId);
SecurityPropertyTableLoad(ssoId);
//
// Filling in the roles table
// requires a peek at the viewstate
// since we have a text box driving this.
//
if (!IsPostBack)
{
UpdateRolesTable(new string[] { });
}
else
{
GoGetRoles(null, null);
}
//
// Get the right links for SSO
//
if (ssoId == null)
{
SignOutUrl.Text = "Single Sign On isn't installed...";
SignOutUrl.Enabled = false;
}
else
{
if (ssoId.IsAuthenticated == false)
{
SignOutUrl.Text = "Sign In (you aren't authenticated)";
SignOutUrl.NavigateUrl = ssoId.SignInUrl;
}
else
SignOutUrl.NavigateUrl = ssoId.SignOutUrl;
}
}
void SecurityPropertyTableLoad(SingleSignOnIdentity ssoId)
{
Table t = SecurityPropertyTable;
if (ssoId == null)
{
AddNullValueRow(t);
return;
}
//
// Go through each of the security properties provided.
//
bool alternating = false;
foreach (SecurityProperty securityProperty in ssoId.SecurityPropertyCollection)
{
t.Rows.Add(CreateRow(securityProperty.Uri, securityProperty.Name, securityProperty.Value, alternating));
alternating = !alternating;
}
}
void UpdateRolesTable(string[] roles)
{
Table t = RolesTable;
t.Rows.Clear();
bool alternating = false;
foreach (string s in roles)
{
string role = s.Trim();
t.Rows.Add(CreatePropertyRow(role, User.IsInRole(role), alternating));
alternating = !alternating;
}
}
void IdentityLoad()
{
Table propertyTable = IdentityTable;
if (User.Identity == null)
{
AddNullValueRow(propertyTable);
}
else
{
propertyTable.Rows.Add(CreatePropertyRow("Type name", User.Identity.GetType().FullName));
}
}
void SSOIdentityLoad(SingleSignOnIdentity ssoId)
{
Table propertyTable = SSOIdentityTable;
if (ssoId != null)
{
PropertyInfo[] props = ssoId.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
AddPropertyRows(propertyTable, ssoId, props);
}
else
{
AddNullValueRow(propertyTable);
}
}
void PagePropertyLoad()
{
Table propertyTable = PageTable;
string leftSidePath = Request.Url.GetLeftPart(UriPartial.Path);
propertyTable.Rows.Add(CreatePropertyRow("Simplified Path", leftSidePath));
}
void BaseIdentityLoad()
{
Table propertyTable = BaseIdentityTable;
IIdentity identity = User.Identity;
if (identity != null)
{
PropertyInfo[] props = typeof(IIdentity).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
AddPropertyRows(propertyTable, identity, props);
}
else
{
AddNullValueRow(propertyTable);
}
}
void AddNullValueRow(Table table)
{
TableCell cell = new TableCell();
cell.Text = NullValue;
TableRow row = new TableRow();
row.CssClass = "s";
row.Cells.Add(cell);
table.Rows.Clear();
table.Rows.Add(row);
}
void AddPropertyRows(Table propertyTable, object obj, PropertyInfo[] props)
{
bool alternating = false;
foreach (PropertyInfo p in props)
{
string name = p.Name;
object val = p.GetValue(obj, null);
propertyTable.Rows.Add(CreatePropertyRow(name, val, alternating));
alternating = !alternating;
}
}
TableRow CreatePropertyRow(string propertyName, object propertyValue)
{
return CreatePropertyRow(propertyName, propertyValue, false);
}
TableRow CreatePropertyRow(string propertyName, object value, bool alternating)
{
if (value == null)
return CreateRow(propertyName, null, null, alternating);
else
return CreateRow(propertyName, value.ToString(), value.GetType().FullName, alternating);
}
TableRow CreateRow(string s1, string s2, string s3, bool alternating)
{
TableCell first = new TableCell();
first.CssClass = "l";
first.Text = Abbreviate(s1);
TableCell second = new TableCell();
second.Text = Abbreviate(s2);
TableCell third = new TableCell();
third.Text = Abbreviate(s3);
TableRow row = new TableRow();
if (alternating)
row.CssClass = "s";
row.Cells.Add(first);
row.Cells.Add(second);
row.Cells.Add(third);
return row;
}
private string Abbreviate(string s)
{
if (s == null)
return NullValue;
string retVal = s;
foreach (KeyValuePair<string, string> pair in s_abbreviationMap)
{
//
// We only get one replacement per abbreviation call.
// First one wins.
//
if (retVal.IndexOf(pair.Key) != -1)
{
string replacedValue = string.Format("<span class=\"abbrev\" title=\"{0}\">{1}</span>", pair.Key, pair.Value);
retVal = retVal.Replace(pair.Key, replacedValue);
break;
}
}
return retVal;
}
//
// ASP.NET server side callback
//
protected void GoGetRoles(object sender, EventArgs ea)
{
string[] roles = Roles.Text.Split(';');
UpdateRolesTable(roles);
}
}
See Also
Understanding ADFS Authentication and Authorization
KB Article 911722