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.
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.
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.
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.
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 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.
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
Generatemethod that generates the
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.<
In the source code, generating the private hash is handled by the method
Encode in the
PrivateHashEncoder class. It uses the
MD5Encoderclass to actually generate the MD5 hash.
After the Nonce has been generated the server can validate it.
##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.
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.
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 http://www.ietf.org/rfc/rfc2069.txt
- 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.
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.
##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.
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.
The base classes
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.
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.
##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 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
- Create a custom membership provider derived from
- Implement the
ValidateUsermethod against your back-end security storage
- Create a custom membership user derived from Membership user
- Implement the
GetUsermethod against your back-end security storage
- Create a custom
DigestAuthenticationHostFactory, see the example in the provided source code
- Add the new
DigestAuthenticationHostFactoryto the markup of the .svc file
#Points of Interest