DCSIMG
How To: Implement HMAC Authentication On a RESTful WCF Service - Itai Goldstein

How To: Implement HMAC Authentication On a RESTful WCF Service

There are several approaches in implementing authentication in RESTful WCF services. Using SSL and Digest Authentication are two options, while in this post I would like to demonstrate another approach which uses HMAC (Hash Message Authentication Code) in order to achieve both message authentication and integrity.

In this approach the server provides the client an ID and a secret key through some technique (e.g. sending an email containing the ID and the secret key) which will be used by the client to sign all his requests.

A secret key can be generated in the following way:

byte[] secretkey = new Byte[64]; RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); rng.GetBytes(secretkey);

This post will not cover what is a RESTful service and how should we implement all it’s properties in WCF, for this purpose you can refer to this great article which I used in my research prior to this post.

In our example the service will provide specific user’s bookmarks to its consumer. In order to consume this service, a requester must provide some hashed message (in our case – the request Uri encrypted with the secret key) in the request header authorization property (different implementations of the client would be provided in future posts).

So without further ado's, this is the service contract method definition:

[WebGet(UriTemplate = "users/{username}/bookmarks?tag={tag}")] [OperationContract] Bookmarks GetUserBookmarks(string username, string tag);

The contract enforces us to use the specified Uri template which implies that for a specific username (the requested user) and specific tag filter, the requester will be provided with a bookmarks list matching this criteria.

Here is the service method implementation:

public Bookmarks GetUserBookmarks(string username, string tag) { WebOperationContext context = WebOperationContext.Current; OutgoingWebResponseContext outgoingResponseContext = context.OutgoingResponse; bool isUserAuthenticated = IsUserAuthenticated(username); if (isUserAuthenticated == false) { outgoingResponseContext.StatusCode = HttpStatusCode.Unauthorized; return null; } outgoingResponseContext.StatusCode = HttpStatusCode.OK; Bookmarks bookmarks = new Bookmarks(BookmarksProvider.GetUserBookmarks(username, tag)); return bookmarks; }

The simple implementation I used here suggests that the request is being checked for its user’s authentication: if the authentication fails, the requester will be responded with Unauthorized HTTP status code (401) and no bookmarks. If the authentication succeeds – the bookmarks list is provided along with OK HTTP status code (200).

I’ve skipped the BookmarksProvider.GetUserBookmarks method implementation due to it’s irrelevancy for this post subject.

Here is my implementation for the IsUserAuthenticated method:

private bool IsUserAuthenticated(string username) { WebOperationContext context = WebOperationContext.Current; IncomingWebRequestContext incomingRequestContext = context.IncomingRequest; string requestUri = incomingRequestContext.UriTemplateMatch.RequestUri.ToString(); string authorizationHeader = incomingRequestContext.Headers[HttpRequestHeader.Authorization]; string authenticationToekn = string.Format("{1}{0}{2}", Delimeters.FirstClassDelimeter, authorizationHeader, requestUri); byte[] userKey = GetUserKey(username); bool isValidHash = Authentication.ValidateHash( userKey, authenticationToekn, Delimeters.FirstClassDelimeter, UTF8Encoding.UTF8); return isValidHash; }

This method composes an authentication token (this is a pure implementation step – it is up to the server to decide it’s protocol – and up to the client to obey in order to consume..) from the request Uri and the hashed authorization header. Then the authentication token is being validated along with the current requester secret key (fetched by the GetUserKey method) through the Authentication.ValidateHash method:

public static bool ValidateHash(byte[] key, string message, string delimeter, Encoding encoding) { HMACMD5 hmacMD5 = new HMACMD5(key); string[] messageParts = message.Split(new string[] { delimeter }, StringSplitOptions.RemoveEmptyEntries); if (messageParts == null || messageParts.Length < 2) { return false; } string encodedText = messageParts[0]; string text = messageParts[1]; byte[] textBytes = encoding.GetBytes(text); byte[] computedHash = hmacMD5.ComputeHash(textBytes); string computedHashString = Convert.ToBase64String(computedHash); return encodedText.Equals(computedHashString); }

In the validation method, the authentication token is being split to the message and hashed message parts – in our case, it’s the request Uri and the request Uri hashed using the requester’s secret key. After the parts are split, the secret key is being used to generate the hashed message (using the HMACMD5 class) again in order to be compared to the received hashed message. If the hashed messages are equal then the authentication is ok, otherwise it is invalid.

There are several ways to consume this service. In this post I’ll go over one of them in which a console application consumes our service:

