OAuth Support for FreshBooks and the Gory Technical Details

Written by Michael on June 1, 2010

Posted in Formstack Updates

A new feature we recently introduced was OAuth authentication with our FreshBooks integration. What is OAuth? Glad you asked!

OAuth solves the problem of how to share information between sites without giving your password from one site to another.

We wanted to add OAuth support to our FreshBooks integration to give our users a better, more secure experience when they link their FreshBooks account with our service. Instead of having our users enter their API key or password directly into our application, they now grant our service access to their FreshBooks account, entering their login information on the FreshBooks server (see image). This gives our users a better experience when setting up the service and keeps them in control of 3rd party access!

Formstack requesting access to your FreshBooks account

Formstack requesting access to your FreshBooks account

If you’re wondering how to implement an OAuth client I’d like to show you; PHP examples are scarce and the current OAuth libraries leave a lot to be desired, here’s how we implemented the FreshBooks OAuth client:

Register as a consumer

The first step is to register with FreshBooks as a OAuth consumer. Once you register as a consumer, freshbooks will give you a consumer key and a consumer secret, and both will be used in your code.

Getting a request token

As a registered consumer, the first step in an OAuth request is getting a request token; we will make the initial request to the FreshBooks server. First we set some parameters that we will pass to FreshBooks, $oauth_consumer_key and $oauth_consumer_secret are the values FreshBooks gave us once we became a consumer, $callback is the URL that FreshBooks will redirect to after we request the request token. “oauth_nonce” is a random string of characters; in this case a length of 20.

$params = array(
    'oauth_consumer_key' => $oauth_consumer_key
    ,'oauth_callback' => $callback
    ,'oauth_signature' => $oauth_consumer_secret. '&'
    ,'oauth_signature_method' => 'PLAINTEXT'
    ,'oauth_version' => '1.0'
    ,'oauth_timestamp' => time()
    ,'oauth_nonce' => $prog->generateKey(20)
);

The next step is to URL encode these params and send them to FreshBooks:

// URL encode our params
$postdata ='';
foreach($params as $key => $value) {
    if(!empty($postdata)) $postdata .= '&';
    $postdata .= "{$key}=";
    $postdata .= urlencode($value);
}

// send the request to FreshBooks
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://{$site_name}.freshbooks.com/oauth/oauth_request.php");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$res= curl_exec($ch);

// parse the request
$r = array();
parse_str($res, $r);

Authorizing the request token

If all goes well, $r[‘oauth_token], $r[‘oauth_token_secret’], and $r[‘oauth_callback_confirmed’] should be set. We can now redirect to FreshBooks server to get our request token authorized by the user. We will pass the $r[‘oauth_token] to FreshBooks when we do this:

header('location: '. "https://{$site_name}.freshbooks.com/oauth/oauth_authorize.php?oauth_token={$r['oauth_token']}");

Swapping the authorized request token with an access token

Once the request token is authorized the user will be redirected to the callback URL that was specified in the initial request token. We are now ready to swap the authorized request token with an access token. The access token will give us enough information to authenticate with FreshBook’s API. Again, we set up our parameters to send to Freshbooks. oauth_token and oauth_verifier were sent to us by FreshBooks in the query string upon redirect.

$params = array(
    'oauth_consumer_key' => $oauth_consumer_key
    ,'oauth_token' => $_GET['oauth_token']
    ,'oauth_verifier' => $_GET['oauth_verifier']
    ,'oauth_signature' => $oauth_consumer_secret. '&'
    ,'oauth_signature_method' => 'PLAINTEXT'
    ,'oauth_version' => '1.0'
    ,'oauth_timestamp' => time()
    ,'oauth_nonce' => $prog->generateKey(20)
);

Once again, we URL encode our request, send it to FreshBooks server, and parse the response:

// URL encode our params
$postdata ='';
foreach($params as $key => $value) {
    if(!empty($postdata)) $postdata .= '&';
    $postdata .= "{$key}=";
    $postdata .= urlencode($value);
}

// send the request to FreshBooks
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://{$site_name}.freshbooks.com/oauth/oauth_access.php");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$res= curl_exec($ch);

// parse the request
$r = array();
parse_str($res, $r)

If everything went smoothly, $r[‘oauth_token] and $r[‘oauth_token_secret’] should be set. We now have all the pieces to make an authenticated API call.

Building the Authorization Header

All that is left is to build the Authentication header that will be sent along with the API request. Here’s how to do it:

$params = array(
    'oauth_consumer_key' => self::$oauth_consumer_key
    ,'oauth_token' => $key['oauth_token']
    ,'oauth_signature_method' => 'PLAINTEXT'
    ,'oauth_signature' => self::$oauth_consumer_secret. '&' .$key['oauth_token_secret']
    ,'oauth_timestamp' => time()
    ,'oauth_nonce' => $prog->generateKey(20)
    ,'oauth_version' => '1.0');

$auth ='Authorization: OAuth realm=""';
foreach($params as $kk => $vv)
    $auth .= ",{$kk}" . '="'. urlencode($vv) . '"';