Securing Workflow Services with Windows Identity Foundation, Part 2
WIF, ADFS 2.0, and WF play nicely together in securing your workflow services
January 28, 2011
This article is part two of a two-part series showing how to leverage Windows Identity Foundation (WIF) to secure workflow services. (See "Securing Workflow Services with Windows Identity Foundation, Part 1".) In this article, we add more detail to our treatment of building services secured by WIF. We modify the scenario presented in part one to replace the development-time only Security Token Service (STS) with the production-ready Active Directory Federation Services (ADFS) 2.0. In addition to explaining how to switch STSs, we also explore the challenges that surface when you are dealing with identity issues that result from services calling other services. To this end, we present the Claims to Windows Token Service (C2WTS). C2WTS can help you convert an incoming claim that contains a user principal name (UPN) value into a Windows Identity that you can impersonate and use to make calls to resources, such as a SQL Server, that are secured with Windows Integrated Security.
The Scenario
In this article, we will continue with the WidgetsNow! scenario whereby employees within the Contoso network are able to place orders for widgets using the WidgetsNow! client application. This application launches the WidgetsNow! Workflow Service, which is designed to simulate an order fulfillment workflow. For this article, we modify the WidgetsNow! Workflow Service to call out to a new inventory workflow service. This latter service is responsible for reserving items from the inventory database. Figure 1 shows the enhanced architecture.
Figure 1: The updated WidgetsNow! scenario
While we add a new service to the downstream mix, the authentication and authorization rules we used previously are maintained as we migrate to using ADFS 2.0 as the STS. To recap, the rules are:
Only members of the SeniorManagers Windows group can expedite orders.
Only users in the Contoso domain can place orders.
Starting Out
The Download the Code link at the top of this article contains a link where you can download the project files both in the current and envisioned states. We will begin with the solution in the current state, the final state of part one, which leverages the customized Visual Studio STS template. And we will illustrate the key steps in converting it to use ADFS 2.0. See part one for the details of the workflow implementation.
Implementation
Figure 2 illustrates our approach to implementing the requirements previously described by leveraging WIF, ADFS 2.0, and C2WTS.
Figure 2: Authentication flow in the scenario
Let's step through the communications shown in the diagram, and then we'll drill into implementation.
The Windows Presentation Foundation (WPF) client application has a reference to the WidgetsNow! Workflow Service, and from that the client knows that it will need to present a security token containing a name claim and a CanExpediteOrders claim in order to be able to call the Service. It must acquire the security token from the ADFS 2.0 STS by presenting its Windows username and groups claims.
The ADFS 2.0 STS accepts the security token containing the Windows-based claims, which it validates and converts to a Name claim. In addition, if the user is a member of the SeniorManagers Security Group in Active Directory, it also issues a claim containing a CanExpediteOrders claim with a value of true. These two claims are sent back to the client in a single token.
Now that the client has the required token, the client can call the service by invoking the PlaceOrder operation and presenting the token.
A custom ClaimsAuthorizationManager examines the claims presented in the token. If PlaceOrder is the operation being invoked, the username presented is checked for belonging to the Contoso domain. If it does, then the request is allowed to flow through to the workflow service. If it does not, then the client gets an Access denied error.
The request makes it to the WidgetsNow! Workflow Service for processing and possibly for additional authorization. In most cases, this results in a successful call with the desired value being returned. In the case of a call to PlaceOrder, if the user elected to expedite the order, the CanExpediteOrder claim is examined for a true value. If there is no true value, a fault exception informs the client that he or she is not allowed to place an expedited order.
As a prerequisite to calling the Inventory service, the WidgetsNow! service calls ADFS and presents the Windows credentials of the account running the service. Then, ADFS returns a new token containing the Kerberos UPN for that account.
The WidgetsNow! service calls the ReserveOrder operation on the Inventory service, including the token just acquired in the call.
A SecurityTokenHandler intercepts the UPN token and passes it to the C2WTS, which converts the incoming Claims Identity to a Windows Identity.
During its execution, the Inventory service impersonates this Windows Identity when making a call to a local SQL Server database secured using Windows Integrated credentials.
Now let's look at the three major steps needed to implement this approach: Add the inventory service, configure STS use, and enable the C2WTS.
Note: This article assumes you already have ADFS 2.0 installed; see the Additional References section for guidance on this and for additional backgrounders if you are not familiar with ADFS.
Adding the Inventory Service
To the solution, we add a new WCF Workflow Service project with the XAMLX renamed to InventoryService.xamlx and content as shown in Figure 3.
Figure 3: The Inventory service
This service takes as input to the ReserveInventory operation a Product ID and a Quantity. It then passes them to the UpdateInventory custom activity, which modifies the Inventory database. If the update is successful, which is determined when the query modifies one record in the database, a result of true is returned (false if otherwise). From the WidgetsNowService, we add a service reference to this service so that we can add a call to it by simply adding the ReserveInventory activity to the OrderService.xamlx and configuring its properties to pass the parameter values. See the sample files if you are interested in the details.
Next, we need to turn to configuring both workflow services to use ADFS, and we need to configure ADFS to issue the claims expected by each.
Configuring STS Reference for the WidgetsNow! Workflow Service
The WidgetsNow! service already contains a reference to an STS, but we need it to use ADFS 2.0 for authentication instead of the custom STS project. Switching this out is easy and amounts to re-running the Add STS Reference (FedUtil) and substituting the address of the ADFS metadata:
Right-click the WidgetsNowService project, and then select Add STS Reference. This will launch the Federation Utility, a wizard which helps you configure your service to use an STS and ADFS 2.0 in this case.
On the Welcome screen, we enter the address of our workflow service for the Application URI field. On our machine, this was fsweb.contoso.com/WidgetsNowService/OrderService.xamlx.
The Security Token Service screen allows us to pick between two options for adding an STS. We can create a new STS project that is added to the current solution, or we can point to an existing STS. For production use, you will typically point to an off-the-shelf STS, such as ADFS 2.0. So select the Use an existing STS radio, and in the STS WS-Federation metadata document location textbox, enter the address of your ADFS instance. (In this scenario, it would be [https://fsweb.contoso.com/].)
On the next two screens, we opt to disable Certificate Chain validation because we are using a self-generated certificate, but select Enable encryption, and select an existing certificate for use in securing communications between ADFS and the workflow service.
The rest of the screens we leave at their defaults. When we click Finish, the web.config for the service is updated with the appropriate configurations to trust ADFS 2.0 claims.
Telling ADFS About the WidgetsNow! Service
Now we are ready to complete the other side of the trust by configuring ADFS 2.0 to know about the WidgetsNow! Workflow Service and issue the tokens it expects. To do this, we launch the ADFS 2.0 Management snap-in from the Start menu. We begin by clicking on the Actions link for Add Relying Party Trust. This launches a wizard that enables us to enter basic information about the WidgetsNow! Workflow Service and ends with allowing us to configure how claims are issued to it. The key steps are as follows:
On the Select Data Source screen, we choose Import data about the relying party from a file, and then browse to the WidgetsNow! Workflow Service project folder and select the FederationMetadata.xml document that has been created for us automatically by Visual Studio.
Specify a friendly name for the Display name (e.g., WidgetsNowService).
For the Issuance Authorization Rules, we select the permissive option Permit all users to access this relying party because we don't want ADFS 2.0 to perform authorization. (Our existing Claims Authorization Manager and workflow implementations already take care of that.)
Finally, we ensure that the Open the Edit Claim Rules dialog option is selected on the Finish screen. This displays a dialog that enables us to configure how claims are processed and issued.
Configuring Claims for the WidgetsNow! Workflow Service
Figure 4 shows the completed list of Issuance Transform Rules added with the Edit Claims dialog.
Figure 4: ADFS claims being issued for the WidgetsNowService
In this dialog we have added two rules, one which passes through the Name claim from the WPF client (in the form DOMAINUSER) and a second which generates the CanExpediteOrders claim if the user happens to belong to the SeniorManager role. Figure 5 shows the Name claim, whose configuration is fairly straightforward.
Figure 5: Configuration which passes through the client's Name claim to the WidgetsNowService
The custom CanExpediteOrders claim, however, is a little more complex, but its configuration is also simple (see Figure 6).
Figure 6: Configuration that generates the CanExpediteOrders claim
All we have to do is pick the group to which the caller (or more specifically the claims about the caller) belongs by clicking Browse. Then, we specify our own claim type and set the value to true. If a user does not belong to SeniorManagers, CanExpediteOrders simply isn't issued.
For the Issuance Authorization Rules tab, we automatically get a single rule that allows all users. This was added based on our previous selection. We keep this because we want to perform authorization downstream from ADFS. With that, the WidgetsNow! Workflow Service will receive the claims it expects.
Configuring the Trust Between Inventory Service and ADFS
The same five steps are followed against the Inventory Service project, with the exception that the address used for the Application URI is that of InventoryService.xamlx. Similarly, we add a relying party trust in the same way (using the ADFS 2.0 Management snap-in) as for the other workflow service, again specifying the address of the Inventory Service instead.
Configuring Claims for the Inventory Service
When the Edit claims rule dialog is presented after adding the relying party trust, we have a different claims issuance rule to configure for the Inventory Service. Here, we simply want to pass a UPN representing the caller that C2WTS uses to create a Windows Identity. To accomplish this, we add one new Custom Rule to the Issuance Transform Rules tab. A custom rule uses an expression syntax that consists of two parts: a condition to match against an incoming claim and an issuance statement to provide the desired claim (when the condition is matched). Figure 7 shows the custom rule in its entirety. Observe the condition checks for a Windows Account Name claim.
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"] => issue(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn", Issuer = c.Issuer, OriginalIssuer = c.OriginalIssuer, Value = regexreplace(c.Value, "(?[^\]+)\(?.+)", "${user}@${domain}"), ValueType = c.ValueType);
The Issue statement creates a new UPN claim by using a regular expression to pick out the user name and domain from the Windows Account Name that was received. The result is a UPN claim with a value of the form user@domain.
With this UPN claim being issued, we are now ready to configure C2WTS.
Enabling C2WTS
C2WTS is a Windows Service that allows permitted non-admin accounts to acquire and impersonate other Windows Identities converted from a UPN claim for that identity. The first step to using it is to configure it via its app.config with the names of the accounts that should be allowed to take this action. In our scenario, our workflow services run under Network Service, so we need to grant that account rights to C2WTS. To do this, we open C:Program FilesWindows Identity Foundationv3.5c2wtshost.exe.config. Within the allowedCallers element, we need to add Network Service. Figure 8 shows the complete configuration that supports this.
Next, as this is a Windows service, you will want to ensure that it is set to start automatically, and you’ll also want to start it. (By default, it will not be set to start automatically.)
Configuring the Inventory Service to Use C2WTS
In order for a service to use C2WTS to convert an incoming UPN-based claims identity into a Windows Identity, you need to add a securityTokenHandler to your service's web.config. This securityTokenHandler should have a token requirement indicating that the token should be mapped to a Windows Identity and that this should be performed using C2WTS. Figure 9 shows the securityTokenHandler and samlSecurityTokenRequirement elements added to the web.config of the Inventory service.
… …
Impersonating the Windows Identity Provided by C2WTS in WF
Now we can return to the UpdateInventory activity we glossed over during the introduction to the InventoryService.xamlx. Figure 10 shows the implementation. Notice that the code acquires the Windows Identity from the thread's CurrentPrincipal and then makes the call out to the database using the impersonated identity.
public sealed class UpdateInventory : CodeActivity{ public InArgument ProductId { get; set; } public InArgument Quantity { get; set; } public OutArgument RowsAffected { get; set; }protected override void Execute(CodeActivityContext context) { var currentIdentity = Thread.CurrentPrincipal.Identity as WindowsIdentity; if (currentIdentity == null) return; using (currentIdentity.Impersonate()) { using (SqlConnection con = new SqlConnection( ConfigurationManager.ConnectionStrings["Inventory"].ConnectionString)) { con.Open(); SqlCommand cmd = new SqlCommand(); cmd.Connection = con; cmd.CommandText = "UPDATE ProductInventory " + "SET QuantityOnHand = QuantityOnHand - @QuantityOrdered " + "WHERE ProductID = @ProductId AND " + "(QuantityOnHand - @QuantityOrdered >= 0)"; cmd.Parameters.AddWithValue("@ProductId", ProductId.Get(context)); cmd.Parameters.AddWithValue("@QuantityOrdered", Quantity.Get(context)); int rowsAff = cmd.ExecuteNonQuery(); RowsAffected.Set(context, rowsAff); } } }}
Updating the Client Applications
Just as in part one, configuring the clients to use an STS is simply a matter of updating the service references. In this case, however, we have two clients: the WPF Client and the WidgetsNow! Workflow Service (as it is a client of the Inventory Service). When we update the service references, the process will add all the endpoints that ADFS exposes and pick the first one for use. Typically, this is not what we want, so we need to adjust both configs to use the correct endpoint. In our scenario, this is the Kerberos endpoint. This amounts to two changes in the config file to the WS2007 Federation custom binding ending in the name of our target service contract (e.g., WS2007FederationHttpBinding_IOrdering for the WPF Client's config or WS2007FederationHttpBinding_IInventory for the WidgetsNowService config). First, we need to ensure that the claims, such as Name, Upn, and Role, that are required by the target service are requested. ADFS 2.0, unlike our development STS, will not issue claims that are not specifically requested. Second, we need to configure the issuer element to use the Kerberos endpoint. Both of these changes look as shown in Figure 11.
… … …
Checking Our Results
The end result of our effort is that the WidgetsNow! Service and Inventory Service completely externalize authentication by relying on ADFS 2.0 for the standard set of claims that they can use to make authorization decisions, and these simple UPN claims enable C2WTS to create Windows Identities that can be impersonated in the middle tier.
Additional Resources:
Setting up an ADFS 2.0 with WIF lab environment
Read more about:
MicrosoftAbout the Author
You May Also Like