byte[] key = GetUserKey(); string uri = "http://localhost:4609/ServiceHost/BookmarksService.svc/users/itai/bookmarks?tag=News"; HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest; string encodedUri = Authentication.EncodeText(key, uri, UTF8Encoding.UTF8); request.Headers[HttpRequestHeader.Authorization] = encodedUri; HttpWebResponse response = request.GetResponse() as HttpWebResponse; Stream bookmarksStream = response.GetResponseStream(); StreamReader reader = new StreamReader(bookmarksStream); string str = reader.ReadToEnd(); reader.Close(); bookmarksStream.Close();

As you can see for yourself, this client is rather simple: prepare the request object using service Uri and define the authorization header using the client secret key to hash the message (in our case it is the request Uri), then send the request and parse the response (XML formatted).

Here is Authentication.EncodeText method implementation:

public static string EncodeText(byte[] key, string text, Encoding encoding) { HMACMD5 hmacMD5 = new HMACMD5(key); byte[] textBytes = encoding.GetBytes(text); byte[] encodedTextBytes = hmacMD5.ComputeHash(textBytes); string encodedText = Convert.ToBase64String(encodedTextBytes); return encodedText; }

And here are our client results:image

Summary 

In this post I’ve implemented a RESTful WCF service which is protected by HMAC authentication token which is being passed in the service request header by a simple service consumer.

In my upcoming posts, I’ll review another different client implementations – stay tuned..

Hope you’ll find it useful.

References:

A Guide to Designing and Building RESTful Web Services with WCF 3.5

HMACMD5 at MSDN

Published Sunday, February 22, 2009 4:21 PM by itai

Comments

# HOW TO: IMPLEMENT HMAC AUTHENTICATION ON A RESTFUL WCF SERVICE

You've been kicked (a good thing) - Trackback from DotNetKicks.com

Sunday, February 22, 2009 4:29 PM by DotNetKicks.com

# re: How To: Implement HMAC Authentication On a RESTful WCF Service

Excellent article and nice implementation.  I actually implemented something very similar for a couple WCF projects I did recently using HMAC authentication as it fits very nicely to what we needed to do.

However, adding authentication/logging/tracing inside your methods (web operations) is not a good idea and that needs to be placed into the WCF interceptor layer that can trap unauthorized messages way before they make it onto your business logic stack.

Monday, February 23, 2009 3:22 AM by Bart Czernicki

# re: How To: Implement HMAC Authentication On a RESTful WCF Service

Hi Bart, I totally agree with your remark of the use of these operations in our service methods - they all should stay in some interceptor as a policy. In these kind of articles, I prefer not going very deep in order to focus on the main subject. Thanks for your comment!

Monday, February 23, 2009 10:02 AM by itai

# re: How To: Implement HMAC Authentication On a RESTful WCF Service

You're missing one very importaqnt and easy hack. Replay attack. I don't event have to know your secret key and can simply replay the message.

Friday, July 31, 2009 6:58 PM by Jeff

# re: How To: Implement HMAC Authentication On a RESTful WCF Service

Jeff,

Sorry to say, but almost any implementation is subject to a replay attack if you do not use SSL.  I thing for the purposes of this article, Itai did not include that as a requirement, but it would be a requirement to use SSL for ANY authenticated/secured web service.  Doing otherwise would be irresponsible.

Saturday, October 03, 2009 7:07 PM by Todd

# re: How To: Implement HMAC Authentication On a RESTful WCF Service

You know what would make this better and able to withstand some of the replay effect?  As part of the hashed message instead of a username based uri hash use the current date time down to the minute level.  The server could then take the same time hash and the token hash would only be good for that one minute. Constant change....

Tuesday, March 02, 2010 4:50 PM by Greg Hawk

# re: How To: Implement HMAC Authentication On a RESTful WCF Service

Thanks for this great article. But, given that the user has his secret key somehow (maybe without even knowing, i.e. after login to a secured website (usr_name,pw)), how does the secret key get to the request?

Thank you.

Sunday, March 14, 2010 6:53 PM by neward

# re: How To: Implement HMAC Authentication On a RESTful WCF Service

Hi,

could you please provide a sameple code and the way to use it with jquery jsonP to consume the service

Thursday, January 05, 2012 7:04 AM by nam vo

# Clarification on HMAC authentication with WCF | PHP Developer Resource

Pingback from  Clarification on HMAC authentication with WCF | PHP Developer Resource

# re: How To: Implement HMAC Authentication On a RESTful WCF Service

Thanks for the instructive post.  One suggestion: For security and privacy reasons, it's not a good idea to include the username (or any any sensitive information) in the URI. URIs are cached in the browser, recorded in web server logs and proxy logs. Hackers love to get their hands on usernames to bruteforce the password.

I know this is toy example. Even then, developers should be discouraged from such practice.

Thursday, August 16, 2012 9:25 PM by Dard

Leave a Comment

(required) 
(required) 
(optional)
(required) 

Enter the numbers above: