Click here to Skip to main content
Click here to Skip to main content

Digest Authentication on a WCF REST Service

, 27 Feb 2011
Rate this:
Please Sign up or sign in to vote.
An article that explains Digest Authentication and a library to use Digest Authentication with WCF Rest and validate against any back-end.

Contents

Introduction

This is a follow-up on my earlier article that described how to use BASIC Authentication with a WCF REST Service. The disadvantage of that solution was that you need a https tunnel to really secure the username password verification process. Although possible, this is not always a feasible situation, for example, if you don't want to invest in certificates or loose performance when using a https tunnel.

This article explains a different authentication mechanism called Digest Authentication which provides an alternative. This security mechanism is more secure than Basic Authentication and does not have the drawbacks from using a https tunnel.

Digest Authentication is available on multiple web servers and supported by multiple internet browsers. The drawback when using Digest Authentication with Internet Information server is that it automatically authenticates credentials against active directory. This article describes an implementation which enables you to secure a WCF REST service with Digest Authentication and authenticate against any back-end.

Digest Authentication was first described in RFC 2069 as an extension to HTTP Basic Authentication. Later, the verification algorithm and security was improved by RFC 2617. This is the current stable specification. The implementation in this article is based on that RFC 2617 specification. Digest Authentication is more secure because it uses MD5 cryptographic hashing and the use of a nonce to discourage cryptanalysis.

Overview of Digest Communication

Digest communication starts with a client that requests a resource from a web server. If the resource is secured with Digest Authentication, the server will respond with the http status code 401, which means Unauthorized.

Digest Authentication Communication

In the same response, the server indicates in the HTTP header with which mechanism the resource is secured. The HTTP header contains the following "WWW-Authenticate: Digest realm="realm", nonce="IVjZjc3Yg==", qop="auth". The first thing you should notice is the string Digest in the response, here the server indicates that the resource that was requested by the client is secured using Digest Authentication. Secondly, the server indicates the type of Digest Authentication algorithm to use by the client with Quality Of Protection (QOP) and the string called nonce which I will explain later in this article.

An internet browser responds to this by presenting the user a dialog, in this dialog the user is able to enter his username and password. Note, that this dialog does not show the warning about transmitting the credentials in clear text as with a Basic Authentication secured site.

Digest Authentication Credentials Screen

When the user enters the credentials in this dialog, the browser requests the resource from the server again. This time, the client adds additional information to the HTTP header regarding Digest Authentication.

Digest Authentication Second

The server validates the information and returns the requested resource to the client. The details of the response from the server and the additional request of the client will be described in the following part of this article.

Digest Authentication

When the server responds to an unauthenticated client request, the server adds a nonce and a qop key to the header of the HTTP response. Both are typical for Digest Authentication. First, the nonce will be described and second the QOP quality of protection.

The Nonce

The Nonce stands for "Number used Once", this is a pseudo random number that ensures that old communications between a client and a server cannot be reused in replay attacks. A replay attack is a network attack in which previous valid data transmission is repeated. This is done by an adversary who intercepts the data and retransmit it. According to the RFC 2716 specification, the Nonce is a server specified data string which should be uniquely generated each time a 401 response is returned by the server. The 401 response that is sent back to the client includes the Nonce generated by the server. According to RFC 2716, the client should add this nonce to the header of next requests.

Generating the Nonce

The format of the nonce depends on the implementation. Each RFC 2617 digest authentication implementation may define their own nonce format. However, one should carefully design the format of the nonce as it is a part of the quality of the security. For my implementation, I choose to include a date time stamp and the IP address of the client into the nonce. The implementation generates the nonce as follows.

Nonce = Base64( TimeStamp : PrivateHash)

The nonce is generated by base64 encoding the string that is constructed by concatenating the time stamp, a colon and a generated private hash. In the source code, this is handled by the NonceGenerator class which has a Generate method that generates the Nonce string.

public string Generate(string ipAddress)
{
   double dateTimeInMilliSeconds =
      (DateTime.UtcNow - DateTime.MinValue).TotalMilliseconds;
   string dateTimeInMilliSecondsString =
      dateTimeInMilliSeconds.ToString();
   string privateHash = privateHashEncoder.Encode(
      dateTimeInMilliSecondsString,
      ipAddress);
   string stringToBase64Encode =
      string.Format("{0}:{1}", dateTimeInMilliSecondsString, privateHash);
   return base64Converter.Encode(stringToBase64Encode);
}

