Eugenio announced yesterday the kickoff of a new guide from patterns & practices in which I’m collaborating: Claims based Authentication & Authorization Guide.

This is not a new topic as Eugenio suggests in his blog, but it’s getting more and more attention because:

  • Technology is more mature, hence it’s easier to implement claim-based identity
  • Enterprises are failing to control the amount of different identity repositories, leading to higher provisioning/deprovisioning costs, security problems, etc.
  • End users want simpler user experiences and less passwords
  • The cloud makes all these even more challenging

We started with this project a couple of weeks ago planning the content. The approach we decided to use was heavily driven by scenarios (aka zero bulls**t). We used the visual metaphor of a tube map with scenarios being the stations separated in two main lines:

  • The blue one, the Enterprise track approaches the federated identity problem from the point of view of a company with many applications that wants to implement SSO and Federation. The main stations are SSO (within the enterprise), Federation (with partners), SOAP Web Services (and flow of identity across services), SSO with a third party cloud app and some variations like: what if the company decides to host an application on the cloud (namely Windows Azure); or what if the company needs to integrate with an application that talks SAML protocol (i.e. Salesforce, Google Apps)
  • The yellow one, ISV track on the other hand tackle the problem from the perspective of an ISV that wants to offer an application as a service (think about Salesforce or Dynamics CRM Online as the canonical examples). In this track we start by explaining how to implement federated identity for a cloud application. Then we show how to automate federation to on board new customers. We also show things like exposing a REST API and how that plays with claims; how to integrate with LiveID (or OpenID) for small customers that don’t have an Identity Provider in place; and we end up explaining how to do auditing/billing with claims.

image

I’m very proud and excited about being part of such a great team including: Dominick Baier, Vittorio Bertocci, Keith Brown, David Hill and Eugenio Pace. I’m sure that something great will come up from this team, the board of reviewers and the community that will help to prioritize and keep the focus!

In my last post I talked about an identity roadmap and how we are helping companies to achieve Level 1: Externalizing Authentication. In this first level, we only care about checking the credentials of a user in a Security Token Service and issue a token with a couple of claims. That token will be enough to prove access to the application.

Reaching the Level 1 will make a great difference to the IT department of a given company. By having a central login place they will be able to answer to questions like “when someone logged in to a certain application?”, “which applications someone used in this timeframe?”, etc. In terms of governance, having a single way to implement login will allow the architecture department (if any) to decrease security threats because there is a single well thought piece of infrastructure to perform user authentication across all apps. As a side effect it will also reduce costs of development and maintenance.

Level 2 talks about the authorization process. The authorization decision happens near the application or the service because it knows about the resource (each application has a different domain model).

Geneva Server – Level 1 Authentication

  • SSO & Federation – unified login experience and federation with partners
  • Centralized Claim Mapping management
  • Externalize authentication
  • Near the identity provider
  • Questions it will answer
    • When a subject logged in to an app?
    • What claims the subject presented to the app?

Policy Server  - Level 2 Authorization

  • Policy Enforcement
  • Centralized Policy Rules management
  • Externalize authorization
  • Near the application
  • Questions it will answer
    • What permissions did the subject requested?
    • What permissions where denied?

The following figure shows a very high level architecture of the components and its interactions

image

Resources

[XACML] http://www.oasis-open.org/committees/xacml/
[Geneva Server] http://connect.microsoft.com/content/content.aspx?ContentID=10106&SiteID=642
[SAML] http://www.oasis-open.org/specs/#samlv2.0

The following table shows an analogy of identity concepts between a single application and a federated application.

The single app has its own identity silo and the federated app relies on an STS (like Geneva Server). I find this analogy useful to explain how things differ from the non-federated non-claim-based world.

image

During the last couple of months I’ve been helping the Microsoft DPE team (namely Vittorio and Donovan) building the Identity Development Training Kit. It’s been great to work with such knowledgeable guys like them and with one of the best frameworks I’ve ever developed with: Microsoft Geneva Framework. Identity Training Kit

