Click here to Skip to main content
15,860,972 members
Articles / Security / Encryption

WCF .NET 4.0 Console Hosted Json Rest Secure HTTP Web Service with Authentication and Roles

Rate me:
Please Sign up or sign in to vote.
4.98/5 (20 votes)
7 Dec 2011CPOL9 min read 91.8K   3.4K   50   18
A RESTful web service using the newest .NET platform features including automatic serialization/deserialization of complex types.

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:

XML
<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 SSL
  • security.transport.clientCredentialType - "Basic" means we are using Basic authentication over SSL

behaviors.serviceBehavior.behavior

  • serviceDebug.includeExceptionDetailsInFaults - only set true if in development
  • serviceCredentials.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 authentication
  • serviceCredentials.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 methods
  • serviceAuthorization.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

C#
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();

           //Close the ServiceHost.
           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:

C#
/// <summary>
/// Here is where we actually validate the password
/// </summary>
class WtfUserNamePasswordValidator : UserNamePasswordValidator
{  
    public override void Validate(string userName, string password)
    {
        if (string.IsNullOrEmpty(userName) | string.IsNullOrEmpty(password))
            throw new ArgumentNullException();

        //validate the username and password here against the database
        if (userName != "John" | password != "Doe")
            // This throws an informative fault to the client.
            throw new FaultException("Unknown Username or Incorrect Password");
            // When you do not want to throw an informative fault to the client,
            // throw the following exception.
            // throw new SecurityTokenException("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:

C#
public class WtfPrincipal : IPrincipal
    {
        IIdentity _identity;
        string[] _roles;

        public WtfPrincipal(IIdentity identity)
        {
            _identity = identity;
            LoadUsersRoles();
        }
        
        /// <summary>
        /// we should load up the user's roles from the database here based on username
        /// </summary>
        private void LoadUsersRoles()
        {
            //run query on db using _identity.Name
            //then put into _roles
            _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.

C#
public class WtfAuthorizationPolicy : IAuthorizationPolicy
   {
       private static readonly ILog log =
       LogManager.GetLogger(typeof(WtfAuthorizationPolicy));

       public bool Evaluate(EvaluationContext evaluationContext, ref object state)
       {
           // get the authenticated client identity
           IIdentity client = GetClientIdentity(evaluationContext);

           // set the custom principal
           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:

C#
[ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = 
            AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    // NOTE: If the service is renamed, remember to update the global.asax.cs file
    public class WtfSvc
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(WtfSvc));

        /// <summary>
        /// Demonstrates http get where parameters are passed in the URL.
        /// Also requires the user to be in the role of 'WtfUser'.
        /// </summary>
        /// <param name="firstName"></param>
        /// <param name="lastName"></param>
        /// <returns></returns>
        [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);
        }

        /// <summary>
        /// Demonstrates http post where request body contains two parameters 
        /// of simple type.
        /// Also requires the user to be in the role of 'WtfUser'
        /// </summary>
        /// <param name="firstName">comes from the json content of the body 
        /// of the post e.g. "{\"firstName\" : \"John\", \"lastName\" : \"Doe\" }"</param>
        /// <param name="lastName">comes from the json content of the body of the post 
        /// e.g. "{\"firstName\" : \"John\", \"lastName\" : \"Doe\" }"</param>
        /// <returns></returns>
        [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);
        }

        /// <summary>
        /// Demonstrates http post where request body contains the json string 
        /// representing an object of type Person.
        /// Also requires the user to be in the role of 'WtfUser'
        /// </summary>
        /// <param name="person">comes from the json content of the body of the post 
        ///e.g. "{\"person\":{\"FirstName\" : \"John\", \"LastName\" : \"Doe\" } }"</param>
        /// <returns></returns>
        [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).

ChromeGet.png

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:

C#
// Test first for trust override parameters
// This nice trust override stuff comes from Joey Parrish via As3crypto forums
var certTrusted:Boolean;
if (_config.trustAllCertificates) {
    certTrusted = true; // Blatantly trust everything
} else if (_config.trustSelfSignedCertificates ) {
    // Self-signed certs
   certTrusted = firstCert.isSelfSigned(new Date); 
} else {
    // Certs with a signer in the CA store - realistically, 
    // I should setup an event chain to handle this
    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:

C#
/**
     * Create the socket.
     * Create Socket or TLSSocket depending on URI scheme (http or https).
     */
    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

FlashClientGetAndPost.png

Here then is the code that you can use to make your flash client connect to the service and test the functions:

XML
<?xml version="1.0" encoding="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>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>
    
    <fx:Script>
        <![CDATA[
            import com.adobe.net.*;
            import com.hurlant.util.*;
            import org.httpclient.*;
            import org.httpclient.events.*;
            import org.httpclient.http.*;
                        
            //settings
            private var baseUri:String = "https://lacey:8000/WtfService/"
            
            //http client
            private var client:HttpClient;
            private var request:HttpRequest;
            private var uri:URI;
                        
            private function init():void{
                //create the http client
                client = new HttpClient();
                client.listener.onStatus = onStatus;
                client.listener.onData = onData;
                client.listener.onComplete = onComplete;
                client.listener.onError = onError;                
            }
                
            private function onStatus(event:org.httpclient.events.HttpStatusEvent):void{
                
            }
            
            private function onData(event:org.httpclient.events.HttpDataEvent):void{
                
                this.txtResult.text += "\n" + event.readUTFBytes();
            }
            
            private function onComplete(event:org.httpclient.events.HttpResponseEvent):void{
                
            }
            
            private function onError(event:org.httpclient.events.HttpErrorEvent):void{
                
            }            
            
            protected function btnPostSimple_clickHandler(event:MouseEvent):void
            {
                //call hello at service
                uri = new URI(baseUri + "HelloWorldPostSimple");
                
                var data:ByteArray = new ByteArray();
                data.writeUTFBytes("{\"firstName\" : \"John\", \"lastName\" : \"Doe\" }");
                data.position = 0;
                
                request = new Post();
                request.contentType = "application/json";
                request.body = data;
                request.header.add("Authorization", "Basic " + Base64.encode("John:Doe"));
                
                client.request(uri, request);                
            }
            
            protected function btnPostComplex_clickHandler(event:MouseEvent):void
            {
                //call hello at service
                uri = new URI(baseUri + "HelloWorldPostComplex");
                
                var data:ByteArray = new ByteArray();
                data.writeUTFBytes
           ("{\"person\":{\"FirstName\" : \"John\", \"LastName\" : \"Doe\" } }");
                data.position = 0;
                
                request = new Post();
                request.contentType = "application/json";
                request.body = data;
                request.header.add("Authorization", "Basic " + Base64.encode("John:Doe"));
                
                client.request(uri, request);
            }
            
            protected function btnGet_clickHandler(event:MouseEvent):void
            {
                //call hello at service
                uri = new URI(baseUri + "HelloWorld/John/Doe");
                request = new Get();
                request.header.add("Authorization", "Basic " + Base64.encode("John:Doe"));
                client.request(uri, request);                
            }            
        ]]>
    </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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Self Employed
Australia Australia
Has a background in Finance and Software development.
Has worked as a RAD developer for the likes of Credit-Suisse and Westpac Banking corporation.
Currently self-employed working on some personal projects.

Comments and Discussions

 
GeneralMy vote of 5 Pin
mhchang21-Jan-13 16:17
mhchang21-Jan-13 16:17 
QuestionCan not access to my service after config IIS with authentication succefully Pin
timbake11-Jul-12 22:47
timbake11-Jul-12 22:47 
QuestionLink for XP users Pin
GaryP15626-Apr-12 3:51
GaryP15626-Apr-12 3:51 
GeneralMy vote of 1 Pin
GaryP15625-Apr-12 4:23
GaryP15625-Apr-12 4:23 
GeneralMy vote of 1 Pin
GaryP15624-Apr-12 8:00
GaryP15624-Apr-12 8:00 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey27-Mar-12 4:10
professionalManoj Kumar Choubey27-Mar-12 4:10 
GeneralMy vote of 5 Pin
Scott Witherell21-Mar-12 8:11
Scott Witherell21-Mar-12 8:11 
QuestionIIS and Authentication Pin
Member 39507927-Jan-12 4:33
Member 39507927-Jan-12 4:33 
AnswerMessage Closed Pin
22-Sep-15 3:00
Member 1200320622-Sep-15 3:00 
QuestionHow does this function when applied to a WCF application instead of using a library and console? Pin
Member 81460839-Jan-12 5:21
Member 81460839-Jan-12 5:21 
QuestionCan this run in IIS and how do you get this config options in 4.0? Pin
claude.robert.bell7-Jan-12 17:14
claude.robert.bell7-Jan-12 17:14 
QuestionThanks a lot Pin
techno__guru22-Dec-11 9:40
techno__guru22-Dec-11 9:40 
GeneralMy vote of 5 Pin
L Hills20-Dec-11 3:03
L Hills20-Dec-11 3:03 
QuestionAwesome job Pin
Fiskinator13-Dec-11 3:29
Fiskinator13-Dec-11 3:29 
GeneralMy vote of 5 Pin
User 482203312-Dec-11 22:31
User 482203312-Dec-11 22:31 
QuestionMy 5 Pin
David C# Hobbyist.7-Dec-11 16:10
professionalDavid C# Hobbyist.7-Dec-11 16:10 
Nice 555555 Just for the WTFsvc name Laugh | :laugh:
Frazzle the name say's it all

AnswerRe: My 5 Pin
jeremylei8-Dec-11 1:39
jeremylei8-Dec-11 1:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.