Securing a WCF service with username and password
In the .NET world, the use of WCF services is very common. There are lots of possible configurations available with these services and that's why we use them a lot! When it comes to securing these services I found that this is often overlooked. In this post I want to show you a really simple way of securing your services with a username and password. But if you prefer to use some other authentication mechanism you can also build upon this to implement your own.
Creating the service
If we want to implement security in a WCF service, we first need a service to work with. So let's create one!
Using the build in template in Visual Studio I created a WCF Service Application and edited the default service like this:
[ServiceContract] public interface IMyService { [OperationContract] int AddNumbers(int number1, int number2); }
public class MyService : IMyService { public int AddNumbers(int number1, int number2) { return number1 + number2; } }
It doesn't get any simpler than this, does it?
And in the Web.config I haven't changed the default configuration:
<system.serviceModel> <behaviors> <serviceBehaviors> <behavior> <!-- To avoid disclosing metadata information, set the values below to false before deployment --> <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/> <!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information --> <serviceDebug includeExceptionDetailInFaults="false"/> </behavior> </serviceBehaviors> </behaviors> <protocolMapping> <add binding="basicHttpsBinding" scheme="https" /> </protocolMapping> <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" /> </system.serviceModel>
Great, that's all we need to set up the service!
Testing the service
To test the service, we could just use WCF test client, but because I want to show you how to implement the service anywhere, I have created a simple console application to test with.
In the console application I have added a service reference 'MyServiceReference' to the service we just created and in the program.cs we just call the service method like this:
class Program { static void Main(string[] args) { using (var client = new MyServiceReference.MyServiceClient()) { Console.WriteLine(client.AddNumbers(5, 10)); } Console.ReadLine(); } }
And of course this outputs 15 in our console. Yay, it works! But anyone who can find the URL of our service can use it, lets fix this..
Implementing security
Up until now we were able to use IIS expres to host our service, but because we want to implement security using a custom validator, we have to run our service on our local IIS and implement SSL (https). If you don't do this, the security settings will be ignored!
I won't go over how to set up a WCF service on your local IIS with SSL in detail, but basiclly this is what you need to do:
- Create a new website on your IIS (mapped to your project)
- Create a self signed certificate
- Add the certificate to the trusted people directory
- Set up https binding with the certificate your created
- Configure security in your Web.config --> I will explain this below!
For more information about this you can view this link.
Define our authorization
Now that we have added our service to the local IIS it's time to define our authorization. To do this let's create a custom validator:
public class Validator : UserNamePasswordValidator { public override void Validate(string userName, string password) { if (userName == "user1" && password == "P@ssword1") return; throw new System.IdentityModel.Tokens.SecurityTokenException("Unknown Username or Password"); } }
Because we inherit form UserNamePasswordValidator we have to add a reference to 'System.IdentityModel' and add the following using statement:
using System.IdentityModel.Selectors;
All we do here is check if our username and password are correct, and if the aren't we return an exception. This exception will tell our WCF service that the authentication failed and the caller doesn't have access to use the service.
Configuring the service
To use this new validator we have to do some configuration in our Web.config. Inside our <system.serviceModel> I have changed some things.
First, I have created a new binding:
<bindings> <wsHttpBinding> <binding name="MyBinding"> <security mode="TransportWithMessageCredential"> <message clientCredentialType="UserName" /> </security> </binding> </wsHttpBinding> </bindings>
This tells us that when we use "MyBinding" there will be message security of type UserName.
Next, I have edited the behavior and I gave it the name "MyBehavior":
<behaviors> <serviceBehaviors> <behavior name="MyBehavior"> <!-- To avoid disclosing metadata information, set the values below to false before deployment --> <serviceMetadata httpGetEnabled="false" httpsGetEnabled="true"/> <!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information --> <serviceDebug includeExceptionDetailInFaults="false"/> <serviceCredentials> <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WcfService.Validator,WcfService"/> </serviceCredentials> </behavior> </serviceBehaviors> </behaviors>
There are a 2 things I did here. I have set the httpGetEnabled to false because we don't want to expose our service using HTTP, and I have added a new section <serviceCredentials> which tells our behavior to use our custom validator (specify the full name and namespace here).
Finally I have added the service configuration:
<services> <service name="WcfService.MyService" behaviorConfiguration="MyBehavior"> <endpoint address="/" binding="wsHttpBinding" contract="WcfService.IMyService" bindingConfiguration="MyBinding" /> </service> </services>
Here I have defined our service, using the default ABC (address, binding, contract) configuration, and I have also specified to use our behavior and bindingConfiguration we just specified.
That's it! We have secured our WCF service! Let's confirm that it works using our console app.
Verify that it actually works
I have re added the service reference to make sure we are using the latest version (don't forget to build your service!).
Now let's run the application again without any modifications, and see what happens.
An exception, Great!
As expected, we can't access our service without specifying a username and password. To verify that our service is still accessible if you provide the right credentials, let's edit our program like this:
class Program { static void Main(string[] args) { using (var client = new MyServiceReference.MyServiceClient()) { client.ClientCredentials.UserName.UserName = "user1"; client.ClientCredentials.UserName.Password = "P@ssword1"; Console.WriteLine(client.AddNumbers(5, 10)); } Console.ReadLine(); } }
Now if we run our program again, we get our result (15) as expected!
Wrapping up
As you can see, implementing username and password security in a WCF service is pretty easy if you known what you are doing. There a few things that you need to remember in order for it to work as expected. The most important one being that you have to run your service in HTTPS or you'll be banging your head against the wall for hours!
I hope this will help you when you have to implement some sort of security in WCF.
Thanks for reading, and leave a comment below if you have any questions or suggestions!