The training kit covers a lot of interesting scenarios related to claim-based identity. Here is the shortcut list (if you want a full explanation of each one, read Vittorio’s post)

  • Lab: Web Sites and Identity:
    • Exercise 1: Enabling claims based access for an ASP.NET Web Application by generating a local STS
    • Exercise 2: Customizing the Credentials Accepted by a Local STS
    • Exercise 3: Accepting Tokens from a Geneva Server STS
    • Exercise 4: Accepting Tokens from Live ID
    • Exercise 5: Accepting Tokens from .NET Access Control Service
    • Exercise 6: Invoking a WCF Service on the Backend via Delegated Access
  • Lab: Enhancing an ASP.NET Membership Provider Website with Identity Provider Capabilities
  • Lab: Web Services and Identity
    • Exercise 1: Using Geneva Framework for Handling Authentication and Authorization in a WCF Service
    • Exercise 2: Accepting Tokens from a Geneva Server STS
    • Exercise 3: Accepting Tokens from .NET Access Control Service
    • Exercise 4: Invoking a WCF Service on the Backend via Delegated Access

We made sure that all of the exercises followed the best practices of developing with Geneva Framework. Building this training kit was a big effort and I would like to mention the great team that helped creating this:  Ariel “lutz” Neisen, Jonathan “passive” Cisneros, Ezequiel “checklist” Sculli and Sebastian “pattern” Iacomuzzi

I invite you to take a look at the training kit and open your mind with the new possibilities the Geneva Framework brings into the table.

 
One of the things I didn’t like of the WSFederationHttpBinding is that it encapsulates lots of things. In particular, the call against the STS to obtain a SAML token. I wanted to have control over that process.  The good news is that the Geneva Framework allow us to do all that in a very simple fashion (after using some Lutz :)).
So, forget about custom WCF IssuedSecurityTokenProviders, and welcome WSTrustClient and FederatedClientCredentials!
 

Calling your STS to obtain a token

 
The first thing you might want to do is call your STS to obtain a token. This is the simplest way to do it:
 
private SecurityToken GetIdentityProviderToken()
{
    var client = new WSTrustClient(
                                    this.identityProviderActiveStsBinding,
                                    this.identityProviderActiveStsUrl,
                                    System.ServiceModel.Security.TrustVersion.WSTrustFeb2005,
                                    new System.ServiceModel.Description.ClientCredentials());

    client.ClientCredentials.ServiceCertificate.DefaultCertificate = this.identityProviderStsCertificate;
    var request = new RequestSecurityToken(RequestTypeConstants.Issue);
    request.AppliesTo = this.identityProviderAppliesToUrl;

    var identityToken = client.Issue(request);
    client.Close();
    return identityToken;
}

 

Overriding the ClientBase to inject the security token with Geneva

Once you have the token you want to inject that token into your client proxy. The code below shows a nice and clean way to inject the SAML token into the WCF channel. Notice the ctor takes a dependency on a custom interface ISecurityTokenProvider. This is a very simple interface I created with a single method GetSecurityToken. On channel construction we are going to pass the proxy through Geneva CreateChannelWithIssuedToken method. This will change the ClientCredentials of the channel to use the FederatedClientCredentials that will shortcircuit the IssuedSecurityTokenProvider of the binding by returning the "external" security token.

public class FederatedCatalogServiceClient : ClientBase<ICatalogService>, ICatalogService
{
    private ISecurityTokenProvider tokenProvider;

    public FederatedCatalogServiceClient(ISecurityTokenProvider tokenProvider, …)
    {
        this.tokenProvider = tokenProvider;
    }

    // operations …

