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 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 let's write some simple code. In this example, we will put everything in the code. (In part 2, I will enhance the code to look at 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!";
}
}
}
}
="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)
{
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.
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 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.