MD5 is used to generate the private hash of the string that is constructed by concatenating the time stamp, a colon, the IP address of the client, a colon and a private key that is only known to the server. As MD5 is used, the generation is one-way. It is not possible to reconstruct this information from the private hash.

PrivateHash = MD5Hash( TimeStamp : IP Address : Private key)

In the source code, generating the private hash is handled by the method Encode in the PrivateHashEncoder class. It uses the MD5Encoder class to actually generate the MD5 hash.

public string Encode(string dateTimeInMilliSecondsString,
    string ipAddress)
{
  string stringToEncode = string.Format(
     "{0}:{1}:{2}",
     dateTimeInMilliSecondsString,
     ipAddress,
     privateKey);
  return md5Encoder.Encode(stringToEncode);
}

Validating the Nonce

Every time the client sends the nonce to the server, the server validates if this is the nonce that the server sends to the client. The server validates the nonce in two steps:

The first thing that this implementation on the server does is validate if this PrivateHash was generated by this server and returned to this client. The server does this by generating the PrivateHash with the time stamp that is available in the Nonce and the IP address of the client. If this does not deliver the same PrivateHash as in the nonce from the client, the nonce is incorrect and the server responds with a 401. The NonceValidator is responsible in the source code for validating this nonce.

public virtual bool Validate(string nonce,
   string ipAddress)
{
   string[] decodedParts = GetDecodedParts(nonce);
   string md5EncodedString = privateHashEncoder.Encode(
      decodedParts[0],
      ipAddress);
   return string.CompareOrdinal(
      decodedParts[1],
      md5EncodedString) == 0;
}

Secondly, the server checks if the Time Stamp is too old. The server holds a certain time-out for a nonce. For example, the time-out is 300 seconds. The server validates if the time stamp in the nonce is not older than 300 seconds. If the nonce is older than 300 seconds, the server returns an indication in the HTTP header that the received nonce is too old together with a new nonce. RFC 2617 uses a special key called Stale in the header that is sets to true when the Nonce is too old. The NonceValidator is also responsible for checking if the time stamp is too old.

public virtual bool IsStale(string nonce)
{
   string[] decodedParts = GetDecodedParts(nonce);
   DateTime dateTimeFromNonce =
      nonceTimeStampParser.Parse(decodedParts[0]);
   return dateTimeFromNonce.AddSeconds(
      staleTimeOutInSeconds) < DateTime.UtcNow;
}

By using a time stamp and the IP address in the nonce, we make sure that the request is recent and comes from the client that requested the resource.

Quality Of Protection

Digest Authentication allows the server to ask which algorithm the client should use to encrypt the credentials of the user. Digest Authentication allows the following Quality Of Protection.

  • none = Default protection compatible with RFC 2069
  • auth = Increased protection that includes a client nonce and a client nonce counter
  • auth-int = Increased protection and integrity that included all of auth and a hash of the contents of the body

Note that it is a request from the server, the client itself is allowed to choose a lesser secure qop algorithm. If the server requests for auth, it is ok for the client to start communicating with the default or none qop.

The implementation with this article supports both default/none and auth. The class DefaultDigestEncoder and the class AuthDigestEncoder implement the default and the auth type of quality of protection. Both classes derive from DigestEncodeBase which holds common functionality.

DigestEncoder

At runtime, the server instantiates both types of encoders and stores them in a dictionary with the qop algorithm as the key. This enables the server to easily switch between different types of encoders at runtime.

internal class DigestEncoders :
   Dictionary
{
 public DigestEncoders(MD5Encoder md5Encoder)
 {
  Add(DigestQop.None, new DefaultDigestEncoder(md5Encoder));
  Add(DigestQop.Auth, new AuthDigestEncoder(md5Encoder));
 }

 public virtual DigestEncoderBase GetEncoder(DigestQop digestQop)
 {
  return this[digestQop];
 }
}

None or Default QOP

When an internet browser receives 401 HTTP status code with Digest in the authentication header, it will show a dialog for entering the username and password. When the client uses the default qop which is compatible with RFC 2069, the client encrypts the user name and password as follows.

HA1 = MD5( username : realm : password)

HA2 = MD5( method : digestURI)

response = MD5( HA1 : nonce : HA2)

