Introduction
The article covers the creation of a SSL secured .NET 4.0 HTTP JSON REST web service hosted in a console application (non IIS dependent).
The service also provides a socket policy file server which is required for Adobe flash clients to connect (using as3httpclientlib).
A flash client application (WebServiceSubscribe
) is also available for download which demonstrates connecting to the web service. Any other client types can of course connect as well.
Background
The reason I want to share this code is because it has taken a very long time to get to this point. There are many tutorials and code snippets out there on the web about creating a restful service in WCF, but none of them cover all the features that one would actually want to utilize in an internet hosted service - there are just bits and pieces here and there. A lot of them also implement features in strange ways, trying to make WCF do things it just doesn't want to do, or shouldn't do.
A lot of the complexity comes down to the configuration - if this is done properly, things work together nice and simple.
So let's get to it!
Using the Code
The most important part to getting your service up and running is the configuration. The configuration provided by the WCF Rest Service Template 4.0 is just not up to the task as it doesn't provide any config for security or authentication. When you actually want these features, your configuration looks a lot different.
So here is the configuration:
<system.serviceModel>
<services>
<service name="WtfService.WtfSvc" behaviorConfiguration="WtfServiceBehaviour" >
<endpoint address=https://localhost:8000/WtfService
binding="webHttpBinding" bindingConfiguration="wtfSslBinding"
behaviorConfiguration="WebHttpBehaviour" contract="WtfService.WtfSvc">
</endpoint>
</service>
</services>
<bindings>
<webHttpBinding>
<binding name ="wtfSslBinding">
<security mode="Transport">
<transport clientCredentialType="Basic" />
</security>
</binding>
</webHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="WtfServiceBehaviour" >
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceCredentials >
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType=
"WtfService.WtfUserNamePasswordValidator, WtfService" />
</serviceCredentials>
<serviceAuthorization principalPermissionMode="Custom">
<authorizationPolicies>
<add policyType="WtfService.WtfAuthorizationPolicy, WtfService" />
</authorizationPolicies>
</serviceAuthorization>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="WebHttpBehaviour">
<webHttp automaticFormatSelectionEnabled="false"
defaultBodyStyle="Wrapped" defaultOutgoingResponseFormat="Json"
helpEnabled="true" />
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
The Config Explained (skip this if you already know what these mean)
services.service
service.behaviourConfiguration
- references behaviors.serviceBehaviors ("WtfServiceBehaviour")
services.service.endpoint
endpoint.address
-Assigns where the server will listen for requests endpoint.binding
- The type of binding the service will use. webHttpBinding
is a .NET provided binding type. See http://msdn.microsoft.com/en-us/library/bb412176.aspx endpoint.bindingConfiguration
- points to the settings of our webHttpBinding
- settings are set up in bindings.webHttpBinding.binding ("wtfSslBinding")
endpoint.behaviorConfiguration
- points to the behaviour of our webHttp
endpoint
- setup in endpointBehaviors.behavior ("WebHttpBehaviour")
endpoint.contract
- is the runtime type of our ServiceContract
class
bindings.webHttpBinding.binding
security.mode
- "Transport
" means the transport layer is secured by SSLsecurity.transport.clientCredentialType
- "Basic" means we are using Basic authentication over SSL
behaviors.serviceBehavior.behavior
serviceDebug.includeExceptionDetailsInFaults
- only set true
if in developmentserviceCredentials.userNameAuthentication.userNamePasswordValidationMode
- "Custom" means we are implementing our own user/pass validation class - i.e. so we can query a database rather than using Windows based authenticationserviceCredentials.customUserNamePasswordValidatorType
- the runtime class where we validate the credentials serviceAuthorization.principalPermissionMode
- "Custom" means we can implement our own authorization policy for role based access to our service methodsserviceAuthorization.authorizationPolicies.add.policyType
- This class needs to be implemented so we can associate our own iPrincipal
implementation with the running thread, so that we can take control of its "IsUserInRole
" method for role based security.
endpointBehaviors.behavior
webHttp.defaultBodyStyle
- "Wrapped" means our json result will be contained within a json object i.e. "{}" webHttp.defaultOutgoingResponseFormat
- "Json" means our service serves responses in json rather than XML webHttp.helpEnabled
- "true
" means a user can browse to e.g. https://localhost:8000/WtfService/Help and the framework will auto-generate a help page which describes the web service functions
Instantiating the Service from a Console Application
class Program
{
static void Main(string[] args)
{
SocketPolicyFileServer server = new SocketPolicyFileServer();
server.Start();
var host = new ServiceHost(typeof(WtfSvc));
host.Open();
Console.WriteLine("The service is ready at {0}",
host.Description.Endpoints[0].ListenUri);
Console.WriteLine("Press <Enter> to stop the service.");
Console.ReadLine();
host.Close();
}
}
Because we set up everything in the configuration file, instantiating the service is very simple. We just create a new ServiceHost
and pass in the type of our service.
The SocketPolicyFileServer
is just another service we create to let flash clients connect which serves up a SocketPolicyFile.xml file over TCP port 843. The credit for this socket policy code goes to http://socketpolicyfile.codeplex.com/.
Password Validation
Password validation occurs in a class overriding UserNamePasswordValidator
, and is referred to in the config file. This is what it looks like - obviously you would want to query a real database here:
class WtfUserNamePasswordValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (string.IsNullOrEmpty(userName) | string.IsNullOrEmpty(password))
throw new ArgumentNullException();
if (userName != "John" | password != "Doe")
throw new FaultException("Unknown Username or Incorrect Password");
}
}
Role Based Authentication
Password validation only authenticates the user. But we also want role based access to our web service methods. The first part of solving this is to create our own implementation of the IPrincipal
interface like so:
public class WtfPrincipal : IPrincipal
{
IIdentity _identity;
string[] _roles;
public WtfPrincipal(IIdentity identity)
{
_identity = identity;
LoadUsersRoles();
}
private void LoadUsersRoles()
{
_roles = new string[] { "WtfUser" };
}
public IIdentity Identity
{
get { return _identity; }
}
public bool IsInRole(string role)
{
if (_roles.Contains(role))
return true;
else
return false;
}
public string[] Roles
{
get { return _roles; }
}
}
The important part of the above code is to load up the user's roles from a database, and then IsInRole
will be called which checks that the user has the relevant role.
To push this IPrincipal
into the WCF channel, we do it within the IAuthorizationPolicy
which we implement - and again referenced in the config file.
public class WtfAuthorizationPolicy : IAuthorizationPolicy
{
private static readonly ILog log =
LogManager.GetLogger(typeof(WtfAuthorizationPolicy));
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
IIdentity client = GetClientIdentity(evaluationContext);
evaluationContext.Properties["Principal"] = new WtfPrincipal(client);
return true;
}
private IIdentity GetClientIdentity(EvaluationContext evaluationContext)
{
object obj;
if (!evaluationContext.Properties.TryGetValue("Identities", out obj))
throw new Exception("No Identity found");
IList<IIdentity> identities = obj as IList<IIdentity>;
if (identities == null || identities.Count <= 0)
throw new Exception("No Identity found");
return identities[0];
}
public System.IdentityModel.Claims.ClaimSet Issuer
{
get { throw new NotImplementedException(); }
}
public string Id
{
get { throw new NotImplementedException(); }
}
}
WCF will call into the Evaluate
function, and from there we can get the IIdentity
, which will among other things, contain the username of the client making the service call. We then wrap this IIdentity
in our custom IPrincipal
, and set it to the evaluationContext.Properties["Principal"]
. Once this is set, WCF will automatically call the IsUserInRole
on any PrincipalPermission
attributes that we apply to our service class. More information on this and code can be found here.
The Service Class
Next we will take a look at our service class and see how it all comes together. Note that you do not need to apply the OperationContract
attributes to the methods, nor do you need to apply DataContract
to complex types passed to or returned from the service (e.g. class Person
below).
Here is the code:
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode =
AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class WtfSvc
{
private static readonly ILog log = LogManager.GetLogger(typeof(WtfSvc));
[WebGet(UriTemplate = "HelloWorld/{firstName}/{lastName}")]
[PrincipalPermission(SecurityAction.Demand, Role = "WtfUser")]
public string HelloWorld(string firstName, string lastName)
{
return string.Format("Hello {0} {1}", firstName, lastName);
}
[WebInvoke(Method = "POST", UriTemplate = "HelloWorldPostSimple")]
[PrincipalPermission(SecurityAction.Demand, Role = "WtfUser")]
public string HelloWorldPostSimple(string firstName, string lastName)
{
return string.Format("Hello {0} {1}", firstName, lastName);
}
[WebInvoke(Method = "POST", UriTemplate = "HelloWorldPostComplex")]
[PrincipalPermission(SecurityAction.Demand, Role = "WtfUser")]
public string HelloWorldPostComplex(Person person)
{
return string.Format("Hello {0} {1}", person.FirstName, person.LastName);
}
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
Note that the 'PrincipalPermission
' attributes are all we require to secure our methods using role based authorization.
That covers the server side. Next we will look at some configuration issues to get the SSL up and running.
Creating a Developer Self Signed SSL Certificate
Creating the server certificate is very easy if you are running Windows 7. You can do this from IIS manager (even though we are not using IIS to host it). Follow the steps here:
If you don't have IIS 7.0 to create the certificate, here are some links which show you how to do it with makecert utility:
Reserve a Namespace for Use by your Service
Detailed steps for this are at the following MSDN article:
But basically, all you need to do is:
netsh http add urlacl url=http://+:8000/WtfService user=DOMAIN\user
with your relevant port, service endpoint and credentials.
Get the SSL Cert Running on a Port
To get the certification running on a port (in our case port 8000), you need to follow the steps listed in this MSDN article - http://msdn.microsoft.com/en-us/library/ms733791.aspx (and for the mmc snapin, this one - http://msdn.microsoft.com/en-us/library/ms788967.aspx).
Basically, all you need to do is:
netsh http add sslcert ipport=0.0.0.0:8000
certhash=0000000000003ed9cd0c315bbb6dc1c08da5e6
appid={00112233-4455-6677-8899-AABBCCDDEEFF}
where certhash
is the hash of the certificate you just created, and appid
can be a random guid
, or you can just pull it out one in your .NET project properties, assembly info.
That should be all you need to get the SSL side of your service running.
To check that everything is working, you should go to your web browser and test it out by browsing to the URL https://localhost:8000/WtfService/HelloWorld/John/Doe (for the credentials use username John, password Doe).
We get the red cross in crome for the https because it is a self-signed cert.
Flash Client (if you won't use flash, you can skip to the end)
Some precursors before using as3httpclientlib with self-signed certificate:
In order to allow a flash client to connect to the service, it should pretty much be out of the box with as3httpclientlib
if you use a certificate signed by a real CA. But we are self-signing, so the as3httpclientlib
available at https://github.com/gabriel/as3httpclient is not going to work for you.
as3httpclientlib
is built on top of as3cryptolib and as3cryptolib
by default does not allow self-signed certificates. But browsing the source code for as3cryptolib
I found that there is a configuration option in a class TLSConfig
that is used in TLSEngine
like so:
var certTrusted:Boolean;
if (_config.trustAllCertificates) {
certTrusted = true;
} else if (_config.trustSelfSignedCertificates ) {
certTrusted = firstCert.isSelfSigned(new Date);
} else {
certTrusted = firstCert.isSigned(_store, _config.CAStore );
}
So we want to set this config.trustSelfSignedCertificates
property to true
in as3httpclientlib
.
So what you can do, is to download the source for as3cryptolib
(you will need the source from svn not the download page as that is out of date) and then compile it.
Then download the source for as3httpclientlib
, and replace the as3cryptolib .swc file reference with the one you just compiled (the one as3httpclientlib
has in its references is out of date, and does not support this config feature). And then make the following amendment to the class HTTPSocket
in as3httpclientlib
:
protected function createSocket(secure:Boolean = false):void {
if (secure && !_proxy) {
var config:TLSConfig = new TLSConfig(TLSEngine.CLIENT);
config.trustSelfSignedCertificates = true;
_socket = new TLSSocket(null,0,config);
}
else _socket = new Socket();
_socket.addEventListener(Event.CONNECT, onConnect);
_socket.addEventListener(Event.CLOSE, onClose);
_socket.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
_socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
_socket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
}
What you are doing is just to create an instance of TLSConfig
and pass it to the TLSSocket
constructor. Then compile as3httpclientlib
.
Flash client and Code
Here then is the code that you can use to make your flash client connect to the service and test the functions:
="1.0"="utf-8"
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955"
minHeight="600" applicationComplete="init()">
<fx:Declarations>
</fx:Declarations>
<fx:Script>
<![CDATA[
</fx:Script>
<s:Button id="btnGet" x="34" y="28" label="Get" click="btnGet_clickHandler(event)"/>
<s:TextArea id="txtResult" x="33" y="68" width="294" height="257"/>
<s:Button id="btnPostSimple" x="122" y="28" label="Post Simple"
click="btnPostSimple_clickHandler(event)"/>
<s:Button id="btnPostComplex" x="229" y="28" label="Post Complex"
click="btnPostComplex_clickHandler(event)"/>
</s:Application>
Note that our project needs references to as3httpclientlib, as3cryptolib, and as3corelib .swc files.
And that's that. :)
Points of Interest
I initially thought of implementing a custom authentication method on top of WCF rather than using SSL so I could avoid the SSL handshake and so save a bit of bandwidth (and also the trouble with getting things working from flash). I pretty much had the whole thing implemented including HMAC, RSA, AES and it was a pretty robust solution.
The problem was I could only encrypt content back from the server to the client, and not from the client to the server. This is due to implementing encryption inside a MessageEncoder
in WCF (similar to here), which cannot access the HTTP headers on the ReadMessage
side due to the message not being created yet. I realised the only way to do it properly would be to create an entire Transport channel implementation, which would just be getting ridiculous. I could have implemented a handshake and put AES session keys in a static
class (which by itself is messy), but then I'm using a handshake, so I may as well use SSL and make things MUCH MUCH simpler. Which is what I did.
Conclusion
I hope this article helps you to get started with REST in WCF. I don't know about the rest of you but I like to get on with the coding of the business logic rather than messing around with the technology implementation, as a huge amount of time can be wasted without achieving anything of value, i.e., now that I have got all this up and running, I still have to develop the functionality of the web service I set out to build in the first place. ;) Hopefully this saves others from wasting their time.
History
- Release 1.0. - Article and code