1. Goals and Requirements
The Tailspin Surveys
application targets a wide range of customers, from large enterprises
all the way down to individuals. All customers of the Surveys
application will require authentication
and authorization services, but they will want to implement these
services differently. For example, a large enterprise customer is likely
to require integration with their existing identity infrastructure, a
smaller customer may not be in a position to integrate their systems and
will require a basic security system as part of the Surveys
application, and an individual may want to reuse an existing identity
such as a Windows Live® ID or OpenID.
2. Overview of the Solution
Tailspin has identified three different identity scenarios that the Surveys application must support:
Organizations
may want to integrate their existing identity infrastructure and be able
to manage access to the Surveys application themselves, in order to
include Surveys as a part of the Single Sign-On (SSO) experience for
their employees.
Smaller
organizations may require Tailspin to provide a complete identity
system because they are not able to integrate their existing systems
with Tailspin.
Individuals and small organizations may want to re-use an existing identity they have, such as a Windows Live ID or OpenID.
Tailspin uses a claims-based infrastructure to provide the flexibility it needs to support its diverse customer base. |
To support these scenarios, Tailspin uses the WS-Federation protocol to implement identity federation. At the time of writing, Access
Control Services (ACS) does not implement the WS- ederation protocol,
and Tailspin uses the Windows Identity Foundation (WIF) implementation
of this protocol.
The following three diagrams describe how the authentication and authorization process works for each of these three scenarios.
Note:
The
three scenarios are all claims-based and share the same core identity
infrastructure. The only difference is the source of the original claims.
In the scenario shown in Figure 1,
users at Adatum, a large enterprise subscriber, authenticate with
Adatum’s own identity provider (step 1), in this case Active Directory®
Federation Services (ADFS). After successfully authenticating an Adatum
user, ADFS issues a token. The Tailspin federation provider trusts
tokens issued by Adatum’s ADFS (step 2), and if necessary can perform a
transformation on the claims in the token to claims that Tailspin
Surveys recognizes (step 3) before returning a token to the user.
Tailspin Surveys trusts tokens issued by the Tailspin federation
provider and uses the claims in the token to apply authorization
rules (step 4). Users at Adatum will not need to remember separate
credentials to access the Surveys application and an administrator at
Adatum will be able to configure in ADFS which Adatum users have access
to the Surveys application.
In the scenario shown in Figure 2,
users at Fabrikam, a smaller company, authenticate with the Tailspin
identity provider (step 1) because their own Active Directory can’t
issue tokens that will be understood by the Tailspin federation
provider. Other than the choice of identity provider, this approach is
the same as the one used for Adatum. The downside of this approach for
Fabrikam users is that they must remember a separate password to access
the Surveys application.
Tailspin plans to
implement this scenario by using an ASP.NET Membership Provider to
manage the user accounts and to use a security token service (STS) that
integrates with the membership provider.
Note:
For guidance on how to implement this scenario, take a look at the Starter STS project at http://startersts.codeplex.com.
For individual users, the process is again very similar. In the scenario shown in Figure 3,
the Tailspin federation provider is configured to trust tokens issued
by a third-party provider such as Windows Live ID or OpenID (step 1).
When the user tries to access their surveys, the application will
redirect them to their external identity provider for authentication.
Tailspin plans to build a
protocol translation STS to convert the various protocols that the
third-party providers use to the protocol used by the Surveys
application.
Note:
For guidance on how to implement this scenario, take a look at the project named “protocol-bridge-claims-provider” at http://github.com/southworks/protocol-bridge-claims-provider.
3. Inside the Implementation
Now is a good time to walk through the code that authentication
and authorization in more detail. As you go through this section, you
may want to download the Microsoft® Visual Studio® development system
solution for the Tailspin Surveys application from http://wag.codeplex.com/. The diagram in Figure 4
will be useful to help you keep track of how this process works .
The sequence shown
in this diagram applies to all three scenarios. In the context of the
diagram, the Issuer is the Tailspin federation provider, so step 3
includes redirecting to another issuer to handle the authentication. |
For clarity, Figure 4 shows the “logical” sequence, not the “physical” sequence. Wherever the diagram has an arrow with a Redirect label, this actually sends a redirect response back to the browser, and the browser then sends a request to wherever the redirect message specifies.
The following describes the steps illustrated in Figure 4:
The process starts when an unauthenticated user sends a request for a protected resource; for example the adatum/surveys page. This invokes a method in the SurveysController class.
The AuthenticateAndAuthorizeAttribute attribute that implements the MVC IAuthorizationFilter
interface is applied to this controller class. Because the user has not
yet been authenticated, this will redirect the user to the Tailspin
federation provider at https://localhost/TailSpin.SimulatedIssuer with
the following querystring parameter values:
wa. Wsignin1.0
wtrealm. https://tailspin.com
wctx. https://127.0.0.1:444/survey/adatum
whr. http://adatum/trust
wreply. https://127.0.0.1:444/federationresult
The following code example shows the AuthenticateUser method in the AuthenticateAndAuthorizeAttribute class that builds the query string.
private static void AuthenticateUser(
AuthorizationContext context)
{
var tenantName =
(string) context.RouteData.Values["tenant"];
if (!string.IsNullOrEmpty(tenantName))
{
var returnUrl =
GetReturnUrl(context.RequestContext);
// User is not authenticated and is entering
// for the first time.
var fam = FederatedAuthentication
.WSFederationAuthenticationModule;
var signIn = new SignInRequestMessage(
new Uri(fam.Issuer), fam.Realm)
{
Context = returnUrl.ToString(),
HomeRealm =
RetrieveHomeRealmForTenant(tenantName)
};
// In the Windows Azure environment,
// build a wreply parameter for the SignIn
// request that reflects the real address of
// the application.
HttpRequest request = HttpContext.Current.Request;
Uri requestUrl = request.Url;
StringBuilder wreply = new StringBuilder();
wreply.Append(requestUrl.Scheme); // HTTP or HTTPS
wreply.Append("://");
wreply.Append(request.Headers["Host"] ??
requestUrl.Authority);
wreply.Append(request.ApplicationPath);
if (!request.ApplicationPath.EndsWith("/"))
{
wreply.Append("/");
}
wreply.Append("FederationResult");
signIn.Reply = wreply.ToString();
context.Result = new
RedirectResult(signIn.WriteQueryString());
}
}
The
Issuer, in this case the Tailspin simulated issuer, authenticates the
user and generates a token with the requested claims. In the Tailspin
scenario, the Tailspin federation provider uses the value of the whr parameter to delegate the authentication
to another issuer, in this example, to the Adatum issuer. If necessary,
the Tailspin federation issuer can transform the claims it receives
from the issuer to claims that the Tailspin Surveys application understands. The following code from the FederationSecurityTokenService class shows how the Tailspin simulated issuer transforms the Group claims in the token from the Adatum issuer.
switch (issuer.ToUpperInvariant())
{
case "ADATUM":
var adatumClaimTypesToCopy = new[]
{
WSIdentityConstants.ClaimTypes.Name
};
CopyClaims(input, adatumClaimTypesToCopy, output);
TransformClaims(input,
AllOrganizations.ClaimTypes.Group,
Adatum.Groups.MarketingManagers,
ClaimTypes.Role,
TailSpin.Roles.SurveyAdministrator, output);
output.Claims.Add(
new Claim(TailSpin.ClaimTypes.Tenant,
Adatum.OrganizationName));
break;
case "FABRIKAM":
...
default:
throw new InvalidOperationException(
"Issuer not trusted.");
}
Note:
The
sample code in the simulated issuer for Tailspin contains some
hard-coded names, such as Adatum and Fabrikam, and some hard-coded claim
types. In a real issuer, these values would be retrieved from a
configuration file or store.
The Tailspin federation provider then posts the token and the value of the wctx parameter (https://127.0.0.1:444/survey/adatum) back to the address in the wreply parameter (https://127.0.0.1:444/federationresult). This address is another MVC controller (that does not have the Authen-ticateAndAuthorizeAttribute attribute applied). The following code example shows the FederationResult method in the ClaimsAuthenticationController controller.
[RequireHttps]
public class ClaimsAuthenticationController : Controller
{
[ValidateInput(false)]
[HttpPost]
public ActionResult FederationResult()
{
var fam = FederatedAuthentication
.WSFederationAuthenticationModule;
if (fam.CanReadSignInResponse(
System.Web.HttpContext.Current.Request, true))
{
string returnUrl = GetReturnUrlFromCtx();
return this.Redirect(returnUrl);
}
return this.RedirectToAction(
"Index", "OnBoarding");
}
The WSFederationAuthenticationModule validates the token by calling the CanReadSignInResponse method.
The ClaimsAuthenticationController controller retrieves the value of the original wctx parameter and issues a redirect to that address.
This time, when the request for the adatum/surveys page goes through the AuthenticateAndAuthorizeAttribute
filter, the user has been authenticated. The following code example
shows how the filter checks whether the user is authenticated.
public void OnAuthorization(
AuthorizationContext filterContext)
{
...
if (!filterContext.HttpContext.User
.Identity.IsAuthenticated)
{
AuthenticateUser(filterContext);
}
else
{
this.AuthorizeUser(filterContext);
}
}
The AuthenticateAndAuthorizeAttribute filter then applies any authorization rules. In the Tailspin Surveys application, the AuthorizeUser method verifies that the user is a member of one of the roles listed where the AuthenticateAndAuthorize attribute decorates the MVC controller, as shown in the following code example.
[AuthenticateAndAuthorize(Roles = "Survey Administrator")]
[RequireHttps]
public class SurveysController : TenantController
{
...
}
The controller method finally executes.
4. Protecting Session Tokens in Windows Azure
By default, when you use the
Windows Identity Foundation (WIF) framework to manage your identity
infrastructure, it encrypts the contents of the cookies that it sends to
the client by using the Windows Data Protection API (DPAPI). Using the
DPAPI for cookie encryption is not a workable solution for an
application that has multiple role instances because each role instance
will have a different key, and the Windows Azure load balancer could
route a request to any instance. You must use an encryption mechanism,
such as RSA, that uses shared keys. The following code example shows how
the Surveys application configures the session security token handler to use RSA encryption.
An ASP.NET web application running in a web farm would also need to use shared key encryption instead of DPAPI. |
Note:
For
more information about using the DPAPI and shared key encryption
mechanisms to encrypt configuration settings, see “How To: Encrypt
Configuration Sections in ASP.NET 2.0 Using DPAPI” on MSDN (http://msdn.microsoft.com/en-us/library/ff647398.aspx).
For more information about the DPAPI, see “Windows Data Protection” on MSDN (http://msdn.microsoft.com/en-us/library/ms995355.aspx).
private void OnServiceConfigurationCreated(object sender,
ServiceConfigurationCreatedEventArgs e)
{
var sessionTransforms =
new List<CookieTransform>(
new CookieTransform[]
{
new DeflateCookieTransform(),
new RsaEncryptionCookieTransform(
e.ServiceConfiguration.ServiceCertificate),
new RsaSignatureCookieTransform(
e.ServiceConfiguration.ServiceCertificate)
});
var sessionHandler = new
SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());
e.ServiceConfiguration.SecurityTokenHandlers.AddOrReplace(
sessionHandler);
}
The Application_OnStart method in the Global.asax.cs file hooks up this event handler to the FederatedAuthentication module.