Taking this post as a starting point, we now have a working HTTP module that implements HTTP Basic Authentication against a membership provider. This is fine for ASP.NET applications and content, but to integrate a WCF service, there is some extra work necessary.
Even though WCF is technically also an HTTP module/handler participating in the ASP.NET pipeline, the WCF architects decided to keep a WCF service separate from the hosting environment. This means e.g. that you don’t get access to the current HttpContext from within WCF by default.
Let’s start with the service configuration itself. The service uses the basicHttpBinding and is configured for Transport security. Note that the clientCedentialType is set to None – this means that WCF itself is not doing any authentication. This is done by the HTTP module.
<system.serviceModel> <services> <service name="WcfService" behaviorConfiguration="Behavior"> <endpoint address="" binding="basicHttpBinding" bindingConfiguration="CustomBasicAuth" contract="IWcfService" /> </service> </services> <!-- turn off authentication in WCF, we get the client from the HttpContext --> <bindings> <basicHttpBinding> <binding name="CustomBasicAuth"> <security mode="Transport"> <transport clientCredentialType="None" /> </security> </binding> </basicHttpBinding> </bindings> <!-- enable metadata --> <behaviors> <serviceBehaviors> <behavior name="Behavior"> <serviceMetadata httpGetEnabled="true" /> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel>
The next step is to allow WCF to access the ASP.NET HttpContext to be able to access HttpContext.User which holds the identity of the client as authenticated by the Basic Authentication HttpModule.
This is done by adding a configuration setting to <system.serviceModel /> in web.config:
<!-- to enable IIS authentication for WCF, we have to switch to compatibility mode --> <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
Additionally you also need to decorate the service implementation to opt-in to ASP.NET compatibility mode:
[AspNetCompatibilityRequirements(RequirementsMode=AspNetCompatibilityRequirementsMode.Required)] public class WcfService : IWcfService
Now you could access HttpContext.Current.User in a service operation. This is not very natural for a WCF service developer since in WCF the client identity is typically exposed via ServiceSecurityContext.Current.PrimaryIdentity. To propagate the user information properly from ASP.NET to WCF you can use an IAuthorizationPolicy. This code takes the value from Context.User.Identity and puts it into a special dictionary which is later picked up by WCF to construct the ServiceSecurityContext.In addition you could also add claims for the user here.
// syncs ServiceSecurityContext.PrimaryIdentity in WCF with whatever is set // by the HTTP pipeline on Context.User.Identity (optional) public class HttpContextIdentityPolicy : IAuthorizationPolicy { public bool Evaluate(EvaluationContext evaluationContext, ref object state) { HttpContext context = HttpContext.Current; if (context != null) { // set the identity (for PrimaryIdentity) evaluationContext.Properties["Identities"] = new List<IIdentity>() { context.User.Identity }; // add a claim set containing the client name Claim name = Claim.CreateNameClaim(context.User.Identity.Name); ClaimSet set = new DefaultClaimSet(name); evaluationContext.AddClaimSet(this, set); } return true; } public System.IdentityModel.Claims.ClaimSet Issuer { get { return ClaimSet.System; } } public string Id { get { return "LeastPrivilege HttpContextIdentityPolicy"; } } }
You wire up this policy in the <serviceAuthorization /> behavior:
<serviceAuthorization> <authorizationPolicies> <!-- sync ServiceSecurityContext.PrimaryIdentity with Context.User.Identity --> <add policyType="LeastPrivilege.HttpContextIdentityPolicy, LeastPrivilege.HttpContextPolicies" /> </authorizationPolicies> </serviceAuthorization>
Another “slot” for security information in WCF is Thread.CurrentPrincipal. This is used to support classic role-based IsInRole calls and the [PrincipalPermission] attribute. T.CP gets populated according to another configuration attribute in the <serviceAuthorization /> behavior called principalPermissionMode.
By default WCF tries to create a WindowsPrincipal which wouldn’t work here. Another option would be to associate the user with an ASP.NET role provider by setting the principalPermissionMode to UseAspNetRoles. In this case T.CP is populated with a RoleProviderPrincipal which takes the user name from ServiceSecurityContext.PrimaryIdentity to find associated roles from a configured role provider datastore. Also read this post if you plan to use a role provider with WCF.
The most flexible option is to create your own custom IPrincipal and put it on Thread.CurrentPrincipal. This way you can optimize the IsInRole implementation to your specific back-end credential store. I wrote about this in detail here.
The sample app sets a custom IPrincipal by copying the IPrincipal provided by the ASP.NET pipeline to the WCF world. This may be useful if you want to share an existing principal implementation between ASP.NET and WCF:
// syncs Thread.CurrentPrincipal in WCF with whatever is set // by the HTTP pipeline on Context.User (optional) public class HttpContextPrincipalPolicy : IAuthorizationPolicy { public bool Evaluate(EvaluationContext evaluationContext, ref object state) { HttpContext context = HttpContext.Current; if (context != null) { evaluationContext.Properties["Principal"] = context.User; } return true; } public System.IdentityModel.Claims.ClaimSet Issuer { get { return ClaimSet.System; } } public string Id { get { return "LeastPrivilege HttpContextPrincipalPolicy"; } } }
So that’s it. You can now write an IIS hosted service that uses HTTP Basic Authentication against custom accounts while preserving the usual WCF security programming model. As you’ve seen this involves some plumbing and configuration and the interaction between all these configurations and extensibility points is sometimes quite confusing. Would be nicer if this functionality would be provided out of the box…
Attached are the WCF specific parts of the sample project. Just insert them into the download from the last post.