Episode #221

Easy Auth - Part 2

13 minutes
Published on May 19, 2016

This video is only available to subscribers. Get access to this video and 586 others.

This time we take a look at how to improve security by not transmitting the user's auth token directly. Instead, we'll leverage HMAC SHA1 hashing with the provided client id. Doing this makes the response not directly useful. The client needs to use the client id and the agreed upon hashing algorithm to arrive at the common auth token.

Episode Links

  • Source Code
  • HMAC - a good overview of HMAC on Wikipedia if you like reading about cryptography.

Updating the Server to HMAC-SHA1 Hash the Auth Data

First, we create a method to hash the data with a key:

require 'openssl'

def hmac_digest(key, data)
  digest = OpenSSL::Digest.new('sha1')
  OpenSSL::HMAC.hexdigest(digest, key, data)
end

Then we'll take the generated token as data instead of the actual auth token. The actual auth token will be generated as a result of the hash, using the client id as the key:

post '/activate' do

      # ...

      auth_token_data = SecureRandom.hex(24)
      client_id = json["client_id"]
      auth_token = hmac_digest(client_id, auth_token_data)

      # this is the generated auth token. we can use this to compare with
      # what the client generates
      puts ">" * 20
      puts auth_token
      puts ">" * 20

      # save the auth token data instead
      json['auth_token_data'] = auth_token_data


      # ...
end

We'll also stop sending the client_id value back to the client when requested, since this is being used as the key. Now we'll just return the status and the auth_token_data:

get '/easy_auth/:token' do |token|
  key = "auth_req:#{token}"
  if data = redis.get(key)
    json = JSON.parse(data)
    response = {
      "status" => json["status"]
    }
    response[:auth_token_data] = json["auth_token_data"] if json["auth_token_data"]
    return response.to_json
  else
    halt 404, 'token not valid or expired'
  end
end

Once that's working we can follow the activate workflow using the provided Paw document and our browser.

Verifying on the Client

Our iOS client needs to perform the same operation to arrive at the same value.

We'll start by adding a dummy objective-c file that will trigger Xcode to create a bridging header for us. In the bridging header, we'll import CommonCrypto to get access to the HMAC functions:

#import <CommonCrypto/CommonHMAC.h>

Then, in our swift file, we'll do the same HMAC-SHA1 process:

import Foundation

let algorithm = CCHmacAlgorithm(kCCHmacAlgSHA1)

let digestLength = Int(CC_SHA1_DIGEST_LENGTH)
let buffer = UnsafeMutablePointer<UInt8>.alloc(digestLength)

let data = "558e79f580baf895e4f1d002071a87604bb9ed7d4f780ecd"
let key = "abcd1234"

CCHmac(algorithm, key,
       key.lengthOfBytesUsingEncoding(NSUTF8StringEncoding),
       data,
       data.lengthOfBytesUsingEncoding(NSUTF8StringEncoding),
       buffer)

var str = ""
for i in 0..<digestLength {
   let char = buffer[i]
    str = str.stringByAppendingFormat("%x", char)
}

print("Result: \(str)")

Here I've hard coded the values that I received from the API, then I check the result to see if they match, and they do!

This episode uses Tvos 9.0.