For this example, I'm using Visual Studio 2013 with Update 2 installed. Update 2 will ensure that the templates use the latest and greatest versions of all technologies involved.
Add a project using the WCF Service Application template. You could also use the Asp.Net Web Application template, but I think you'll just end up with a bunch of extra references you don't need. Now take a look at the web.config file. You'll notice that using this template automatically adds the protocolMapping element which allows the service to be accessed using https. If you did not use this template, you would have had to add this for https support. You'll also notice that there are no endpoints or bindings configured. That's because WCF has default bindings and IIS can determine the endpoint by examining the .svc file.
I delete the sample service created for you.
Next, I find it convenient to host the web services I'm developing on my local IIS. Click on My Project, Web tab, and type http://{name of your computer}.{domain}.{com}/{something meaningful}. It is important to use the fully qualified name of your computer because the certificate you will soon use is named the same and WCF won't like it if they are not the same. Click Create Virtual Directory. Open up IIS Manager, and change the app pool of the site to one with an associated identity of your (hopefully powerful) account. This becomes important when you connect to a database for authentication.
Add your web service by using the WCF Service template. Build the solution, and right click the service. Choose View in Browser to make sure all is well so far.
Now it's time to enable SSL (https).
Read here to do this. At this point, you'll want to change the Project URL to include https on the Web tab when you go to My Project. I think it's a good idea to go ahead and setup a binding configuration and an endpoint. As I said earlier, WCF can figure all this out because of default values. However, Visual Studio's Add Service Reference automatic proxy generator can get confused when you are setting up a client. You can either use the Microsoft Service Configuration Editor (Right click web.config and choose Edit WCF Configuration) tool or edit the web.config by hand. If you use the tool, just click the Service folder, and choose Create a New Service. This will walk you through a wizard to create the service node. After that there is a link to create the binding configuration. You end up with this inside the <system.serviceModel> element:
<bindings>
<basicHttpsBinding>
<binding name="BasicHttpsBindingConfig" />
</basicHttpsBinding>
</bindings>
<services>
<service name="AssemblyName.YourService">
<endpoint address="https://ComputerName.domain.whatever/IISSite/YourService.svc"
binding="basicHttpsBinding" bindingConfiguration="BasicHttpsBindingConfig"
contract="AssemblyName.IYourService" />
</service>
</services>
You also need to set the multipleSiteBindingEnabled to False when you are using https:
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="false" />
At this point, I would create a little test app in a completely different solution and make sure you can call your new web service. A Windows Console app will work great. Just Add, Service Reference and specify the endpoint of the service. By adding the reference, Visual Studio will add the following to your app.config file:
<bindings>
<basicHttpBinding>
<binding name="BasicHttpsBinding_IYourService">
<security mode="Transport" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://ComputerName.domain.whatever/IISSite/YourService.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpsBinding_IYourService"
contract="BenefitsServiceReference.IYourService" name="BasicHttpsBinding_IYourService" />
</client>
There are a couple of things to note here. Why does the client configuration set up for basicHttpBinding when the server configuration is set up for basicHttp
sBinding? Essentially basicHttpBinding and basicHttpsBinding are exactly the same. The only difference is that basicHttpsBinding includes the <security mode="Transport" /> by default.
Now that the web service is up and running with SSL we want to configure it to require credentials in order to access it. You can use the ASP.NET Membership, but this has been superceded by ASP.NET Identity and WCF doesn't directly interface with ASP.NET Identity yet. So, we will create a custom user name and password validator which we will eventually use to authenticate with ASP.NET Identity. First, we need to create the validator class itself. Add references to the following:
- System.IdentityModel
- System.IdentityModel.Selectors
Add this class to your project:
Imports System.IdentityModel.Selectors
Imports System.ServiceModel
Public Class CustomUserNameValidator
Inherits UserNamePasswordValidator
Public Overrides Sub Validate(userName As String, password As String)
If Not (userName = "Steve" AndAlso password = "topsecret") Then
Throw New FaultException("Unknown Username or Incorrect Password")
End If
End Sub
End Class
It's pretty obvious what is happening here. If your User Name and Password is not right, an exception gets thrown back to the client. Now we have to configure the web service to use this. First of all we need to edit our binding configuration. Remember that by default basicHttpsBinding uses Transport as the Security mode. We need to change this to TransportWithMessageCredential. We are protecting (SSL) the service at the transport (IIS) level and authenticating at the message (web service) level. Here is the binding configuration after we do that:
<bindings>
<basicHttpsBinding>
<binding name="BasicHttpsBindingConfig">
<security mode="TransportWithMessageCredential" />
</binding>
</basicHttpsBinding>
</bindings>
You also need to add a serviceCredentials behavior to your existing behavior element:
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="AssemblyName.CustomUserNameValidator, AssemblyName" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
As with most configuration changes, here you're definitely better off using the Service Configuration Editor tool. This is where we specify our custom class for validating the user name and password. Note the specific way that the class is specified along with the assembly that contains the class.
The only configuration change in your client console app is changing the security mode to "TransportWithMessageCredential". When you run your console, you will notice that you'll get an exception if you fail to provide credentials. To specify the client's credentials, do the following:
YourServiceProxy.ClientCredentials.UserName.UserName = "Steve"
YourServiceProxy.ClientCredentials.UserName.Password = "topsecret"
If the client provides accurate credentials, then the service will return results; otherwise, an exception will be thrown.
With this infrastructure in place, you can change the code in the CustomUserNameValidator class to connect to any database you want to authenticate. I mentioned earlier my desire to access ASP.NET Identity to authenticate users for the web service. In a separate solution, I have
set up an ASP.NET MVC Web Application and configured it with ASP.NET Identity authentication. I am going to use this web application to administer user accounts for my web service. I simply add this class to my MVC project:
Imports Microsoft.AspNet.Identity
Imports Microsoft.AspNet.Identity.EntityFramework
Public Module SecurityManager
Public Function IsAuthenticated(userName As String, password As String) As Boolean
Dim MyUserStore As UserStore(Of IdentityUser) = New UserStore(Of IdentityUser)
Dim MyUserManager As UserManager(Of IdentityUser) = New UserManager(Of IdentityUser)(MyUserStore)
Dim MyUser As IdentityUser = MyUserManager.Find(userName, password)
If MyUser Is Nothing Then
Return False
Else
Return True
End If
End Function
End Module
Now, if I can call this function from my CustomUserNameValidator in my WCF project, I will be authenticating using ASP.NET Identity. First add a reference to the dll produced by building the MVC project. Then, simply add this code to your CustomUserNameValidator:
If Not SecurityManager.IsAuthenticated(userName, password) Then
Throw New FaultException("Unknown Username or Incorrect Password")
End If
Of course, it's not quite that easy. You need to do two more things:
- Use NuGet to install the same version of Entity Framework that your MVC project uses. This will setup your web.config with some special Entity Framework stuff that you will need.
- Copy the connectionStrings section out of the MVC web.config file and put it into the WCF web.config file. This connection string contains the database where the identity data is.
To get the name of the user who is accessing your service use this line:
System.ServiceModel.ServiceSecurityContext.Current.PrimaryIdentity.Name
So there we go. We now have a secure web service. Only accessible via https and secured by credentials stored in an ASP.NET Identity database.