Windows Identity Foundation 101’s : WS-Federation Passive Requestor Profile (part 1 of 2)

Background

It is becoming more commonplace for the means of authenticating a user to be externalized away from the content provider. In federation parlance the content provider is known as the Relying Party (RP) and is so named because it is reliant upon an external entity for authentication, that entity being known as the Identity Provider (IdP). The diagram below depicts this relationship where the RP and IdP are separate entities linked by a trust relationship. i.e – the RP trusts that the IdP can correctly authenticate and assert information about the user (we might expect that the IdP is trusted by several other RP’s or that the RP trusts several IdP’s but this simplest of scenarios is a good starting point from which to evolve the discussion).

Simplest_4_02DA8180 

The steps towards giving a user access to the RP content are as follows:

  1. End user attempts to access the RP
  2. The RP has no knowledge of the user and instructs the client to send the user off to the IdP for authentication
  3. The user authenticates and the IdP instructs the client to send the user back to the RP with a token (generally comprised of XML but not exclusively so) representing the users identity
  4. The RP validates the token and gives access to the user

In this scenario the client application can either:

  • Simply route traffic between the RP/IdP with no modifications
    • This is a ‘passive’ client, a good example being a web browser executing GET/POST redirects
  • Compose the messages based upon metadata served up by the RP/IdP
    • This is an ‘active’ client, one example being an browser control that sends SOAP messages into the RP/IdP based upon the published policies

Here is a more comprehensive description of active and passive clients.

What follows is a demonstration of how WIF trivialises the effort required to mediate between RP/IdP using the WS-Federation passive profile.

You will need the following installed:

Create a simple Relying Party application for sending requests

1. Create an ASP.NET web application called WIFRelyingParty with the following attributes:

  • Accessible at https://localhost/WIFRelyingParty
  • Has a basic web page called Content.aspx
  • Has references to Microsoft.IdentityModel.dll and System.ServiceModel.dll
  • Has an assembly name and default namespace of ‘WIFRelyingParty’

2. In the Page_Load event handler for Content.aspx.cs, insert the following code (you will need to add the using directives yourself):

 // This is a new request
SignInRequestMessage mess = new SignInRequestMessage(
    new Uri("https://localhost/WIFIdentityProvider/Login.aspx"),
    "https://MyRealm", "https://localhost/WIFRelyingParty/Content.aspx");
 
HttpContext.Current.Response.Redirect(mess.WriteQueryString());
 
// You could also do this
// HttpContext.Current.Response.Write(mess.WriteFormPost());
// HttpContext.Current.Response.End();

 

As you can see, two lines of code are all it takes to create an industry standard request message and instruct the browser to perform a HTTP GET against the IdP (you could also POST). The parameters to the SignInRequestMessage constructor are as follows:

  • IdP endpoint– where the signin request is going
  • RP realm– identifies the RP to the IdP
  • Reply address– the url that the IdP should redirect the browser back to

Here is a snippet of what the IdP recieves:

 GET /WIFIdentityProvider/Login.aspx?wa=wsignin1.0&wtrealm=http%3a%2f%2fMyRealm&wreply=http%3a%2f%2flocalhost%2fWIFRelyingParty%2fContent.aspx HTTP/1.1

 

The set of query string parameters starting with ‘w’ are the means of conveying information from RP to IdP and vice versa. There are more than those shown in this example and I encourage you to read further if you want to find out more. I will however be introducing some of these parameters in later posts.

Create a simple Identity Provider application for processing requests

1. Create an ASP.NET web application called WIFIdentityProvider with the following attributes:

  • Accessible at https://localhost/WIFIdentityProvider
  • Has a basic web page called Login.aspx
  • Has references to Microsoft.IdentityModel.dll, System.IdentityModel.dll and System.ServiceModel.dll
  • Has an assembly name and default namespace of ‘WIFIdentityProvider’

2. Add a new class to the project called CustomSecurityTokenService.cs and overwrite it with the following code:

 namespace WIFIdentityProvider
{
    using Microsoft.IdentityModel.Claims;
    using Microsoft.IdentityModel.Configuration;
    using Microsoft.IdentityModel.Protocols.WSTrust;
    using Microsoft.IdentityModel.SecurityTokenService;
 
    public class CustomSecurityTokenService : SecurityTokenService
    {
        public CustomSecurityTokenService(SecurityTokenServiceConfiguration config)
            : base(config)
        {
        }
 
         // Returns information about the target of the token issuance
        protected override Scope GetScope(IClaimsPrincipal principal, RequestSecurityToken request)
        {
            Scope scope = new Scope(request.AppliesTo.Uri.OriginalString, SecurityTokenServiceConfiguration.SigningCredentials);
            scope.TokenEncryptionRequired = false;
            scope.ReplyToAddress = request.ReplyTo;
            return scope;
        }
 
         // Returns an IClaimsIdentity implementation populated with claims about the user
        protected override IClaimsIdentity GetOutputClaimsIdentity(IClaimsPrincipal principal, RequestSecurityToken request, Scope scope)
        {
            return new ClaimsIdentity();
        }
    }
}

 

3. In the Page_Load event handler for Login.aspx.cs, insert the following code:

 

 protected void Page_Load(object sender, EventArgs e)
{
    string action = Request.QueryString[WSFederationConstants.Parameters.Action];
 
     // This check is not strictly necessary for a prototype but is good practice
    if (action == WSFederationConstants.Actions.SignIn)
    {
        SignInRequestMessage requestMessage = (SignInRequestMessage)WSFederationMessage.CreateFromUri(Request.Url);
        SecurityTokenService sts = new CustomSecurityTokenService(
            new SecurityTokenServiceConfiguration(
                “https://WIFIdentityProvider”,
                new X509SigningCredentials(GetCertificate(StoreName.My, StoreLocation.LocalMachine, "<certificate subject>"))));

        SignInResponseMessage responseMessage = 
            FederatedPassiveSecurityTokenServiceOperations.ProcessSignInRequest(requestMessage, User, sts);
 
        FederatedPassiveSecurityTokenServiceOperations.ProcessSignInResponse(responseMessage, Response);
    }
}

