Authentication Token Service for WCF Services (Part 1)
I am setting out to create a thin web UI that consists of only HTML, CSS, and Javascript (HCJ) for the front end. For the back end, I have Ajax-enabled WCF services.
I have a couple of options for authentication.
Options:
- Authenticate with username and password every time a service is called.
- Store the username and password once, then store the credentials in the session or a cookie or a javascript variable and pass them every time I call a subsequent service.
- Authentication to one WCF service, then store a token.
Option 1 – Authenticate every time
This is not acceptable to the users. It would be a pain to type in credentials over and over again when clicking around a website.
Option 2 – Authenticate once and store credentials
This option is not acceptable because we really don’t want to be storing credentials in cookies and headers. You could alleviate the concern by hashing the password and only storing the hash, but that is still questionable. It seems this might cause the username and password to be passed around too often and eventually, your credentials will be leaked.
Option 3 – Authenticate once and store a token
This option seems the most secure. After authenticating, a token is returned to the user. The other web services can be accessed by using the token. Now the credentials are not stored. They are only passed over the network at authentication time.
Secure Token Service
This third idea is the idea around the Secure Token Service (STS). However, the STS is designed around the idea of having a 3rd party provide authentication, for example, when you login to a website using Facebook even though it isn’t a Facebook website.
STS service implementation is complex. There are entire projects built around this idea. What if you want something simpler?
Basic Token Service (BTS)
I decided that for simple authentication, there needs to be an example on the web of a Basic Token Service.
In the basic token service, there is a the idea of a single service that provides authentication. That service returns a token if authenticated, a failure otherwise. If authenticated, the front end is responsible for passing the token to any subsequent web services. This could be a header value, a cookie or a url parameter. I am going to use a header value in my project.
Here is the design.
Since this is “Basic” it should use basic code, right? It does.
The BTS Code
Download here: WCF BTS
In Visual Studio, I chose New | Project | Installed > Templates > Visual C# > WCF | WCF Service Application.
OK, so lets do some simple code. In this example, we will put everything in code. (In part 2, I will enhance the code to look to the database.)
Ajax-enabled WCF Services
Add the Authentication WCF Service first. In Visual Studio, I right-clicked on the project and chose Add | New Item … | Installed > Visual C# > Web | WCF Service (Ajax-enabled)
<%@ ServiceHost Language="C#" Debug="true" Service="WcfSimpleTokenExample.Services.AuthenticationTokenService" CodeBehind="AuthenticationTokenService.svc.cs" %>
using System.Security.Authentication; using System.ServiceModel; using System.ServiceModel.Activation; using System.ServiceModel.Web; using WcfSimpleTokenExample.Business; using WcfSimpleTokenExample.Interfaces; using WcfSimpleTokenExample.Model; namespace WcfSimpleTokenExample.Services { [ServiceContract] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class AuthenticationTokenService { [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)] [OperationContract] public string Authenticate(Credentials creds) { ICredentialsValidator validator = new CodeExampleCredentialsValidator(); if (validator.IsValid(creds)) return new CodeExampleTokenBuilder().Build(creds); throw new InvalidCredentialException("Invalid credentials"); } } }
A second example service:
<%@ ServiceHost Language="C#" Debug="true" Service="WcfSimpleTokenExample.Services.Test1Service" CodeBehind="Test1Service.svc.cs" %>
using System.ServiceModel; using System.ServiceModel.Activation; using System.ServiceModel.Web; using System.Web; using WcfSimpleTokenExample.Business; using WcfSimpleTokenExample.Interfaces; namespace WcfSimpleTokenExample.Services { [ServiceContract] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class Test1Service { [OperationContract] [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)] public string Test() { var token = HttpContext.Current.Request.Headers["Token"]; ITokenValidator validator = new CodeExampleTokenValidator(); if (validator.IsValid(token)) { return "Your token worked!"; } else { return "Your token failed!"; } } } }
<?xml version="1.0"?> <configuration> <appSettings> <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" /> </appSettings> <system.web> <compilation debug="true" targetFramework="4.5" /> <httpRuntime targetFramework="4.5"/> </system.web> <system.serviceModel> <services> <service name="WcfSimpleTokenExample.Services.AuthenticationTokenService" behaviorConfiguration="ServiceBehaviorHttp" > <endpoint address="" behaviorConfiguration="AjaxEnabledBehavior" binding="webHttpBinding" contract="WcfSimpleTokenExample.Services.AuthenticationTokenService" /> </service> <service name="WcfSimpleTokenExample.Services.Test1Service" behaviorConfiguration="ServiceBehaviorHttp" > <endpoint address="" behaviorConfiguration="AjaxEnabledBehavior" binding="webHttpBinding" contract="WcfSimpleTokenExample.Services.Test1Service" /> </service> </services> <behaviors> <endpointBehaviors> <behavior name="AjaxEnabledBehavior"> <webHttp helpEnabled="true" /> </behavior> </endpointBehaviors> <serviceBehaviors> <behavior name="ServiceBehaviorHttp"> <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="true" /> </behavior> </serviceBehaviors> </behaviors> <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" /> </system.serviceModel> <system.webServer> <modules runAllManagedModulesForAllRequests="true"/> <directoryBrowse enabled="true"/> </system.webServer> </configuration>
Note: In the project, there is an xdt:Transform for the web.config.debug and the web.config.release if you use web deploy. These enforce that the web services that make them only use HTTPS. Check them out.
Models
Now we are going to have a single class in the Model for this basic example, a Credentials class.
namespace WcfSimpleTokenExample.Model { public class Credentials { public string User { get; set; } public string Password { get; set; } } }
Interfaces
using WcfSimpleTokenExample.Model; namespace WcfSimpleTokenExample.Interfaces { public interface ICredentialsValidator { bool IsValid(Credentials creds); } }
using WcfSimpleTokenExample.Model; namespace WcfSimpleTokenExample.Interfaces { interface ITokenBuilder { string Build(Credentials creds); } }
namespace WcfSimpleTokenExample.Interfaces { public interface ITokenValidator { bool IsValid(string token); } }
Business Implementations
using WcfSimpleTokenExample.Interfaces; using WcfSimpleTokenExample.Model; namespace WcfSimpleTokenExample.Business { public class CodeExampleCredentialsValidator : ICredentialsValidator { public bool IsValid(Credentials creds) { // Check for valid creds here // I compare using hashes only for example purposes if (creds.User == "user1" && Hash.Get(creds.Password, Hash.HashType.SHA256) == Hash.Get("pass1", Hash.HashType.SHA256)) return true; return false; } } }
using System.Security.Authentication; using WcfSimpleTokenExample.Interfaces; using WcfSimpleTokenExample.Model; namespace WcfSimpleTokenExample.Business { public class CodeExampleTokenBuilder : ITokenBuilder { internal static string StaticToken = "{B709CE08-D2DE-4201-962B-3BBAC74C5952}"; public string Build(Credentials creds) { if (new CodeExampleCredentialsValidator().IsValid(creds)) return StaticToken; throw new AuthenticationException(); } } }
using WcfSimpleTokenExample.Interfaces; namespace WcfSimpleTokenExample.Business { public class CodeExampleTokenValidator : ITokenValidator { public bool IsValid(string token) { return CodeExampleTokenBuilder.StaticToken == token; } } }
I also use the Hash.cs file from this post: A C# class to easily create an md5, sha1, sha256, or sha512 hash.
Demo
I use the Postman plugin for Chrome.
Postman
Step 1 – Authenticate and acquire token
- Set the url. In this example, it is a local debug url:
http://localhost:49911/Services/AuthenticationTokenService.svc/Authenticate. - Set a header value: Content-Type: application/json.
- Add the body: {“User”:”user1″,”Password”:”pass1″}
- Click Send.
- Copy the GUID returned for the next step.
Step 2 – Call subsequent service
- Set the url. In this example, it is a local debug url:
http://localhost:49911/Services/Test1Service.svc/Test - Add a header called “Token” and paste in the value received from the authentication step
Part 1 uses examples that are in subbed in statically in the code. In Authentication Token Service for WCF Services (Part 2 – Database Authentication), we will enhance this to use a database for credentials validation and token storage and token validation.
Hi,
I just want to know that how BTS it works like if I create token using BTS then need to store token in database of how will manage token..??
Check out the project on GitHub. https://github.com/rhyous/Auth.TokenService
Hi I am trying to download sample code. but the url mentioned in the article is broken.
Can you please provide latest url here?
Thanks
Omkar.
I having error in CodeExampleCredentialsValidator
Error CS0117 'Hash' does not contain a definition for 'HashType'
Hi,
Two issues:
1) The authenticate on this and the database, the only two I have tried do not work, I get an 'The incoming message has an unexpected message format 'Raw'. when submitting using the postman utility. If I switch to urlencoded, I get into the program but no matter what I do with the data it does not get passed into the program.
Also, the screen print of postman was wrong on the Test1Service.Test (first of all, you cannot see the full url entry) and it didn't work unless I put the parameters into the header.
I would like to use this as a model for my solution but I cannot get the Authenticate to pass variables. Not sure what id different from your API implementation from mine but passing in the body pulls my data out but I am not getting anything in your example. When it works thru encoding.
Let me know, Thanks, the sample app is pretty awesome and when the kinks get worked out, whether it is environment or whatever, it will be the best example on the web. I can say that with assurance since I feel like I have seen them all.
Regards,
I updated the images. Did you have the Content-Type: application/json header?
Got it running, I had changed the BodyStyle to wrapped and forgot to change it back to bare.
Thanks, this is a great start. Hope you can clean up the clutter I created...
Karl Jobst
Basic Token Service for WCF Services (Part 1) | Rhyous