    protected override ICatalogService CreateChannel()
    {
        ICatalogService channel;
        lock (this.ChannelFactory)
        {
            FederatedClientCredentials.ConfigureChannelFactory(this.ChannelFactory);
            channel = ChannelFactoryOperations.CreateChannelWithIssuedToken(
                this.ChannelFactory,
                this.tokenProvider.GetSecurityToken());
        }

        return channel;
    }
}

 

Custom Binding to call a service that requires a token

Finally, since we don’t want to use WSFederationBinding anymore because it will grab the token for us, the code below will create a binding "similar" to the federation binding but won’t never call our STS. We still have to provide some configuration settings like the urls so the binding can be constructed, but they won’t be used on runtime.

public Binding CreateBinding()
{
    var finalBinding = new CustomBinding();

    var bindingProtection = new X509SecurityTokenParameters(X509KeyIdentifierClauseType.Thumbprint);
    bindingProtection.ReferenceStyle = SecurityTokenReferenceStyle.Internal;
    bindingProtection.InclusionMode = SecurityTokenInclusionMode.Never;
    bindingProtection.X509ReferenceStyle = X509KeyIdentifierClauseType.Thumbprint;

    var security = new SymmetricSecurityBindingElement(bindingProtection);

    security.MessageSecurityVersion = MessageSecurityVersion.WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;
    security.RequireSignatureConfirmation = true;

    var issuerBinding = new CustomBinding();

    issuerBinding.Elements.Add(new TransactionFlowBindingElement());

    var protection = new SecureConversationSecurityTokenParameters();
    var issuerSecurity = new SymmetricSecurityBindingElement(protection);
    issuerSecurity.MessageSecurityVersion = MessageSecurityVersion.WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;

    issuerBinding.Elements.Add(issuerSecurity);

    security.EndpointSupportingTokenParameters.Endorsing.Add(
        new IssuedSecurityTokenParameters(
            "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1",
            new EndpointAddress("http://notused"),
            issuerBinding)
        {
            IssuerMetadataAddress = new EndpointAddress("http://notused/mex")
        });

    security.ProtectionTokenParameters = new X509SecurityTokenParameters(X509KeyIdentifierClauseType.Thumbprint, SecurityTokenInclusionMode.Never);

    var protectionSecurityProtectionParameters = new SspiSecurityTokenParameters();
    protectionSecurityProtectionParameters.RequireCancellation = true;
    security.ProtectionTokenParameters.InclusionMode = SecurityTokenInclusionMode.Never;

    var protectionSecurity = new SymmetricSecurityBindingElement(protectionSecurityProtectionParameters);
    protectionSecurity.MessageSecurityVersion = MessageSecurityVersion.WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;

    protection.BootstrapSecurityBindingElement = protectionSecurity;

    finalBinding.Elements.Add(security);

    issuerBinding.Elements.Add(new TextMessageEncodingBindingElement());
    issuerBinding.Elements.Add(new HttpTransportBindingElement());

    finalBinding.Elements.Add(new TextMessageEncodingBindingElement());
    finalBinding.Elements.Add(new HttpTransportBindingElement());

    return finalBinding;
}

Happy Federation! :)

In the previous post I introduced a scenario where you can use .NET Services Access Control and Windows LiveID to delegate authentication and authorization. In this post we will go through the different pieces needed in the application to perform authorization checks. First thing will be configure the passive federation using Geneva on the application and later we will create an ASP.NET MVC action filter to perform the access check against the incoming claims.

Note: all the code showed here is using Microsoft Identity Framework "Zermatt" Beta 1. The new Geneva Framework might have some changes.

Configuring passive federation on the website

Configure passive federation on the website is about defining which SAML token version we will accept and the certificate we will use to decrypt the incoming token. The following configuration uses Zermatt Beta 1, so this probably changes on Geneva.