NB - You will need to add a certificate (public and private keys) to the local machine personal store and replace <certificate subject> with the correct name. You may also have to assign permissions to the private key of the cert for the identity that your website is running under.

4. Add the following helper method to Login.aspx.cs: 

 private static X509Certificate2 GetCertificate(StoreName name, StoreLocation location, string subjectName)
{
    X509Store store = new X509Store(name, location);
    X509Certificate2Collection certificates = null;
    store.Open(OpenFlags.ReadOnly);
 
    try
    {
        X509Certificate2 result = null;
        certificates = store.Certificates;
 
        for (int i = 0; i < certificates.Count; i++)
        {
            X509Certificate2 cert = certificates[i];
            if (cert.SubjectName.Name.ToLower() == subjectName.ToLower())
            {
                if (result != null)
                {
                    throw new ApplicationException(string.Format("There are multiple certificates for subject Name {0}", subjectName));
                }
 
                result = new X509Certificate2(cert);
            }
        }
 
        if (result == null)
        {
            throw new ApplicationException(string.Format("No certificate was found for subject Name {0}", subjectName));
        }
 
        return result;
    }
    finally
    {
        if (certificates != null)
        {
            for (int i = 0; i < certificates.Count; i++)
            {
                X509Certificate2 cert = certificates[i];
                cert.Reset();
            }
        }
 
        store.Close();
    }
}

OK, so what have we achieved? The only bits of real relevance are steps 2 and 3. In step 2 a custom Security Token Service (STS) has been created simply by deriving from the SecurityTokenService base class and overriding a couple of methods. The GetScope method allows us to define the certificate that will be used to sign the token (so that the RP can verify the token has not been tampered with and also that it is from the targeted IdP). It also allows us to define a certificate for encrypting the token (using the RP public key with the private key held at the RP for decryption) but I have set this property to false for now so that we can see the clear text token.

 

The GetOutputClaimsIdentity method is used to populate the outgoing token with information about the user. Each of these snippets of information is known as a claim. And finally we get to the point of this whole thing. WIF is all about claims, claims and more claims … claims about the user that will be used by the RP.

 

Finally, in step 3 the Page_Load event handler uses the custom STS to craft up a token and instructs the browser to redirect the browser back to the RP.

 

A common mistake

We now get to reap the fruits of our labour……. don’t we?

 

After browsing to https://WIFRelyingParty/Content.aspx, we get redirected to the IdP, authentication occurs (well kind of, more on that in later posts) and the resulting token is returned to the RP. The token looks like the following:

 

 image_2_02DA8180

“A SAMLAssertion requires at least one statement” ……………. What’s this!?

Ok I have been a bit naughty but I wanted to demonstrate an error I have hit time and time again when trying to shortcut prototypes. The clue as to why this occurs is in the SAML1.1 assertion schema, specifically the following:

image_4_02DA8180

The SAML assertion created by the IdP MUST contain at least one Attribute (i.e – a claim) and rather than WIF saying as much, it reports that the entire AttributeStatement is missing. Trust me, if you use WIF or ADFS v2 you WILL eventually get careless and hit this one.

The remedy is easy, just modify the GetOutputClaimsIdentity method to return something substantial like the following:

 protected override IClaimsIdentity GetOutputClaimsIdentity(IClaimsPrincipal principal, RequestSecurityToken request, Scope scope)
{
    ClaimsIdentity id = new ClaimsIdentity();
    id.Claims.Add(new Claim("IdP/Claim1", "Hello from the Idp"));
    return id;
}

 

Extend the Relying Party application to handle responses

1. Overwrite the contents of the Page_Load event handler for Content.aspx.cs with the following code:

 if (HttpContext.Current.Request.Form[WSFederationConstants.Parameters.Result] != null)
{
    // This is a response from the STS
    SignInResponseMessage mess = WSFederationMessage.CreateFromNameValueCollection(
        WSFederationMessage.GetBaseUrl(HttpContext.Current.Request.Url),
        HttpContext.Current.Request.Form) as SignInResponseMessage;
    string tokenXml = mess.Result;
}
else
{
    // This is a new request
    SignInRequestMessage mess = new SignInRequestMessage(
        new Uri("https://localhost/WIFIdentityProvider/Login.aspx"),
        "https://WIFRelyingParty", "https://localhost/WIFRelyingParty/Content.aspx");
    HttpContext.Current.Response.Redirect(mess.WriteQueryString());
 
    // You could also do this
    // HttpContext.Current.Response.Write(mess.WriteFormPost());
    // HttpContext.Current.Response.End();
}

Note that the code to create the initial request hasn’t changed, if has just been tidied away into the else statement.

The if statement checks that the Result property is populated with a response (Result => wresult in the GET/POST response, another of those ‘w’ parameters I mentioned earlier) and if so, the response is deserialized with a single line of code. We now have a token with which to determine authorisation to view the RP content.

Summary

This post demonstrates that it is possible, using WIF, to implement simple Relying Party (RP) and Identity Provider (IdP) applications using very few lines of code. It goes without saying that this is not production-ready code but it serves to highlight the important points.

Thus far this has all been achieved programmatically. However, in the true WCF spirit (upon which WIF is built) it is possible to achieve the same results using configuration and reduce even further the lines of custom code. In the next post I will be demonstrating this and also how we can tighten up the security of the RP/IdP applications.

 

 

Written by Bradley Cotier