Fundamentals of WCF Security (Cont.) Role-Based Authorization Despite the importance and granularity of the claims-based authorization model in WCF, role-based security is still alive and well, and is useful for controlling access to service operations and business classes used downstream. This type of authorization is based on the security principal for the request. | " | “The identity model in WCF supports a rich, claims-based approach to authorization.”
| " |
The identity of the caller is attached to the executing request thread in the form of a security principal, accessible through the CurrentPrincipal property. System.Threading.Thread.CurrentPrincipal
The security principal is a wrapper for an identity-its type directly related to the token type received. For example, it could be a WindowsIdentity, X509Identity, GenericIdentity, or a custom type that implements System.Security.Principal.IIdentity. The identity is created during authentication as I discussed. The actual security principal is a type that implements System.Security.Principal.IPrincipal. This interface has two members: - A read-only Identity property that returns a reference to the IIdentity for the request.
- An IsInRole() method that returns a true or false result after checking to see if the identity is in a particular role.
The choice of role provider for a request influences the type of security principal attached to the thread. Options for role provider include: None. No role provider. Windows. Use Windows roles and add a WindowsPrincipal to the security context. UseAspNetProvider. Use the configured RoleProvider type, which defaults to the ASP.NET role provider. This adds a RoleProviderPrincipal to the security context. Custom. Relies on a custom authorization policy to add a security principal to the security context. The default role provider is “Windows” therefore a WindowsPrincipal is the default type. For Windows, UserName or Certificate credentials (when Certificates are mapped to Windows accounts) this will contain an authenticated WindowsPrincipal, otherwise the principal is unauthenticated and has no runtime use for role-based security. If you aren’t expecting Windows credentials, you can change the role provider by setting the principalPermissionMode value of the <serviceAuthorization> behavior (Listing 1). If you are using the ASP.NET credentials database, you can set it to “UseAspNetProvider”. This causes a RoleProviderPrincipal to be attached to the thread instead of a WindowsPrincipal. This IPrincipal type is new to WCF, and holds a reference to the ServiceSecurityContext. When the identity is requested from the principal, it actually returns a reference to the ServiceSecurityContext’s PrimaryIdentity property (discussed earlier). When IsInRole() is invoked, it uses the configured RoleProvider (in this case, the default ASP.NET role provider) to check if this identity is in the specified role. You can also customize this behavior with a custom ASP.NET RoleProvider or with a custom authorization policy. In any case, .NET role-based security relies on the IPrincipal object attached to the thread to perform authorization checks. So, even with WCF you can use the PrincipalPermission type to demand things like: - Is the user authenticated?
- Is the user in a particular role?
- Is a particular user calling?
At runtime, this can be done with an imperative permission demand within the WCF operation or any business component. Just create a PrincipalPermission object, initialize the values you want to enforce, and issue the Demand(). public string AdminsOnly() { // unprotected code
PrincipalPermission p = new PrincipalPermission(null, "Administrators"); p.Demand(); // protected code }
In this example, an exception will be thrown if the user is not in the Administrators group. You can also place a declarative PrincipalPermissionAttribute on any WCF operation or business component method to apply the demand before the operation or method is invoked: [PrincipalPermission(SecurityAction.Demand, Role = "Administrators")] public string AdminsOnly() { // protected code }
This approach is preferable since it decouples the security requirements from the actual code within the operation. In both scenarios, the IsAuthenticated property of the identity is verified, and the IsInRole() method is invoked to check membership using the IPrincipal object attached to the thread. Custom Authorization Policies Credential authentication, default claimset generation, and access to the security principal for role-based demands are all features that are configurable as I’ve discussed so far in this section. These are features that you get “for free”, with little extra coding effort. I also mentioned earlier that you can create custom authorization policies for your WCF services. These are types that implement the IAuthorizationPolicy interface from the System.IdentityModel.Policy namespace. Here are a few useful reasons to create a custom authorization policy: - When the service requires SAML tokens as the client credential type the claims are not authenticated against any existing role provider. A custom authorization policy can inspect these claims and initialize the security context accordingly.
- A service may replace traditional role-based security with claims-based security. An authorization policy can be used to normalize the set of claims received from different tokens into a common set of claims used for claims-based security.
- Services that use a custom role provider must provide an authorization policy to create an IPrincipal for the security context. Without it, authorization will fail.
The code sample for this article includes an advanced sample that illustrates an IAuthorizationPolicy implementation. Impersonation With all this talk about authentication and authorization, impersonation is worth discussing. When Windows credentials are used, the service can be configured to impersonate callers so that the request thread operates under the impersonated Windows token. This makes it possible for services to access protected Windows resources under the identity of the caller, instead of the process identity of the service-for that request. Using the OperationBehaviorAttribute you can apply impersonation rules per operation by setting the Impersonation property to one of the following: - ImpersonationOption.NotAllowed. The caller will not be impersonated.
- ImpersonationOption.Allowed. The caller will be impersonated if a Windows credential is provided.
- ImpersonationOption.Required. The caller will be impersonated and a Windows credential must be provided to support this.
This behavior is applied to service operations. [OperationBehavior(Impersonation = ImpersonationOption.Allowed)] public string DoSomething() { ... }
You can also set this for all operations by declaratively configuring the impersonateCallerForAllOperations attribute for the service authorization behavior. <behaviors> <serviceBehaviors> <behavior name="serviceBehavior"> <serviceAuthorization impersonateCallerForAllOperations="false"/> </behavior> </serviceBehaviors> </behaviors>
Clients can also control impersonation, to prevent services from using their identity to access resources. Windows credentials have an AllowedImpersonationLevel property that can be set to one of the following: - TokenImpersonationLevel.None
- TokenImpersonationLevel.Anonymous
- TokenImpersonationLevel.Identification
- TokenImpersonationLevel.Impersonate
- TokenImpersonationLevel.Delegate
None and Anonymous protect the caller’s identity but aren’t useful for authentication. Identify is the default and preferred setting since it allows services to identify the caller but disallows impersonation. Impersonate and Delegate will allow impersonation across one machine, or delegation with a Kerberos ticket, respectively. You set the value on the proxy as follows: localhost.HelloIndigoServiceClient proxy = new Client.localhost.HelloIndigoServiceClient(); … proxy.ClientCredentials.Windows. AllowedImpersonationLevel = TokenImpersonationLevel.Identification;
|