<microsoft.identityModel>
    <tokenHandlers>
      <remove type="Microsoft.IdentityModel.Tokens.Saml11.Saml11TokenHandler, Microsoft.IdentityModel, Version=0.4.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      <add type="Microsoft.IdentityModel.Tokens.Saml11.Saml11TokenHandler, Microsoft.IdentityModel, Version=0.4.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
        <samlSecurityTokenRequirement>
          <allowedAudienceUris>
            <add value="http://localhost/YourApp/" />
          </allowedAudienceUris>
        </samlSecurityTokenRequirement>
      </add>
    </tokenHandlers>
    <federatedAuthentication enabled="true">
    </federatedAuthentication>
    <serviceCertificate>
      <certificateReference findValue="01 20 …" storeLocation="LocalMachine" storeName="My" x509FindType="FindByThumbprint" />
    </serviceCertificate>
  </microsoft.identityModel>

When the user click on the sign in button, the link will point to to the .NET Services Access Control passive STS url. The following method uses Geneva to generate this WS-Federation url.

private static string GetFederationUrl(string realm, string issuer, string homeRealm, string returnUrl)
{
    FederatedAuthenticationModule fam = new FederatedAuthenticationModule();
    fam.Realm = realm;
    fam.Issuer = issuer;
    fam.Reply = returnUrl;
    SignInRequestMessage signInMsg = fam.CreateSignInRequest();
    signInMsg.Parameters.Add("whr", homeRealm);
    string url = signInMsg.WriteQueryString();
    return url;
}

The following code and configuration will give you an idea of the url that is being built. Pay attention to this url because a small change might break the whole thing.

string url = GetFederationUrl(ConfigurationManager.AppSettings["AccessControlRealm"],
                            ConfigurationManager.AppSettings["AccessControlIssuer"],
                            ConfigurationManager.AppSettings["AccessControlHomeRealm"],
                            replyTo);
<!– Windows Azure Federation –>
<add key="AccessControlRealm" value="http://localhost/YourApp/"/> <!– should match to a scope –>
<add key="AccessControlIssuer" value="https://accesscontrol.windows.net/passivests/yoursolution/LiveFederation.aspx"/>
<add key="AccessControlDefaultReply" value="http://localhost/YourApp" />
<add key="AccessControlHomeRealm" value="http://login.live.com" />

The AccessControlRealm config is important because it will match the scope on your solution. You will have to configure the scope to encrypt with the public key of your website certificate and create the claim mappings from Windows LiveID to your well known claims. If you don’t have the scope created or configured to output at least one claim you will get a 403 Forbidden on the .NET Services Access Control STS.

image

Performing access check in the web site

Now that we have everything configured and the token should be coming back to our website, it’s time to do the access check. By using Geneva, the token will be transformed to a Principal object and it will be accessed through the ClaimsPrincipal static class. On the other hand, ASP.NET MVC allow us to plug into the action execution pipeline and get access to the context data like route values. The following code shows an ActionFilterAttribute that will grab the claims from the the Geneva ClaimsPrincipal and will call a strategy class that will perform the access check. If the access check is not successful, the filter will render a NotAuthorized view.

namespace YourApp.Identity
{
    using System;
    …

    public class ClaimAuthorizationRouteFilterAttribute : ActionFilterAttribute
    {
        public ClaimAuthorizationRouteFilterAttribute(string[] operations)
        {
            this.Operations = operations;
        }

        public string[] Operations { get; set; }

        public override void OnActionExecuting(ActionExecutingContext context)
        {
            var identity = ClaimsPrincipal.Current.Identity as IClaimsIdentity;
            var claims = identity.Claims.ToArray();
            var routeData = context.RouteData.Values.ToArray();
            var strategy = CreateAuthorizationStrategy();
            var executionContext = new ExecutionContext()
            {
                ClaimsNeeded = Operations,
                OperationContextData = routeData,
            };

            if (!strategy.IsAuthorizedFor(executionContext, claims))
            {
                context.Result = new ViewResult
                {
                    ViewName = "NotAuthorized"
                };
            }

            base.OnActionExecuting(context);
        }

    }
}

Finally, the following code shows an implemented strategy for a multi tenant application that manage projects.

