Setting up security in WCF can be a daunting task, there are so many switches that you can turn that it can be overwhelming.
(WCF security configuration is like all these helicopter cockpit switches, with only certain combinations of them not making the helicopter explode)
It doesn’t help that there isn’t much excitement around WCF anymore, it’s hard to find documentation, and most of what’s available is long past ripe, just try to download the samples from this MSDN article: Authentication and Authorization in WCF Services.
One thing I’ve noticed that most articles/books about WCF, particularly the parts that describe security, is that they dedicate an immense portion to motivate WCF’s approach, without providing many examples (I can think of one particular book that has over 100 pages that you need to read just to figure out how to secure a WCF service).
WCF Security Concepts, in a nutshell
In WCF, securing a service means that the communication between client and service cannot be tampered with (there is message integrity), is private and both client and service authenticate themselves to each other.
Because in WCF you can specify which protocol you want to use for communication, you can not only configure security to be achieved by encrypting the message itself, but also by using the protocol itself to achieve security (e.g. https instead of http).
This is referred to as the security mode, and is a binding configuration, i.e. you configure a binding to use either Transport Security or Message Security (as always with WCF there are more esoteric configurations available, and we’ll address one of them, which I find is the most useful, but for now, think only Message or Transport as the available security modes).
Transport Security Mode
When you select the Transport Security mode you are basically stating that you want to use the transport protocol to ensure that the service is secure. If you use basicHttpBinding that means you want to use https to exchange messages.
If you configure a service with Transport security and try to serve it using an http address you’ll get an exception.
Here’s how you configure a service with basicHttpBinding so that it uses Transport security.
<system.serviceModel>
<services>
<service name="WCFSecuritySurvivalGuide.Services.UserInformation">
<endpoint address="" binding="basicHttpBinding" contract="WCFSecuritySurvivalGuide.Services.IUserInformation" bindingConfiguration="transportSecurityConfiguration"/>
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="transportSecurityConfiguration">
<security mode="Transport"></security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
You can find a solution with this example and all the next here:
git clone https://github.com/ruidfigueiredo/wcfsecuritysurvivalguide
The important part here is the binding configuration (transportSecurityConfiguration
). It’s there that you can select the security mode.
In case you are wondering this is the same as using basicHttpsBinding. All bindings have a default configuration, and basicHttpsBinding is just basicHttpBinding with security mode set to Transport by default (basicHttpBinding‘s default is None).
This security configuration ensures message privacy, integrity and authenticates the service. This is all because of the certificate used to enable https (setting up https is a whole story in itself, especially if you are self-hosting, but if you are trying this in visual studio and using IIS Express, just select the web project where you have the service host file (.svc) and press F4, and set SSL Enabled to true).
This does not authenticate the client towards the service though, if you want to do this you have to specify the clientCredentialsType
, and that is done this way:
<bindings>
<basicHttpBinding>
<binding name="transportSecurityWithWindowsConfiguration">
<security mode="Transport">
<transport clientCredentialType="Windows"/>
</security>
</binding>
</basicHttpBinding>
...
Notice that I’ve selected Windows as the clientCredentialType
. For this to work you need to enable Windows Authentication in your host (if you are using IIS Express, just select the web project that is hosting the .svc file, press F4 and set Windows Authentication to Enabled).
If you do this, you’ll have access, in the service, to information about the client’s windows user, more on this later.
There are other clientCredentialTypes
, if you are thinking about using Basic
because, instead of using a Windows user, you want to pass in an arbitrary username/password combination and have it validated in the service, there’s a better way than using the Transport security mode (and because Basic requires you to enable basic authentication in IIS, and as far as I know this does not work in IIS Express).
Message Security Mode
If you use this mode, it means that you’ll be sending an encrypted message over a non-secure transport (e.g. http). To encrypt the message you’ll have to use your own a certificate, and you also have to configure how the client validates the certificate (this ensures the client is speaking to the right service).
The most popular binding that enables you to use message security is wsHttpBinding
, and is frequently used when you have a service that is available in the internet
, and not only an in-house (intranet
).
There are many quirks about using this configuration, I’ll try to highlight them all. First, the service configuration on the server:
<system.serviceModel>
<service name="WCFSecuritySurvivalGuide.Services.UserInformationForWsBinding" behaviorConfiguration="serviceWithCertificate">
<endpoint address="" binding="wsHttpBinding" contract="WCFSecuritySurvivalGuide.Services.IUserInformation" bindingConfiguration="wsHttpBindingWithWindows"/>
</service>
...
<bindings>
<wsHttpBinding>
<binding name="wsHttpBindingWithWindows">
<security mode="Message">
<message clientCredentialType="Windows"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="serviceWithCertificate">
<serviceMetadata httpGetEnabled="true"/>
<serviceCredentials>
<serviceCertificate
findValue="localhost"
storeName="My"
storeLocation="LocalMachine"
x509FindType="FindByIssuerName"/>
</serviceCredentials>
</behavior>
...
NOTE: I’ve set the clientCredentialType
to Windows since setting it to none causes an exception when you try to call the service (the exception message is not particularly informative, I assume that you can’t use this binding without the client providing some credentials).
The important part here is the certificate, you set it through a behaviour, serviceCredentials
. I’ve created that certificate myself using makecert.exe
, so it’s not terribly useful, but if you were to host this service in the internet, you’d have to buy a proper certificate.
So what’s going on in the serviceCertificate
bit of serviceCredentials
. I’m basically saying, find a certificate that has an Issuer Name equal to localhost
in my Personal certificate store in the machine that is running the service (My = Personal store).
Now the client. You have to specify how the client validates that certificate. There are a couple of options (values of the CertificateValidationMode
enumeration):
- None
- PeerTrust – Public key must be found in a certificate in the client’s trusted people store
- ChainTrus – Certificate was issued by a root authority (client’s Trusted Root Certification Authorities)
- PeerOrChainTrust
- Custom
Here’s the client configuration:
<system.serviceModel>
<client>
<endpoint address="http://localhost:37915/UserInfoForInternetService.svc" binding="wsHttpBinding" contract="WCFSecuritySurvivalGuide.Services.IUserInformation" name="wsHttpConfiguration" behaviorConfiguration="validateCertificateBehavior"/>
</client>
<bindings>
<wsHttpBinding>
<binding>
<security mode="Message">
<message clientCredentialType="Windows"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="validateCertificateBehavior">
<clientCredentials>
<serviceCertificate>
<authentication certificateValidationMode="PeerTrust"/>
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
Now the quirk that can really bite you. The certificate name must match the domain or the machine name where the service is hosted. In this case it’s all well and good because we are running the service and the client in the same machine and we are using localhost as the certificate name (I also named the certificate authority of my self-signed certificate as localhost, but that has no influence). If your certificate name does not match, you have to set the incredibly informative property named identity in the client configuration:
<client>
<endpoint ...>
<identity>
<dns value="You certificate name goes here"/>
</endpoint>
</client>
Transport Security With Credentials in the Message
What if you want to use https, not use Windows authentication and instead use arbitrary credentials (without relying on Basic Http Authentication). There’s a security mode for that, and it’s TransportWithMessageCredential
.
You can use it with for example, basicHttpBinding or wsHttpBinding, and what it means is: use of SSL transport security in combination with the client credentials being carried in the message.
Why is this useful? Because if you want to use arbitrary credentials you’d have to use message security and deal with providing a certificate so that the messages are encrypted, and also provide the client configuration to deal with all of that. With TransportWithMessageCredential
you can just use SSL configured in IIS (the only requirement for WCF is that the service has a https address) and provide the client credentials when you invoke the service in the client (I’ll show you how to do that shortly).
You might be wondering, if we use arbitrary credentials those need to be validated somehow. You create your own username and password validator and provide configuration to enable it. Here’s the configuration first, and then we’ll look at the custom username and password validator:
<system.serviceModel>
<services>
<service name="WCFSecuritySurvivalGuide.Services.UserInformationForTransportWithMessageCredential"
behaviorConfiguration="customAuthentication">
<endpoint address=""
binding="basicHttpBinding"
contract="WCFSecuritySurvivalGuide.Services.IUserInformation"
bindingConfiguration="transportSecurityWithMessageCredentialConfiguration"/>
</services>
<bindings>
<basicHttpBinding>
<binding name="transportSecurityWithMessageCredentialConfiguration">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
</security>
</binding>
</basicHttpBinding>
</bindigs>
<behaviors>
<serviceBehaviors>
<behavior name="customAuthentication">
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WCFSecuritySurvivalGuide.Services.CustomUserNameAndPasswordValidator, WCFSecuritySurvivalGuide.Services"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
Things to pay attention to: security mode="TransportWithMessageCredential"
, the clientCredentialType
is set on the message and is set to UserName
. The custom username and password validation is set as a service behaviour, namely through the serviceCredentialsBehavior
by setting usernameAuthentication
‘s userNamePasswordValidationMode
to Custom
and customUserNamePasswordValidatorType
to an actual type that you have to define, in this case that was CustomUserNameAndPasswordValidator
in the WCFSecuritySurvivalGuide.Services
namespace and WCFSecuritySurvivalGuide.Services
assembly.
Custom Username and Password Validator
To create your own custom username and password validator you first have to reference the .Net Assembly System.IdentityModel
(the custom validator isn’t in System.ServiceModel
like the majority of WCF that we’ve discussed here).
Then you have to create a class that implements the abstract class UserNamePasswordValidator
:
using System.IdentityModel.Selectors;
using System.ServiceModel;
namespace WCFSecuritySurvivalGuide.Services
{
public class CustomUserNameAndPasswordValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (userName == "johndoe" && password == "P@ssw0rd")
return;
throw new FaultException("Invalid Username and/or Password");
}
}
}
Then you use it in the serviceCredentials
in the service’s behaviours like we did in the previous example.
How to invoke a WCF service and use arbitrary credentials
Depending on how you decide to create your WCF clients, the way you do this varies slightly.
If you use Add Service Reference to create your clients, then on your proxy you do this:
proxy.ClientCredentials.UserName.UserName = "johndoe";
proxy.ClientCredentials.UserName.Password = "P@ssw0rd";
If you use ChannelFactory
:
var channelFactory = new ChannelFactory<IServiceInterface>("configurationName");
channelFactory.Credentials.UserName.UserName = "johndoe";
channelFactory.Credentials.UserName.Password = "P@ssw0rd";
And if you prefer to inherit from ClientBase<T>
, it exposes a ClientCredentials property, and you use it exactly as with the proxy generated with AddServiceCredentials (in fact, the generated proxy uses ClientBase<T>).
If this is the first time you’ve heard about ChannelFactory
or ClientBase
stop everything that you are doing and go read WCF the Manual Way… the Right Way.
Note that after you specify a set of credentials, if you specify another they will be silently ignored.
Don’t you feel an annoying tingling sensation when you have to write UserName.UserName="someusername"
? It’s not the best of names, however, it’s there because you can specify several types of credentials. There’s for example Credentials.Windows
(.Username
, .Password
and .Domain
) or Credentials.Certificate
that you would use to authenticate the client with a certificate.
How to check which user was authenticated in the service
There are two ways of getting the authenticated user’s username in WCF, one is through the current thread, i.e., inside your WCF service method:
var username = Thread.CurrentPrincipal.Identity.Name
Unfortunately, on some occasions this value will not be set even though the user is authenticated. A method that works every time is:
var username = ServiceSecurityContext.Current.PrimaryIdentity.Name;
You will have to use this method if you use the TransportWithMessageCredential
security mode.
You can also use the PrincipalPermission
attribute to decorate your methods so that you limit access to specific users, for example:
[PrincipalPermission(SecurityAction.Demand, Name = @"DomainName\\UserName")]
public UserInfo GetUserInformation()
{
return _userInformation.GetUserInformation();
}
I’ve used a windows user as an example (instead of an arbitrary username). If you want to use PrincipalPermission
with custom authentication you should check out Part 2 of this guide.
Summary
Configuring security in WCF involves many steps, and when it fails sometimes it is hard to pinpoint the source of the error, also the error messages are often cryptic which doesn’t help.
It is very important to at least have some notion of how security works in WCF, namely that you configure the security modes through binding configurations. The way that the service authenticates the user and authenticates itself to the client is done through service behaviours, namely serviceCredentials and clientCredentials
. And also that you can specify how the client’s credentials are validated (you can also use authorization mechanisms in WCF, much like you can with ASP.NET MVC with the Authorize
attribute, but that didn’t make it to this post).
This blog post describes these concepts, provides configuration examples and comes with a project hosted in github that shows them in action.