Using the .NET Access Control Service with Geneva

If you haven’t checked out the .NET Access Control Service yet – I can highly recommend it!

Justin did two talks about it at PDC:

In the 2nd talk Justin showed how to use and process claims coming from the ACS rules engine in your own services. You can find this code in the “CardSpace Calculator” sample in the ACS SDK.

The sample uses the “old” WCF plumbing to process tokens and create claims based on that. I wanted to find out what has to be done to migrate the sample to use Geneva.

First let’s have a look at what you want to accomplish with such a scenario:

  • your clients live in a different trust domain as your service
  • to federate these two domains, you do the following
    • register your client’s identity provider (Live ID, Geneva Server/Framework, other WS-Trust 1.3 compat STS) at the ACS
    • your client obtains a token from the ACS (by sending their identity token)
    • this token is used to authenticate with your service
    • your service accepts tokens from the ACS and uses their claims for identity related work

For your service this means the following:

In Geneva terms this boils down to the following pieces of plumbing:

  • issuer name registry that knows about the ACS issuer certificate
  • security token handler that check the SAML issuer name

Issuer Registry
There are two ways to accomplish this. Either you use the standard ConfigurationBasedIssuerNameRegistry and add the ACS issuer thumbprint to it:

<microsoft.identityModel>
  <
issuerNameRegistry type=“…ConfigurationBasedIssuerNameRegistry>
    <
trustedIssuers>
      <
add name=http://accesscontrol.windows.net
          
thumbprint=416E6FA5D982B096931FBF42C4A3DCD608856C95 />
    </
trustedIssuers>
  </
issuerNameRegistry>
</
microsoft.identityModel>

Or you write a custom registry that has the ACS issuer baked in:

class AccessControlServiceIssuerRegistry : IssuerNameRegistry
{
  public override string GetIssuerName(SecurityToken securityToken)
  {
      X509SecurityToken token = securityToken as X509SecurityToken;
      if (token == null)
      {
        throw new SecurityTokenException(“Token is not a X509 Security Token”);
      }

      var cert = token.Certificate;
      if (cert.Thumbprint.Equals(“416E6FA5D982B096931FBF42C4A3DCD608856C95”, StringComparison.OrdinalIgnoreCase))
      {
        return http://accesscontrol.windows.net&#8221;;
      }

      throw new SecurityTokenException(“Token not issued by access control service”);
  }
}

Security Token Handler
Once you trust the issuer, you also have to make sure that the token got issued by your personal instance of the ACS. This is done by checking the SAML issuer name information in the token. This is the job of a custom security token handler – you can simply derive from the existing Saml11SecurityTokenHandler and inject this logic in the ValidateToken method.

class AccessControlServiceSaml11SecurityTokenHandler : Saml11SecurityTokenHandler
{
    string _solution;

    public AccessControlServiceSaml11SecurityTokenHandler(string solution)
      : base()
    {
        _solution = solution;
    }

    public override ClaimsIdentityCollection ValidateToken(SecurityToken token)
    {
        var identities = base.ValidateToken(token);

        identities[0].DemandClaim(
            ClaimTypes.SamlIssuerName,
            http://accesscontrol.windows.net/&#8221; + _solution);

        return identities;
    }
}

Bringing the pieces together
You can now wire up the two custom components using the FederatedServiceCredentials class like this:

ServiceHost host = new ServiceHost(typeof(CalculatorService));

var handlers = new SecurityTokenHandlerCollection();
handlers.Add(new AccessControlServiceSaml11SecurityTokenHandler(“leastprivilege”));

FederatedServiceCredentials.ConfigureServiceHost(
    host,
    handlers,
    new AccessControlServiceIssuerRegistry(),
    TimeSpan.FromMinutes(5));

FederatedServiceCredentials.ConfigureServiceHost(host);
host.Open();

 

Another option would be to use the configuration file – but in this case we somehow have to transfer the solution name to the security token handler. Geneva security token handlers have a standard configuration extensibility hook that we can use to accomplish this (not very obvious at the beginning – but makes sense ;).

Let’s say our configuration should look like this:

<microsoft.identityModel>
  <
issuerNameRegistry type=LeastPrivilege.AccessControlServiceIssuerRegistry, Service />

  <securityTokenHandlers>
    <
clear />
    <
add type=LeastPrivilege.AccessControlServiceSaml11SecurityTokenHandler, Service>
      <
accessControlServiceTokenRequirement solutionName=leastprivilege />
    </
add>
  </
securityTokenHandlers>
</
microsoft.identityModel>

 

To make the token handler aware of this sub config element, you have to add a new constructor to the token handler like this:

public AccessControlServiceSaml11SecurityTokenHandler(XmlElement customConfigElement)
    : base()
{
    if (customConfigElement.LocalName != “accessControlServiceTokenRequirement”)
    {
        throw new InvalidOperationException(“accessControlServiceTokenRequirement expected”);
    }

    var solutionAttr = customConfigElement.Attributes[“solutionName”];
    if (solutionAttr == null)
    {
        throw new InvalidOperationException(“solution name expected”);
    }

    _solution = solutionAttr.Value;
}

(if you also need support for the samlSecurityTokenRequirement element, you can manually call LoadSamlTokenAuthenticatorRequirement on the SecurityTokenHandlerElement class).

Here you can find the complete code – to get it working I’d suggest you first get the original SDK sample up and running. After that you have to make the necessary adjustments in my code (solution name, certificates and URIs).

HTH

 

This entry was posted in ASP.NET, IdentityModel, WCF. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s