namespace YourApp.Identity
{
    using System.Linq;
    using System;

    public class StandardAuthorizationStrategy : IAuthorizationStrategy
    {
        private const string ProjectClaimType = "urn:Project";
        private const string TenantClaimType = "urn:Tenant";
        private const string OperationClaimType = "urn:Operation";

        public bool IsAuthorizedFor(ExecutionContext context, Microsoft.IdentityModel.Claims.Claim[] claims)
        {
            bool authorized = true;
            var tenantClaim = claims.SingleOrDefault(c => c.ClaimType == TenantClaimType);
            var operationClaims = claims.Where(c => c.ClaimType == OperationClaimType);
            var projectClaims = claims.Where(c => c.ClaimType == ProjectClaimType);
        var tenant = context.OperationContextData["Tenant"].ToString();
        var project = context.OperationContextData["Project"].ToString();

            if (!string.IsNullOrEmpty(tenant))
            {
                authorized &= tenantClaim.Value.Equals("*", StringComparison.OrdinalIgnoreCase) ||
                              tenantClaim.Value.Equals(tenant, StringComparison.OrdinalIgnoreCase);
            }

            if (!string.IsNullOrEmpty(project))
            {
                authorized &= projectClaims.Where( p => p.Value.Equals("*", StringComparison.OrdinalIgnoreCase)).Count() > 0 ||
                              projectClaims.Where( p => p.Value.Equals(project, StringComparison.OrdinalIgnoreCase)).Count() > 0;
            }

            if (context.Operations != null)
            {
                bool temp = true;
                foreach (string op in context.ClaimsNeeded)
                {
                    temp &= operationClaims.Where(o => o.Value.Equals(op, StringComparison.OrdinalIgnoreCase)).Count() > 0 ||
                            operationClaims.Where(o => o.Value.Equals("*", StringComparison.OrdinalIgnoreCase)).Count() > 0;
                }
                authorized &= temp;
            }

            return authorized;
        }
    }
}

The only thing left is to put an attribute above the action. The following attribute specifies that the New action will be executed if the incoming token contains the following "urn:Operation" claims.

public class ProjectsController : Controller
{

        [ClaimAuthorizationRouteFilter(new string[] {
                                            "AddUser",
                                            "AddUsersToProject",
                                            "CreateProject" })]
        public ActionResult New() {
        ….

    }

    …

}

So if a user browses to: http://yourapp/Contoso/Projecsts/New, the filter will call the strategy that will check:

  • if the user contains a tenant claim with the value "Contoso" (taken from the route data)
  • if the user contains three operation claims: AddUser, AddUsersToProject and CreateProject

And if a user browses to: http://yourapp/Contoso/Projecsts/some-project/Edit, the filter will call the strategy that will check:

  • if the user contains a "tenant" claim with the value "Contoso" (taken from the route data)
  • if the user contains the "operation" claims specified in the Edit action
  • if the user contains a "project" claim with the value "some-project"

image

The last couple of months together with other people at Southworks we’ve been working with the DPE team on samples, demos, hands on labs for PDC all related to the cloud computing services Microsoft announced at PDC, the Azure Services Platform. 

During the week, I attended Kim Cameron’s and Vittorio Bertocci session where they talked about identity federation and claim based architecture using "Geneva" Server, Microsoft Federation Gateway, "Geneva" Framework (previously known as Zermatt) and the .NET Services Access Control. I enjoyed watching Vittorio during the session. 

Other interesting things we did in the identity arena with Ryan Dunn is use the .NET Services Access Control and Windows Live ID to delegate authentication and authorization to the cloud. In this post I will introduce the scenario where you can federate your application against .NET Services Access Control which indeed federates against Windows LiveID. This will allow users of your application to log in to your application using their Windows LiveID account and then use .NET Services Access Control to transform the email claim to a set of claims to perform authorization access checks.

Identity + Access Control using Windows Live ID + .NET Services Access Control

Windows Live ID can authenticate users of your web site and then use .NET Services Access Control to map claims between the Live ID (email) and some other claim (like role, operation, task). The image below shows a claim mapping that you would create in your .NET Services account.

image

The output claims could be used later in the application to perform access check against resources or modify the UI according to the incoming claims. The flow is governed by the WS-Federation protocol as shown below:

 

image

In a nutshell, the browser will click on the Sign In link on the website and it will be redirected to the token issuer, in this case the .NET Services Access Control passive STS. The home realm on the url will be login.live.com and the .NET Services STS trust on Windows LiveID tokens. The user will log in on Windows LiveID and it will send the token back to the .NET Services STS. Finally the claim mapping will occur and the token will come back to the website with the authorization claims.

In the following post I will show how to configure your application to read the incoming token claims and do access check over page urls.

In these series I want to show the usage of Zermatt to solve some typical scenarios in identity management. I will assume that the reader is already familiar with concepts like security token service, claims, tokens, credentials, etc. If not, you can read this article from Vittorio Bertocci on July 2008 issue of the Architecture Journal.

This first post will be about the simplest scenario in identity management: the Active Client with a single STS. In other words, a client that calls a web service with a policy that says that you need to use a certain token to talk to him.

image

 

The sequence of actions in this scenario is:

  1. Since the client needs to obtain a token before talk to the service, it will make a WS-Trust call to the STS sending some kind of credentials. It could be Windows credentials, a X509 certificate, username and password or maybe another token (we’ll leave that for a future post).
  2. The STS will authenticate the caller and probably will output some claims about him. These claims might come from some repository like a database or AD. If the client is using Windows credentials, maybe the claims will be the groups the user belongs, the email and the full name. If the client is using username and password, the claims could be the roles stored in a database table. However, the claims could be anything you want that will be used later to perform access checks on the service.
  3. The response is sent to the client containing an RSTR (Request Security Token Response) that will contain the requested security token with the claims. This token can be encrypted so only the service can decrypt it. For doing that, the STS will use the public key of the service certificate. The token will be also signed by the STS to avoid untrusted issuers. To sign the token, the STS will use a private key.
  4. Finally, the client calls the service using the token obtained in 3. The service will only accept tokens of trusted issuers. Since there is a trust relationship between our STS and the service, the latter will have the public key of the STS that will allow him to check the signature of the token.

Having the sequence of things defined, we’ll see how this can be achieved using Zermatt Beta 1. First, let me put some context on some of the decisions I will take.

One of the things I really like about Zermatt is the WSTrustClient class. This simple class allows anyone to issue a RST to an STS. If you ever tried to implement the approach depicted above with WCF you might have met with the wsFederationHttpBinding. This binding encapsulates the sequence above so the client doesn’t have to worry about the behind the scenes of obtaining tokens. While this is good in some cases, when I started playing with this identity architecture, I felt that there was some kind of "black magic" happening. As someone that uses Reflector as its primary tool to solve most of the challenges with emergent technologies, I wanted to know how all this worked and I wanted to have control over the tokens. Well, knowing the theory helps a lot. I also recommend you to use WSTrustClient  and "do it yourself".

[1] Issue token

