API Authentication with Taffy

Aug. 27, 2013
API Coldfusion


Please Note:
This post appears to be over 6 months old.

Recently I have been working with Taffy to create a simply REST API. The API is used by a native mobile application on the iPhone. When it was complete I needed a simply way to authentication the application talking to the API.

This is not something I was familiar with at all. I looked into lots of different methods before I started. I really did not want, nor have the experience to reinvent the wheel, so I look at current methodologies around the web.

My first attempt was BASIC authentication, but this did not feel right. For reasons I won't go into here, the API was not over HTTPS, anyone could sniff out the password. Bad, very bad!

I found a really good post by Greg Moser on AJAX Authentication with Taffy REST API. He talks about using sessions as an API key. This is a good idea, but as my application is mobile, not really applicable for my situation. However his post did get me thinking and was very helpful.

I didn't need the complexity of OAuth. I found that I liked the way Amazon secures their API. So I looked more into this approach.

I needed a simple "half OAuth" approach. Mainly without the user having to approve. This is how I got my head around it all and what I ended up with, code and explanation below.

Taffy's application.cfc

 //this function is called after the request has been parsed
  	function onTaffyRequest(string verb, string cfc, struct requestArguments, 
                                                string mimeExt, 
                                                struct requestHeaders){			 
  		return authenticationService.authenticateReuqest(verb=verb, cfc=cfc, 
                             requestArguments=requestArguments, requestHeaders=requestHeaders);
  	}
  

authenticationService.cfc

component persistent="false" accessors="true" output="false" {
  
  	property name="fw" type="any";
  	property name="apiUsersDAO" type="any";
  
  	public any function authenticateReuqest(required string verb,required string cfc,
  	required struct requestArguments,required struct requestHeaders) {
  	
  		if(!checkRequiredArguments(arguments.requestArguments)) {
  			return createAuthenticationRequiredMessage("Authentication Required: Missing request arguments.");
  		}
  	
  		if(!havePrivateKey(arguments.requestArguments.publicKey)) {
  			return createAuthenticationRequiredMessage("Authentication Required: No API user by the key.");
  		}
  	  
  		if(!timeInAcceptableBounds(arguments.requestArguments.timestamp)) {
  			return createAuthenticationRequiredMessage("Authentication: Request timeout");
  		}
  		
  		/*
  		 Since I already determined this timestamp was within acceptable bounds to be accepted,
  		I still use it within my own hash calculation to make sure it was the same timestamp sent 
  		from the client originally in their sign, and not a made-up timestamp 
  		from a man-in-the-middle attack.
  		*/
  	
  		if(!compareSignature(theirSign=arguments.requestArguments.signature,
  		    areSign=EncryptSignature(argValue=createSignString(arguments.requestArguments),
  		    publicKey=arguments.requestArguments.publicKey))) {
  			return createAuthenticationRequiredMessage("Signature is bad, get lost.");
  		}
  		
  		return true;
  	}
  	
  	public any function timeInAcceptableBounds(required any timestamp) 
  	hint="I check the request was send within acceptable bounds." {
  		local.dif=DateDiff("s",arguments.timestamp,now());
  		if(local.dif gt 20) {
  			return false;
  		} else {
  		
  			return true;
  		}
  	}
  	
  	public any function compareSignature(required any theirSign,required any areSign) 
  	hint="I compare the two signatures." {
  		/* 	I don't think trim is needed, I just don't know or care it's 
  		8pm and I am still in the office! */
  		if(trim(arguments.theirSign) eq trim(arguments.areSign)) {
  			return true;
  		} else {
  			return false;
  		}
  	}
  	
  	public any function EncryptSignature(required string argValue,required string publicKey) 
  	hint="I create my own signature that I will match with the clients" {
  		// Why can CF not do this? 
  		var jMsg=JavaCast("string",arguments.argValue).getBytes("iso-8859-1");
  		var jKey=JavaCast("string",getapiUsersDAO().getSecretKey(arguments.publicKey)).getBytes("iso-8859-1");
  		var key=createObject("java","javax.crypto.spec.SecretKeySpec");
  		var mac=createObject("java","javax.crypto.Mac");
  		key=key.init(jKey,"HmacSHA1");
  		mac=mac.getInstance(key.getAlgorithm());
  		mac.init(key);
  		mac.update(jMsg);
  		return lCase(binaryEncode(mac.doFinal(),'Hex'));
  	}
  	
  	public string function createSignString(required struct requestArguments) 
  	hint="I create the string for my signature." {
  		var local.returnString = arguments.requestArguments.timestamp & arguments.requestArguments.publicKey;		
  		return local.returnString;
  	}
  	
  	public any function havePrivateKey(required string publicKey) 
  	hint="I check API User exisits and has a key." {
  		return getapiUsersDAO().hasPrivateKey(arguments.publicKey);
  	}
  	
  	public any function checkRequiredArguments(required requestArguments) 
  	hint="I check we have the required arguments to authenticate this request." {
  		if(structkeyexists(arguments.requestArguments,"publicKey") 
  		AND structkeyexists(arguments.requestArguments,"signature") 
  		AND structkeyexists(arguments.requestArguments,"timestamp")) {
  			return true;
  		}
  		return false;
  	}
  	
  	public any function createAuthenticationRequiredMessage(string message) {
  		local.bodyContent=structnew();
  		local.reponseObject=createObject("component","site.api.representations.myRepresentation");
  		bodycontent.msg=arguments.message;
  		return reponseObject.setData("").withMessagesAndErrors(local.bodyContent);
  	}
  	
  	
  	
  
  	
  }
  
  

