Securing Workflow Services Using Credentials
Control access to workflow services using Windows and username/password credentials
March 18, 2010
By default, a service built using Windows Workflow Foundation (WF) 4 defines no explicit restrictions on access to the service endpoint or to individual operations. In fact, if you look at the properties available on the Receive activity, you will find no property that lets you define access restrictions using Windows users and groups or ASP.NET membership users and roles. So how do you configure a workflow service to disallow anonymous access? How about to allow access only by administrators, or access to only some operations to administrators? This article will show an easy approach you can use to enable such security constraints.
Basic Security Principles
There are two general components to security. First, users must be authenticated in a trustworthy fashion that ensures the credentials being presented are valid—in other words, users are who they say they are. This part includes both the aspects of what form of identity is presented (a username and password, a Windows identity, or a certificate) and how the identity is verified (against a database, a Windows Domain, or a certificate store). Once you know for certain who you are dealing with, consider what that user can do in your system. In Windows Communication Foundation (WCF) there are two models you can use for performing this authorization. In one model, sometimes referred to as the CLR model, your service operations can make demands that the user belong to a specific role or group by using the PrincipalPermissions attribute before being allowed to execute a specific operation. In another mechanism (referred to as the Identity Model), a richer set of credentials beyond just roles and groups are examined to make authorization decisions.
In this article, we will focus on how to authorize workflow service operations using roles and groups with constructs similar to that in the CLR model. The approach taken also applies to the more granular authorization that can be done using the claims-based Identity Model, but we will leave specific coverage of that for another article.
Making Demands
In traditional WCF Services, your service implementation is an imperative class that you can decorate with the .NET PrincipalPermission attribute, such as the service defined in Figure 1. This service exposes two operations; one operation (LookupUser) is accessible to all non-anonymous users of the system. The second operation (DeleteUser) is only accessible by users who belong to the Administrators role. This is expressed via the PrincipalPermission attribute with a SecurityAction of Demand and a role having the appropriately named value.
In .NET 4 Workflow Services, activities in the workflow represent the service implementation. There are no methods to attribute and the out-of-the-box Receive activity does not have any properties that allow you to define such a demand. The solution is to create a custom ServiceAuthorizationManager that has PrincipalPermission–style semantics and configure it for use by your workflow service via a service behavior defined in the web.config. In the sections that follow we explore how to build this custom ServiceAuthorizationManager and use it for authorizing both Windows and ASP.NET users.
Workflow Service Implementation
Before diving into securing the service, let's look at how we can implement a service similar to that described previously in code using workflow services. Figure 2 shows the very simple implementation. Effectively, we have our two operations defined within branches of the Pick activity, such that one of the Receive activities for the LookupUser or DeleteUser operation will run and result in the execution of the SendReply in the corresponding action.
In this case, both the LookupUser Receive and DeleteUser Receive have their content configured to store the inputted username string in a workflow variable called username (see Figure 3), declared at the Sequential Service scope.
The return value from LookupUser is defined in the corresponding SendReply, and for simplicity just returns the length of the username submitted, as Figure 4 shows. Similarly, the SendReply that corresponds to the DeleteUser Receive always returns true. In a real implementation, the actions would naturally consist of activities that perform real processing.
With a service implementation in place, let’s turn our attention to securing the operations.
Custom Service Authorization
Regardless of whether our users are authenticating with Windows credentials or supplying a username and password for authentication by the ASP.NET membership provider, we perform authorization by plugging in a custom ServiceAuthorizationManager. A ServiceAuthorizationManager is a class derived from ServiceAuthorizationManager that overrides the protected CheckAccessCore method as shown below:
public class WfAuthorizationManager: ServiceAuthorizationManager { protected override bool CheckAccessCore(OperationContext operationContext) { //...return true if allowed, false otherwise } }
The CheckAccessCore method is called for every invocation of a service operation. In the case of workflows, this is ultimately before a Receive activity runs. The OperationContext parameter provides access to information about the request, headers, message properties, and authenticated identity for the operation being invoked. A typical implementation of CheckAccessCore will make use of the OperationContext provided and return true if access should be permitted and false otherwise.
With ServiceAuthorizationManager defined, using it is simply a matter of adding a serviceAuthorization service behavior to the service behaviors used by the workflow service. Figure 5 shows how this appears in web.config. If you do not specify a serviceAuthorizationManagerType, the default manager simply allows access to all operations.
With the general approach in place, let’s look at how we can apply custom service authorization managers with Windows and ASP.NET credentials.
Authorizing Windows Users and Groups To demonstrate how we use a custom service authorization manager with Windows credentials, let’s take the simplest case—we want to disallow anonymous access to all of the service’s operations. Figure 6 shows the complete implementation for the WFAuthorizationManager. Observe the first line within the overridden CheckAccessCore method examines the IsAnonymous property of ServiceSecurityContext of the passed operationContext. If IsAnonymous is true, we return false because we want to deny access. Otherwise, we return true because we have a non-anonymous user. With this implementation, the call will succeed as long as the client presents recognized Windows credentials.
In order for our service to receive Windows credentials with client requests and use our WfAuthorizationManager, we need to add some configuration to web.config. Figure 7 shows the complete web.config used in this case. Workflow services can most easily be configured by using the default endpoints defined with WCF 4 and overriding the values we need to suit. Starting from near the top, we define a protocol mapping for the http scheme to automatically use the wsHttpBinding. (If we didn’t do this, our workflow service would use a BasicHttpBinding by default.) Next we customize the default endpoint’s wsHttpBinding. We enable message security (security mode=“Message”) and specify that the message’s clientCredentialType is Windows. This will ensure that client requests include Windows user and role information. Finally, we configure the serviceBehaviors to enable the metadata endpoint, include exception details in the faults returned to the client, and register our WfAuthorizationManager via the serviceAuthorization service behavior. The serviceAuthorizationManagerType attribute references the type in the “Namespace.ClassName, Assembly” name format.
Authorizing ASP.NET Users and Roles If you are already using the ASP.NET Membership and Role Providers, you can use these identities for authorizing service calls using username and password. In this example, we show we can perform authorization that is semantically equivalent to using the PrincipalPermissionAttribute from Figure 1 to only allow administrators to call the DeleteUser operation.
To begin, we define a new custom ServiceAuthorizationManager, as Figure 8 shows. This time, within the CheckAccessCore method, we look at the PrimaryIdentity already authenticated (e.g., the username and password checked out). We use the username to perform a lookup against the configured roles provider and request all the roles to which the user belongs (via the GetRolesForUser method). Next, we check what operation is to be performed. We can determine this by examining the Action property of the IncomingMessageHeaders. In this case, the action will have a value of the form http://tempuri.org/IService/LookupUser or http://tempuri.org/IService/DeleteUser. In the case of invoking DeleteUser, we can check if the action ends in DeleteUser, and if it does then we need to ensure that the list of roles for the user includes the Administrators role. If the user belongs to that role, then we can return true to allow the operation to be invoked and the Receive activity to execute in the workflow. Otherwise, we return false and deny access to this operation to the non-administrator user. In the case of invoking LookupUser, we return true to always allow access.
The code for PrincipalPermissionAuthorizationManager relies on our service being enabled to use both an ASP.NET Membership Provider for authentication and ASP.NET Role Provider to assist with authorization. Figure 9 shows the complete web.config we use in this case. Refer to the Additional Resources section at the end of this article for details on configuring the ASP.NET SQL Membership and Role providers. Briefly, to enable both membership and roles you first need to define the connection string that they will use to access the SQL Database within the connectionStrings sections. Then you configure the providers with the membership and roleManager elements within the system.web element.
Since we want to use username and password credentials from the client in this case, we need to enable a certificate to provide the message security and hence have defined a serviceCertificate within the serviceCredentials behavior. We also need to change the clientCredentialType to UserName in the message element.
To perform the authentication using the username and password using the SQL Membership provider, we need to add a userNameAuthentication element configured with a userNamePasswordValidationMode of MembershipProvider. While this element does not reference the configured membership provider by name, it will use the default membership provider that is configured via the defaultProvider attribute of the membership element (which refers to SqlMembershipProvider).
To perform authorization, we again add a serviceAuthorization behavior, but set its principalPermissionMode to UseAspNetRoles, specify the roleProviderName to be the SqlRoleProvider we defined previously, and plug in our custom Service Authorization Manager by providing its type name to the serviceAuthorizationManagerType attribute.
Control Roles and Access for Full Security In this article, we have shown how you can use both Windows credentials and username/password credentials coupled with custom Service Authorization Managers to restrict access to a workflow service. An important takeaway is to notice that the authorization decisions are made before workflow activities run, as the activities themselves have no direct access to the caller’s identity.
Read more about:
MicrosoftAbout the Author
You May Also Like