We will use a simple username and password to call the STS. The following method uses the WSTrustClient class to call the STS in localhost:6001/IPSTS. Notice the usage of WCF WSHttpBinding with Windows credentials. The RequestType property of the RequestSecurityToken class is not the regular WS-Trust issue SOAP action URI(http://schemas.xmlsoap.org/ws/2004/04/security/trust/RST/Issue), but a custom one. This is because Zermatt supports different WS-Trust versions and will do the transformation to the correct URI on runtime depending on the WSTrust version (by default is Feb 2005 version)

static SecurityToken GetToken(string username, string password)
{
    var binding = new WSHttpBinding(SecurityMode.Message);
    binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
    binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;

    var ipAddress = new EndpointAddress("http://localhost:6001/IPSTS");

    var client = new WSTrustClient(binding, ipAddress);

    client.ClientCredentials.Windows.ClientCredential.UserName = username;
    client.ClientCredentials.Windows.ClientCredential.Password = password;

    var rst = new RequestSecurityToken();
    rst.RequestType = "http://schemas.microsoft.com/idfx/requesttype/issue";
    var token = client.Issue(rst);
    return token;
}

[2] Authenticate & fill tokens with claims

The STS in Zermatt have two methods to override. The first is GetScope which provides information related to the "scope" where this STS will be used (things like certificates to use to encrypt, to sign, etc) The second is GetOutputSubjects that will provide information about the subject (like claims). Here we are creating one claim of type http://schemas.xmlsoap.org/claim/Group for each group the user belongs.

public override ClaimsIdentityCollection GetOutputSubjects(Scope scope, IClaimsPrincipal principal, RequestSecurityToken request)
{
    var claimsIdentities = new ClaimsIdentityCollection();
    var wI = (WindowsIdentity)principal.Identity;
    var identity = new ClaimsIdentity(principal.Identity);

    foreach (IdentityReference iD in wI.Groups)
    {
        var groupName = new SecurityIdentifier(iD.Value).Translate(typeof(NTAccount)).ToString();
        identity.Claims.Add(new Claim(Constants.GroupClaimType, groupName));
    }
    claimsIdentities.Add(identity);

    return claimsIdentities;
}

[3] Encrypting and signing the RSTR

In the GetScope method we are indicating how we are going to encrypt and sign the token

protected override Scope GetScope(IClaimsPrincipal principal, RequestSecurityToken request)
{
    Scope scope = new Scope(request, SecurityTokenServiceConfiguration.SigningCredentials);

    scope.EncryptingCredentials = new X509EncryptingCredentials(
                                      CertificateUtil.GetCertificateByThumbprint(StoreName.TrustedPeople,
                                                                      StoreLocation.LocalMachine,
                                                                      Constants.EncryptingCertificateThumbprint));
    return scope;
}

[4] Call the service using the token

At this point the client have a token in his hands. Now we need to use it on the outgoing message (on the WS-Security header). Here, we use the WSTrustClientCredentials class and set the SecurityToken we obtained before.

private static void CallService(SecurityToken token)
{
    var client = new ShippingManagerClient();

    var credentials = new WSTrustClientCredentials();
    credentials.IssuedSecurityToken = token;
    client.Endpoint.Behaviors.Remove<ClientCredentials>();
    client.Endpoint.Behaviors.Add(credentials);
    credentials.ConfigureChannel(client.InnerChannel);

    client.NewShipment();
    client.Close();
}

The client will use a custom binding (not the wsFederationHttpBinding). Notice the issuer address is http://notused. We can do this because we are using WSTrustClientCredentials that creates a custom WCF IssuedSecurityTokenProvider. This provider will shortcircuit the interaction with the issuer and will simply return the token we set in the IssuedSecurityToken on WSTrustClientCredentials.

<bindings>
    <customBinding>
        <binding name="CustomBinding_IShippingManager">
            <security defaultAlgorithmSuite="Default" authenticationMode="IssuedTokenForCertificate"
                requireDerivedKeys="true" securityHeaderLayout="Strict" includeTimestamp="true"
                keyEntropyMode="CombinedEntropy" messageProtectionOrder="SignBeforeEncryptAndEncryptSignature"
                messageSecurityVersion="WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10"
                requireSignatureConfirmation="true">
                <issuedTokenParameters keyType="SymmetricKey" tokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1">
                    <issuer address="http://notused" binding="wsHttpBinding" />
                    <issuerMetadata address="http://notused/mex" />
                </issuedTokenParameters>
                <localClientSettings cacheCookies="true" detectReplays="true"
                    replayCacheSize="900000" maxClockSkew="00:05:00" maxCookieCachingTime="Infinite"
                    replayWindow="00:05:00" sessionKeyRenewalInterval="10:00:00"
                    sessionKeyRolloverInterval="00:05:00" reconnectTransportOnFailure="true"
                    timestampValidityDuration="00:05:00" cookieRenewalThresholdPercentage="60" />
                <localServiceSettings detectReplays="true" issuedCookieLifetime="10:00:00"
                    maxStatefulNegotiations="128" replayCacheSize="900000" maxClockSkew="00:05:00"
                    negotiationTimeout="00:01:00" replayWindow="00:05:00" inactivityTimeout="00:02:00"
                    sessionKeyRenewalInterval="15:00:00" sessionKeyRolloverInterval="00:05:00"
                    reconnectTransportOnFailure="true" maxPendingSessions="128"
                    maxCachedCookies="1000" timestampValidityDuration="00:05:00" />
                <secureConversationBootstrap />
            </security>
            <textMessageEncoding maxReadPoolSize="64" maxWritePoolSize="16"
                messageVersion="Default" writeEncoding="utf-8">
                <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="65536"
                    maxBytesPerRead="4096" maxNameTableCharCount="16384" />
            </textMessageEncoding>
            <httpTransport manualAddressing="false" maxBufferPoolSize="524288"
                maxReceivedMessageSize="65536" allowCookies="false" authenticationScheme="Anonymous"
                bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
                keepAliveEnabled="true" maxBufferSize="65536" proxyAuthenticationScheme="Anonymous"
                realm="" transferMode="Buffered" unsafeConnectionNtlmAuthentication="false"
                useDefaultWebProxy="true" />
        </binding>
    </customBinding>
</bindings>

On the service side we want to check that the token was issued by someone we trust. To do that, Zermatt provides something called SamlSecurityTokenRequirements (this code is somewhere in the host).           

var handler = collection[typeof(SamlSecurityToken)] as Saml11TokenHandler;
handler.SamlSecurityTokenRequirement.IssuerTokenAuthenticators.Clear();
handler.SamlSecurityTokenRequirement.IssuerTokenAuthenticators.Add(
   new X509SecurityTokenAuthenticator(
      new TokenIssuerCertificateValidator(Constants.IssuerCertificateThumbprint)));   

The TokenIssuerCertificateValidator derives from X509CertificateValidator which has a virtual method ValidateToken. The input parameter of this method is the certificate used to sign the token. The following code will check the thumbprint of the incoming token is the one we expect.

public override void Validate( X509Certificate2 incoming )
{
    if ( incoming.Thumbprint != issuerCertificate.Thumbprint )
    {
        throw new SecurityTokenException( "Issuer certificate validation failed" );
    }
}

Since the service has the private key that allows decrypting the token, we can read the claims. Zermatt will populate the ClaimsPrincipal object that will be accessible from any place in the service pipeline. The WCF ServiceAuthorizationManager might be the place where you want to do check access using the ClaimsPrincipal.

var identity = ClaimsPrincipal.Current.Identity as IClaimsIdentity;
identity.Claims…
 
This is the end of the first post on Identity and Zermatt. Next one will be the passive client.

image Couple of weeks ago I posted about Zermatt and how Security Token Services and Claim Based authorization can help  in the Identity Management area.

Sebastian who has been working with Zermatt for a couple of weeks already, is posting a useful “straight to the point” how to implement active and passive STS’s using Zermatt. The abstractions in Zermatt are making this a joy. I like the separation of the STS from the underlying host (i.e. ASP.NET, WCF, “put-the-name-of-the-next-foundation”) because allows you to reuse the same STS for both the service layer and the presentation layer and have a consistent access control mechanism on both layers using claims.

Also, while we are on the subject, I recommend you to read the latest Architecture Journal on Identity. I just read Vittorio’s article and it has all the things you need to know about the underlying concepts.