Basically the principle is as follows...

1) My server and the mobile client have a public and private key. Only the server and mobile client know the private key. The private key is never transmitted, very important! The public key is only used as a means to identify who is speaking to the API. I can identify who is making the call so I know which private key to use.

2) The mobile client creates a unique HMAC (hash) representing it's request to the server by combining the values of the parameters being passed. The client also adds a timestamp to the hash.

3)The client then sends the HASH value to the server, along with all the arguments and values it was going to send anyway. In my example code above the hash value is just another parameter, but you could add this to the request header. If you do tho please remember base64, the fun I had!

4) Now the server receives the request and looks at the timestamp (the non-hashed version) to decide if this is an "old" request. The timestamp was also included into the HMAC generation (effectively stamping a created-on time on the hash) in addition to being checked "within acceptable bounds" on the server.

5) The server re-generates its own unique HMAC (hash) based on the submitted values that were not hashed (including the non-hashed version timestamp) using the same methods the mobile client used with their own private key.

6) The server then compares the two HMACs values. If these are equal, then the server trusts the client, and runs the request.

That's it. If the request has been changed in anyway by a 3rd parity for example the the non-hashed version timestamp, then the signature would not match and it would be rejected.

Every time a request is generated by the mobile application it has XX of seconds to use it.

A middle man could change the time in the URL, but it would make no difference. This is because it would be rejected as the signatures would no longer match. I used the timestamp within my own hash calculation. This makes sure it was the same timestamp sent from the client originally in their sign, and not a made-up one from a man-in-the-middle.

The API can still be compromised if someone gets the private key by decompile the application. However, I now have more control over how the keys are used. I could reissue a new private key and limit the old one until the developer has found a new way to secure the application.

Nothing I have done is different, but this concept did take a while for me get my head around. This was something I have never looked into nor had any experience with before. I am sure I will need full oAuth down the line, for now this seems to work well.

I would be very interested in the pros and cons of the method I went with from people who have more experience securing an API. My requirements were not the norm so given my scenario I personally think the approach was acceptable.

That's all Folks!

Thanks for reading. Let's keep in Touch:
Follow me on GitHub or Twitter @glynjackson


Glyn Jackson is a Python Nerd, consultant and developer.


Find out more