If you are federating by using any Security Token Service (STS) from a .NET Framework application, you are probably using Windows Identity Foundation (WIF). WIF provides all the necessary features to do active (web service–based) and passive (browser-based) federation. The Visual Studio templates that are provided in the WIF SDK also give you a decent starting point for enabling federation in your applications and services. Of course, the devil is always in the details, and sometimes we have to dig a little deeper to augment requests that are sent to an STS or to process responses from an STS. This is particularly true if you are communicating with a custom STS or with a product that may require additional context information from the application to do its job.
In this article I will explore the concept of passing additional parameters between applications and an STS, and the relevant extensibility points that will make this possible. In the case of passive federation, I'll show you how to pass parameters by HTTP GET via a query string or by HTTP POST via form parameters if URI lengths are exceeded. For active federation, I'll show you how to handle custom properties by using a custom request or a response serializer, and how to configure these extensions at the client or at the STS.
Parameters and Passive Federation
Figure 1 shows the flow of communication between a web application and an STS during a request for a security token. Typically, the parameters shown in Figure 2 are passed during this request. In response, the STS returns the context originally supplied in the context (ctx) parameter and also returns the result: a SignIn Response (wresult parameter) that contains the issued token.
Because the context parameter must be passed back to the calling web application, the parameter is a useful way to make sure that the application can redirect to the original URl that the user browsed to. What's important to note is that this parameter is opaque to the STS and is not used to pass additional information that helps process the token issuance. Instead, the WS-Federation specification allows any number of parameters to be passed along with the request, and these parameters can be processed by the STS.
Figure 1 shows the application passing an ApplicationId parameter in a query string parameter. Of course, this process implies that the STS will be expecting additional parameters—something you may encounter if you build a custom STS or even if you use certain vendor products that augment the federation experience.
Adding Request Parameters
When you enable federation for your web applications by using WIF, you typically provide a configuration section indicating the required values for federation. This section includes the address of the STS (issuer), a logical identifier for the calling application (wtrealm), and a fixed reply address (reply), if desired, as follows:
<wsFederation passiveRedirectEnabled="true" issuer="http://localhost:54296/RPSTS/Default.aspx" realm="http://localhost:54293/RPWebSite/" reply=" http://localhost:54293/RPWebSite/Login.aspx"/>
Your job is now done if you rely on the WSFederationAuthenticationModule (federation module) to handle redirection to the STS. The federation module uses these parameters to build the query string to send to the STS, as shown in Figure 1. In fact, what is happening under the hood is that a SignInRequestMessage is created that includes the issuer address, the realm, and the reply address. The federation module also initializes the time stamp parameter (wct) by using the current request time. In addition, WIF produces a context that it can use on reply—including the original URl that the user browsed to for reply. Figure 3 shows an example of how such a SignInRequestMessage would be created programmatically.
In fact, adding parameters to this SignInRequestMessage is very easy. Just add any number of name and value pairs to the Parameters collection. For example, the following code adds an application identifier to the request:
If the STS expects this parameter, it may use the parameter to process the request. For example, the application identifier might be used as an indicator to tell the policy engine which claims should be issued to the authenticated caller.
You can manually create the SignInRequestMessage and send it by HTTP GET request using the following code:
The federation module creates the SignInRequestMessage and handles the redirection process for you. But to augment the request by using your own parameters, you should handle the RedirectingToIdentityProvider event that is exposed by the federation module. This is usually done in your Global.asax, as follows:
void WSFederationAuthenticationModule_RedirectingToIdentityProvider(object sender, RedirectingToIdentityProviderEventArgs e)
So, you can either manually create the request and the redirection or you can rely on the federation module to do that. Then, simply add your custom parameters.
Posting the Request
When you start adding custom parameters to a request, you have to consider the possibility that the request will become too big to pass in an HTTP GET request because of URI length limitations. When you exceed size limitations, the browser cannot process redirection to the STS. However, you're unlikely to see a useful error message, so you must rely upon your instincts to deduce this one. The federation module always redirects by using HTTP GET by default, but you can alter this path and explicitly write a form POST. Figure 4 shows how to produce an HTML form to write to the response in lieu of performing a redirect by using a query string. To do this, override the RedirectingToIdentityProvider event, add your custom parameters, and call WriteFormPost(). You should also cancel the default redirect capabilities before exiting the method.
Once again, this is a task easily accomplished. The trick is to know that your parameters may cause the redirect to exceed URI size limits and, therefore, that you should modify your code to switch to a form POST. You should also make sure that the STS can receive a request as a form POST instead of as a GET. This is, ideally, part of the STS documentation.
Receiving Requests via POST
Though not everyone is in the business of building a custom STS by using WIF, I would be remiss if I didn't at least touch on how the STS might receive the request as a POST instead of as a GET. When you create a custom STS by using the WIF templates, you'll see the code in Figure 5. This code expects the request as a GET.
To receive the request as a form POST, you have to modify this code to look for form parameters instead of query string parameters, as follows:
string action = Request.Form\\[WSFederationConstants.Parameters.Action\\];
In addition, you can use the CreateFromFormPost() method exposed by the WSFederationMessage type, instead of calling CreateFromUri():
SignInRequestMessage requestMessage = (SignInRequestMessage)WSFederationMessage.CreateFromFormPost(Request);
The rest is business as usual.
Properties and Active Federation
Active federation between rich clients and a web service has a slightly different communication flow. The client typically uses a proxy to call a service. When it is federating, the client must first request a security token from the STS and then present that token in the call to the service. Figure 6 shows this process as the following steps:
Step 1: The client sends a Request for Security Token (RST) to the STS.
Step 2: The STS authenticates the caller and issues a token if authentication is successful. The STS returns this token to the client in a Request for Security Token Response (RSTR) message.
Step 3: The client sends the issued token together with the message to the service.
In this illustration, I am omitting some details that I have discussed in previous articles so that we can focus on passing additional context specifically.
During active federation, additional context is passed together with properties either inside the RST or, if applicable, returned together with the RSTR.
Adding RST Properties
When a WCF service exposes a federated endpoint, the client proxy is also federated. Therefore, the client proxy can call the STS first to get a token prior to calling the WCF service. WIF provides an alternative to this method whereby you can explicitly request a token from the STS without relying on the WCF proxy. I will focus on this alternative method for adding properties to the RST and processing properties added to the RSTR.
Figure 7 shows a code listing that uses WIF components to request a token from an STS. Figure 8 shows the endpoint configuration to support the STS proxy. You first create an instance of the WSTrustChannelFactory by using the appropriate configuration—in this case, it loads the client endpoint configuration by the name "sts". Because this example authenticates to the STS by using username and password credentials, these values are set for the channel credentials. You then create a channel (proxy) that is based on the IWSTrustChannelContract interface. The Issue() method is called to request a security token, but you must pass the RST message in the form of a RequestSecurityToken instance. (Note that in the example in Figure 7, the values of the RequestSecurityToken instance are hard-coded for illustration purposes.) In this case, a token is being requested for the service at /TodoListService. The STS is located at /rpsts. Note that after producing the typical RequestSecurityToken instance, a property is added to the Properties collection by using a simple line of code:
The result that we are looking for is shown in Figure 9.
Ah, but not so fast! This code is not quite enough to send the property along to the STS. It might surprise you to see an exception such as the one shown in Figure 10. If you dig deeply enough, you'll see that you have to supply a custom serializer to show WIF how to write this property to the RST message. Yes, it would be nice if it defaulted to "string," but that is not the case. So more work must be done.
Creating a Custom Request or Response Serializer
WIF installs a default instance of WSTrust13RequestSerializer and an instance of WSTrust13ResponseSerializer when you initialize the trust proxy. In the request serializer type, there are two methods of primary interest:
- WriteXmlElement() is called when the runtime tries to write the RST. This method is called one time per property, and the default RST properties that are known to WIF are written by this method. This method cannot write additional properties. Therefore, it throws an exception if a property name is unrecognized.
- ReadXmlElement() is called when the runtime tries to read the RST at the STS. This method is called one time per property, and the default RST properties that are known to WIF are read into the properties that are held by the instance of this method. This method cannot read additional properties. Therefore, it throws an exception if a property name is unrecognized.
Likewise, the response serializer type includes the WriteXmlElement() and ReadXmlElement() methods, which handles properties that are included in the RSTR. When the STS sends the RSTR, the response serializer produces the XML for each property. The response serializer then reads those properties at the client.
To handle custom properties, these methods must be overridden accordingly. Typically, you add properties to the RST for the STS, so I'll focus on that scenario. Figure 11 shows a custom implementation of the WSTrust13RequestSerializer type. Both WriteXmlElement() and ReadXmlElement() are overridden to handle the ApplicationId property. For all other properties, the base class implementation is invoked.
You can tell WSTrustChannelFactory to use this custom request serializer by adding the following code to that shown in Figure 7:
stsClient.WSTrustRequestSerializer = new CustomWSTrust13RequestSerializer();
At the STS, you can install the custom serializer shown in Figure 12 by adding it to the WSTrustServiceHost initialization.
Now, your client can send additional properties, and the STS can understand them.
Adding RSTR Properties
You can write a custom STS by using WIF, and return additional properties together with the RSTR., canto do this, override the GetResponse() function that is exposed by the SecurityTokenService type, and add your properties there, as shown in Figure 13.
Of course, the WSTrustHost must also be initialized by using this serializer:
host.SecurityTokenServiceConfiguration.WSTrust13ResponseSerializer = new CustomWSTrust13ResponseSerializer();
Likewise, the STS proxy should be initialized by using a response serializer that can handle the custom properties in the response:
stsClient.WSTrustResponseSerializer = new CustomWSTrust13ResponseSerializer();
Custom STS Coding Power
Sometimes, you write code to call an STS that requires additional context. You've now seen how to add parameters to a passive federation request and how to pass additional properties in an active federation request. Likewise, the STS must be expecting additional properties. So you've also now seen how to augment your custom WIF STS so that it can receive additional request parameters or properties, and also how to send additional response properties.
Michele Leroux Bustamante ([email protected]) is chief architect for IDesign; chief security architect for BiTKOO, a Microsoft Regional Director for San Diego; and a Microsoft MVP for Connected Systems. You can visit her main blog at www.michelelerouxbustamante.com and follow her tweets at @michelebusta.