An MD5 hash is created from the user name, realm and password, a separate MD5 hash is created from the HTTP method and the URI of the resource that the client requests. The response is created through a MD5 hash that combines the previous two MD5 hashes and the server generated nonce. The DigestEncoderBase class holds the functionality to generate both the HA1 and HA2 hashes.

private string CreateHa1(DigestHeader digestHeader,
   string password)
{
  return md5Encoder.Encode(
    string.Format(
    "{0}:{1}:{2}",
    digestHeader.UserName,
    digestHeader.Realm,
    password));
}

private string CreateHa2(DigestHeader digestHeader)
{
  return md5Encoder.Encode(
    string.Format(
    "{0}:{1}",
    digestHeader.Method,
    digestHeader.Uri));
}

The base classes AuthDigestEncoder and DefaultDigestEncoder are responsible for generating the response. This last step, generating the response, is what differs in the two derived classes. The response of the Auth algorithm should be generated differently. The Auth algorithm includes a nonceCount and a client generated Nonce in the response. Also, the actual qop string is concatenated before the hash is calculated.

response = MD5( HA1 : nonce : nonceCount : clientNonce : qop : HA2)

This is why the Auth algorithm is more secure than Default, the server performs an additional check to see if the nonceCount is incremented by the client with every request. The CreateResponse method of the AuthDigestEncoder generates the Auth response.

public override string CreateResponse(
   DigestHeader digestHeader,
   string ha1,
   string ha2)
{
  return
   md5Encoder.Encode(
     string.Format(
     "{0}:{1}:{2}:{3}:{4}:{5}",
     ha1,
     digestHeader.Nonce,
     digestHeader.NounceCounter,
     digestHeader.Cnonce,
     digestHeader.Qop.ToString(),
     ha2));
}

Extending WCF REST

To be able to integrate Digest Authentication with WCF REST, the WCF REST framework has to be extended. This is done by creating a custom RequestInterceptor. For more information, take a look at my previous article on CodeProject which explains this extension in more detail.

Retrieving and Storing User Credentials

The password of the user is transmitted as part of the response generated by the client to the server. It is not possible for the server to extract the password from the response. The server generates a response and checks if the response is equal to the response that was given by the client. This means that there are two options for storing and retrieval of user credentials using Digest Authentication.

  • The first and most secure option is for every user to store the HA1 key in the credentials data storage and validate using the stored HA1 key. This has a disadvantage because you have to change the HA1 key in the data storage if the username, password or realm changes.
  • The second option is to store the password of the use in the credentials data storage in such a way that it is possible to retrieve the original password. This is obviously less secure than the first option.

Using the Source Code

If you want to secure your own WCF REST service with Basic Authentication using the provided source code, you need to execute the following steps:

  • Add a reference to the DigestAuthenticationUsingWCF assembly
  • Create a custom membership provider derived from MembershipProvider
  • Implement the ValidateUser method against your back-end security storage
  • Create a custom membership user derived from Membership user
  • Implement the GetUser method against your back-end security storage
  • Create a custom DigestAuthenticationHostFactory, see the example in the provided source code
  • Add the new DigestAuthenticationHostFactory to the markup of the .svc file

Points of Interest

The provided source code is developed using TDD and uses the NUnit framework for creating and executing tests. Rhino mocks is used as a mocking framework inside the unit tests.

History

  • 28th Feb, 2011
    • Initial post

License

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

About the Author

Patrick Kalkman
Architect http://www.hinttech.nl
Netherlands Netherlands
Patrick Kalkman is a senior Software Architect with more than 20 years professional development experience. He works for Hinttech where he develops state of the art web applications.

Patrick enjoys writing his blog. It discusses software architectures using semantic web technologies. Patrick can be reached at pkalkie@gmail.com.
 
Published Windows 8 apps:
 
Published Windows Phone apps:
 
Awards:

Best Mobile article of March 2012
Best Mobile article of June 2012
Follow on   Twitter

Comments and Discussions

 
Questionthanks, with a small correction PinmemberMember 317186023-Jun-13 8:56 
QuestionQuestion Regarding Adding WCF Service on Client Side. PinmemberMember 850020721-Sep-12 3:26 
Questionmissing svc file and not able to implement ServiceHostFactory PinmemberManu Gino26-Jul-12 15:23 
Questioncan you help me Pinmembernew soft7-Apr-12 1:23 
AnswerRe: can you help me PinmemberPatrick Kalkman9-Apr-12 20:50 
GeneralExcellent Read PinmemberKeithBarrett23-Feb-12 9:14 
GeneralRe: Excellent Read PinmemberPatrick Kalkman23-Feb-12 22:03 
QuestionGreat article to implement the authentication on WCF REST. Small help required PinmemberBharadwajSonti24-Jan-12 2:46 
AnswerRe: Great article to implement the authentication on WCF REST. Small help required PinmemberPatrick Kalkman23-Feb-12 22:02 
QuestionHosted solution adding extra headers Pinmemberdbullen21-Sep-11 10:04 
QuestionDo not use the word "Digest" in your URI Pinmemberdbullen20-Sep-11 8:04 
AnswerRe: Do not use the word "Digest" in your URI PinmemberPatrick Kalkman20-Sep-11 8:30 
QuestionGreat article but having trouble implementing Pinmemberdbullen18-Sep-11 9:27 
AnswerRe: Great article but having trouble implementing PinmemberPatrick Kalkman18-Sep-11 21:45 
GeneralRe: Great article but having trouble implementing Pinmemberdbullen19-Sep-11 2:18 
GeneralRe: Great article but having trouble implementing Pinmemberdbullen20-Sep-11 0:34 
GeneralRe: Great article but having trouble implementing Pinmemberdbullen20-Sep-11 7:55 
GeneralRe: Great article but having trouble implementing Pinmemberani_198029-Nov-11 5:32 
GeneralRe: Great article but having trouble implementing PinmemberMember 996201124-Apr-13 20:58 
GeneralRe: Great article but having trouble implementing PinmemberBharadwajSonti27-Jan-12 4:26 
QuestionImplementing this model along with Form authentication PinmemberMember 819129826-Aug-11 8:09 
AnswerRe: Implementing this model along with Form authentication PinmemberPatrick Kalkman18-Sep-11 21:48 
QuestionDigest Auth for Non-REST WCF self-hosted Services PinmemberBeppi29-Jul-11 0:41 
AnswerRe: Digest Auth for Non-REST WCF self-hosted Services PinmemberPatrick Kalkman14-Aug-11 0:56 
GeneralHow can the consumer make consecutive requests to the provider which integrated the Digest Auth security layer ? Pinmemberuyhung23-May-11 23:37 
GeneralRe: How can the consumer make consecutive requests to the provider which integrated the Digest Auth security layer ? PinmemberPatrick Kalkman25-May-11 20:03 
AnswerRe: How can the consumer make consecutive requests to the provider which integrated the Digest Auth security layer ? Pinmemberuyhung25-May-11 22:57 
GeneralRe: How can the consumer make consecutive requests to the provider which integrated the Digest Auth security layer ? PinmemberPatrick Kalkman26-May-11 22:11 
GeneralDynamically configure which service methods require secure authenticate or not Pinmemberuyhung9-Apr-12 0:22 
GeneralRe: Dynamically configure which service methods require secure authenticate or not PinmemberPatrick Kalkman9-Apr-12 20:53 
GeneralMy Vote of 5 PinmemberRaviRanjankr1-Mar-11 21:45 
GeneralRe: My Vote of 5 PinmemberPatrick Kalkman2-Mar-11 11:16 
GeneralGreat article, again PinmemberKai Friis1-Mar-11 21:07 
GeneralRe: Great article, again PinmemberPatrick Kalkman2-Mar-11 11:16 
GeneralServiceAuthorizationManager worked here too. PinmemberKai Friis3-Mar-11 7:45 
GeneralRe: ServiceAuthorizationManager worked here too. PinmemberPatrick Kalkman3-Mar-11 9:25 
GeneralRe: ServiceAuthorizationManager worked here too. PinmemberKai Friis3-Mar-11 21:00 
GeneralMy vote of 5 Pinmemberalhambra-eidos1-Mar-11 2:47 
GeneralRe: My vote of 5 PinmemberPatrick Kalkman2-Mar-11 11:15 
GeneralMy vote of 5 PinmvpMarcelo Ricardo de Oliveira28-Feb-11 8:45 
GeneralRe: My vote of 5 PinmemberPatrick Kalkman28-Feb-11 22:37 
GeneralMy vote of 5 PinmemberYingbiao27-Feb-11 10:56 
GeneralRe: My vote of 5 PinmemberPatrick Kalkman27-Feb-11 23:01 

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

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

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 27 Feb 2011
Article Copyright 2011 by Patrick Kalkman
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid