Integrating Freshbooks API into an Expo App

expo freshbooks oauth react native software

So recently, I've been working on an Internal BiteSite staff application. Besides being useful to our team, it is also a testing ground for us to up our mobile development game.

One of the things we do at BiteSite is track our billable time using Freshbooks and to keep ourselves on track, we have weekly targets that we like to hit. It's not a strict thing - but a good way for us to gauge how we're doing.

With that, I wanted to create a feature in the app where we could easily see how close to our target we are. To achieve this, I had to integrate into the Freshbooks API. Although each piece wasn't too hard - there were a LOT of steps to take care of to make this work.

This article will outline all the steps I took to achieve this. This implementation is for an Expo App using Expo's AppAuth - but I'm sure this can easily be adapted for a bare React Native app using the regular React Native AppAuth.

Alright here we go.

TL;DR

If you just want to see the code, hit up the Gist here.

Before we begin

Before we begin, I want to mention that we will be using a library called expo-app-auth. Expo AppAuth is based on the React Native AppAuth library which itself is based on AppAuth libraries for iOS and Android. In this article, when we talk about AppAuth, we're talking about the Expo version - but a lot I'm sure applies to all of these libraries.

Pre-coding setup

So before you start coding your integration, you need to actually create a Freshbooks app. You can find the full instructions here, but basically, you login to your Freshbooks account and hit up the developer page. Once there you just create an app.

At that point, you should have Client ID and a Client Secret.

Before we move on, you should add one more thing. Click on 'Edit Application', and add a redirect URI. I used the value 'exp://127.0.0.1:19000/--/auth/redirect'. The URL will have to be a Deep Link (which you can generate using Linking.makeUrl.

Update: So to get the redirect URI to work with standalone apps, you have to do a little bit of manual URI generation. As of the writing of this article, Linking.markUrl will produce a triple slash URI. For example, if you set your scheme in your app.json to bitesite, and you call Linking.markUrl('/auth/redirect'), you'll end up with bitesite:///auth/redirect. So while the above exp:// URI will work when using the Expo Go app, it won't work when you deploy to a standalone app. So you'd think that all you have to do is add the bitesite:/// triple slash URL to the Freshbooks App. Unfortunately, Freshbooks doesn't consider this a valid URL at this point. As such, you'll have to manually code in the redirect URI to be only double-slash and then add bitesite://auth/redirect to the Freshbooks App. See the code below to see how I do it using app.json.

Ok, so your setup is complete - you'll have a Client ID, a Client Secret, and a Redirect URI.

Where to store your Client ID and Client Secret?

This is probably the most annoying part of this article if you're looking to implement something quick. Unfortunately doing something quick will ultimately be insecure.

Alright, so one of the issues with coding Expo apps is your effectively coding a Javascript bundle that can easily be exposed. As such, the authors of AppAuth recommend that you do NOT hard code your Client ID and Client secret into your Javascript. Sure you can test it out this way, but you shouldn't be storing in the code.

As such, we opted to store this in our own Rails app in the Database.

The Expo app that we're coding in this article will talk to 2 servers:

  1. The BiteSite web app
  2. Freshbooks

If you're cooding this for yourself, you will have to find your own way to store the Client ID and Secret (or at minimum the secret) somehwere that's not your code.

Our BiteSite web app's API only allows authorized users to then fetch the Client ID and Client Secret.

Building the Freshbooks API Client Object

When I started working on this, I really wanted to have a Javscript object that handled the complexity of authenticating against the Freshbooks API and making all the calls. When it comes down to it, this object can really be boiled down to a few basic functions

  • Authenticate the user against Freshbooks and store the resulting OAuth Token
  • Check if the user has an OAuth Token
  • Refresh the OAuth Token if necessary
  • Make Freshbooks API Calls using the OAuth Token

So let's create a basic object and add all the imports we'll need:

/* freshbooks_api_client.js */
import axios from 'axios';
import Constants from 'expo-constants';
import * as AppAuth from 'expo-app-auth';
import * as Linking from 'expo-linking';
import * as SecureStore from 'expo-secure-store';
import moment from 'moment';
import apiClient from './api_client';
import appSettings from '../../app.json';

const FRESHBOOKS_AUTH_KEY = 'freshbooks_auth';

const freshbooks_api_client = {
  /* We'll build our code here */
};

export default freshbooks_api_client;

Let's look at each of these in detail.

Log the user in to Freshbooks

So the very first step is we need to get the user to sign in. So to do this, we're going to use the expo-app-auth library which you can see is imported as AppAuth above.

Now, since a LOT of the code to get a new token and to refresh a token is very similar, I created an internal function. For lack of a better name, I called it internalAuthorize.

/* freshbooks_api_clients.js */
...

const FRESHBOOKS_AUTH_KEY = 'freshbooks_auth';

function internalAuthorize(refreshToken) {

  const internalAuthorizePromise = new Promise((resolve, reject) => {
    let authState;

    apiClient.get('/settings')
      .then(({ data: settings }) => {

        let freshbooksApiClientId;
        let freshbooksApiClientSecret;

        settings.forEach((setting) => {
          if(setting.name === 'freshbooks_api_client_id') {
            freshbooksApiClientId = setting.value;
          }

          if(setting.name === 'freshbooks_api_client_secret') {
            freshbooksApiClientSecret = setting.value;
          }
        });

        let redirectUri = Linking.makeUrl('/auth/redirect');

        if(Constants.appOwnership === 'standalone') {
          redirectUri = `${appSettings.expo.scheme}://auth/redirect`;
        }

        const config = {
          serviceConfiguration: {
            authorizationEndpoint: `https://auth.freshbooks.com/service/auth/oauth/authorize?response_type=code`,
            tokenEndpoint: 'https://api.freshbooks.com/auth/oauth/token'
          },
          redirectUrl: redirectUri,
          clientId: freshbooksApiClientId,
          clientSecret: freshbooksApiClientSecret,
          scopes: [],
        };

        if(refreshToken) {
          console.log("Attempt to refresh token...");
          return AppAuth.refreshAsync(config, refreshToken);
        } else {
          return AppAuth.authAsync(config);
        }
      })
      .then((returnedAuthState) => {
        authState = returnedAuthState;
        return SecureStore.setItemAsync(FRESHBOOKS_AUTH_KEY, JSON.stringify(authState));
      })
      .then(() => {
        resolve(authState);
      })
      .catch(() => {
        SecureStore.deleteItemAsync(FRESHBOOKS_AUTH_KEY).then(() => {
          resolve();
        });
      });
  });

  return internalAuthorizePromise;

}

const freshbooks_api_client = {
  ...
};

export default freshbooks_api_client;

This function returns a promise that basically resolves with an authState object if the authorization is successful and with nothing (undefined) if it's not successful.

The first 10 lines or so are just a get request to get the Client ID and Client Secret that is stored on the BiteSite app. apiClient is an axios api client for the BiteSite web app. So this is where your code will differ.

Once you have the Client ID and Client Secret though, you should be good to go.

Because Freshbooks doesn't use standard OAuth Provider data, we have to custom configure the AppAuth.authorize request. This is one of the keys that's hard to find in other places. Take note of the URL, the Client ID, the Client Secret, and the Redirect URI. Again the Redirect URI might not ultimately matter. Particularly the URL - do NOT use the Authentication URL from Freshbooks. AppAuth automatically constructs a URL using the values you pass into the config object so follow the code closely above.

With this configuration, you'll properly hit the Freshbooks Authentication URL.

What's great, is everything that follows is handled by the AppAuth library. All you have to do is call AppAuth.authAsync(config) and AppAuth will properly bring up a Browser window for the user to login into Freshbooks. If the user successfully logs in, the call to AppAuth.authAsync(config) returns a promise that resolves with the authState object which contains all the token information you need to make Freshbooks calls.

So once we have that, we simply store in using SecureStore.setItem.

Now that the token is stored, we are ready to use it to make Freshbooks API calls.

Now this internal function is used both in authorization and refreshing tokens. I just control this with a true/false parameter. So if we want to authorize, we just call internalAuthorize() and if we want to refresh, we call internalAuthorize(true).

To expose the authorization piece to the public, I wrap this call in a function:

/* freshbooks_api_clients.js */
...

function internalAuthorize(refresh) {
  ...
}

const freshbooks_api_client = {
  authorize: function() {

    const authorizePromise = new Promise((resolve, reject) => {

      internalAuthorize().then((authState) => {
        if(authState) {        
          resolve(true);
        } else {
          resolve(false);
        }
      });

    });

    return authorizePromise;
  },
};

export default freshbooks_api_client;

And with that, if somebody wants to authorize, they can just do this:

/* AnotherFile.js */
import freshbooks_api_client from '../path/to/freshbooks_api_client';

freshbooks_api_client.authorize().then((authorized) => {
  if(authorized) {
    console.log("Authorization succeeded!")
  } else {
    console.log("Something went wrong.");
  }
});

Check user's authorization state

Ok now that we logged the user in, throughout the rest of the app, it would probably be nice to check whether or not the user is 'logged into Freshbooks'. Really what that is is just checking whether or not we have a stored token.

What you should know is that the authState that came back when we authenticated is not just a string. It actually contains a lot of information including the token itself, its expiry date, etc. That's why when we originally stored it, we converted it (serialized it) into a string using JSON.stringify:

  ...
  return SecureStore.setItemAsync(FRESHBOOKS_AUTH_KEY, JSON.stringify(authState));
  ...

So if we want to check the if the user is logged in, and use the accessToken, it might be nice to have a little function that extracts out the serialized object, and deserializes it back into an object. So to do that, I created another internal function:

/* freshbooks_api_client.js */
...
function internalAuthorize(refresh) {
  ...
}

function getFreshbooksAuth() {
  const getFreshbooksAuthPromise = new Promise((resolve, reject) => {
    SecureStore.getItemAsync(FRESHBOOKS_AUTH_KEY)
    .then((data) => {
      if(data) {
        const freshbooksAuthObject = JSON.parse(data);
        resolve(freshbooksAuthObject);
      } else {
        reject('Could not find freshbooks auth object');
      }
    })
    .catch((error) => {
      reject(error);
    })
  });

  return getFreshbooksAuthPromise;
};

const freshbooks_api_client = {
  ...
};

export default freshbooks_api_client;

This internal function getFreshbooksAuth simply goes into our SecureStore, grabs the value, parses it into a Javascript object and returns a promise that resolves with the object.

The object that is resolved is basically the original authState object we got when we authenticated against Freshbooks.

Now we just need to this to the outside world. If the token exists, we're considered authorized.

/* freshbooks_api_client */
...
const freshbooks_api_client = {
  ...
  checkAuthorization: function() {
    const checkAuthorizationPromise = new Promise((resolve) => {
      getFreshbooksAuth()
      .then((freshbooksAuthObject) => {
        console.log("Freshbooks token active.");
        resolve(true);
      })
      .catch((error) => {
        resolve(false);
      });
    });

    return checkAuthorizationPromise;
  }
}

Refresh the token

Now that we have a way to check if we're authorized, we should be clear to make any API calls. Not exactly. Above, when I said "If the token exists, we're considered authorized" - that wasn't entirely true.

The think about OAuth tokens is that the expire - and actually - they expire pretty quickly. In some cases within 24 hours. Luckily, it's pretty easy to refresh the token.

So we need to modify our checkAuthorization slightly if we have a stored token but it's expired, we just need to refresh it.

/* freshbooks_api_client */
...
const freshbooks_api_client = {
  ...
  checkAuthorization: function() {
    const checkAuthorizationPromise = new Promise((resolve) => {
      getFreshbooksAuth()
      .then((freshbooksAuthObject) => {

        if(moment().isAfter(freshbooksAuthObject.accessTokenExpirationDate)) {

          internalAuthorize(freshbooksAuthObject.refreshToken).then((authState) => {
            if(authState) {
              console.log("Freshbooks Token refreshed.");
              resolve(true);
            } else {
              console.log("No Freshbooks Token found.");
              resolve(false);
            }
          });

        } else {
          console.log("Freshbooks token active.");
          resolve(true);
        }
      })
      .catch((error) => {
        resolve(false);
      });
    });

    return checkAuthorizationPromise;
  }
}

Notice that we're using our internalAuthorize that we coded before. Again - logging in and refreshing the token uses very similar code. Once the authState comes back from the refresh, we just store it the exact same way we stored the original authState.

So now, when we call checkAuthorization, if we're not logged in, we get a 'false' response, if we're logged in, we get a 'true' response, and if we need a refresh, the refresh happens and we get a 'true' response.

With this in place, any file can now easily check authorization status:

/* AnotherFile.js */
import freshbooks_api_client from '../path/to/freshbooks_api_client';

freshbooks_api_client.checkAuthorization().then((authorized) => {
  if(authorized) {
    console.log("Authorized!")
  } else {
    console.log("Something went wrong.");
  }
});

Make Freshbooks API Calls

Alright, now we're finally ready to make calls to the Freshbooks API. Making calls to the Freshbooks API is actually pretty simple - you just have to pass in a special string as the authorization header for your request.

The string looks like this:

"Bearer YOUR_OAUTH_API_TOKEN"

Since we have the API Token, this is actually pretty simple. Remember that we already have the internal getFreshbooksAuth function to grab the authState object. This object has a property called accessToken that we can use.

So we just set up an Axios instance with the proper header to make the API call. For those that are not familiar with Axios and use things like jQuery, think of Axios as another way to execute something like $.ajax.

So to put this together, I created a get function on our exported object:

/* freshbooks_api_client.js */
...

const freshbooks_api_client = {
  ...
  get: function(url) {

    const getPromise = new Promise((resolve, reject) => {
      getFreshbooksAuth()
      .then((freshbooksAuthObject) => {
        const internalFreshbooksApiClient = axios.create({
          baseURL: `https://api.freshbooks.com/`,
          headers: {
            'Accept': 'application/json',
            'Api-version': 'alpha',
            'Authorization': `Bearer ${freshbooksAuthObject.accessToken}`
          },
        });

        internalFreshbooksApiClient.get(url).then((response) => {
          resolve(response);
        })
        .catch(() => {
          reject();
        });
      });
    });

    return getPromise;


  }

With that final piece, we can now make calls from other files easily:

import freshbooks_api_client from '../path/to/freshbooks_api_client';

freshbooks_api_client.checkAuthorization().then((authorized) => {
  if(authorized) {

    freshbooks_api_client.get('/auth/api/v1/users/me').then((response) => {
      console.log(response.data.response);
    }
  } else {
    console.log("Something went wrong.");
  }
});

Conclusion

So with that, we now have a nicely encapsulated library with a pretty simple API:

freshbooks_api_client.checkAuthorization();
freshbooks_api_client.authorize();
freshbooks_api_client.get('/freshbooks/api/end_point');

I'm sure there is a lot of cleanup that can be done, but for now this is working and I'm pretty happy with it. Hope this helps others as well!

If you want to see the source code for our Mobile App, check it out here.

And as that will always be evolving, you can check out the Gist here.

Casey Li
CEO & Founder, BiteSite

Starting Small with Software

software software development business

You've seen all the success of large software companies such as Facebook, Amazon, Netflix, or anything else and thought, “I should do that”. So you come up with an idea and want to get started right away.

It goes without saying, investing in software is a safe bet for the future but jumping in without thinking is a sure way to overwhelm yourself. Your idea is great but let’s scale it back to a more manageable size. This allows you to not get overwhelmed with complexity and cost right away.

Approaching a company like BiteSite is a perfect way to ensure the project is the right size. At BiteSite we love to work with projects that start small and see where they end up.

What is a small project?

A small project to us is anything with a deadline of 1-3 months, and has a small set of concrete short term goals. At the end of the deadline the client will have a minimum viable product (MVP) that can be used by end users. This MVP will be able to be improved upon in the future once feedback is gathered.

Client

Starting small is advantageous to everyone involved, especially the client. Most of the advantages stem from not locking into a large project.

When a client decides to work with a custom software shop it is important that the company's culture works well with the client’s. This is mainly because the client is part of the development process. A small project does not lock a client into the company for a long period of time. This allows the client to feel out if they want to continue past the initial MVP.

Often in software development, the initial requirements do not turn out to be the final product. This is where it is dangerous to create large scale projects based off of fixed initial requirements. Short term contracts combat this by helping clients not lock into a product they end up not needing. Hence why we advocate for having a small set of concrete short term goals rather than immovable long term requirements. The short term goals can be added upon after users have tested the MVP and feedback is available as to what will be used.

Overall, the client will save time, effort, and money by choosing a small project and developing off of it in the future. The initial large scale idea will eventually be reached just in smaller increments.

BiteSite

Now you know why small projects are advantageous for customers, you might be wondering why BiteSite would ever advocate for a small project. As everyone knows, larger projects mean less uncertainty and more interesting work right?

Most of the time yes, but not always.

Larger projects are eventually what we try to work towards with all our clients but it’s rarely a good idea in software development to start with a large set of requirements and work towards a goal far in the future. Many requirements will not be known until the software evolves into something that can be tested by the end users.

The first reason to have smaller projects with new customers is to feel out what the end users will use. Small starting projects help us understand the clients and end users needs before anything large has been committed. This minimizes the time wasted creating software that is not needed. We can create an MVP to quickly see what features should be expanded upon and what ones will be dropped.

The next reason to start small is to show the client our competence and convince them their time, money, and effort spent on the project will be worth it. Diving into a large project doesn’t create trust as quick as a smaller project does since the finished product is so far in advance.

Larger projects having more interesting work is normally due to the complexity required. With custom software each project is unique so naturally it creates interesting work our developers possibly haven’t seen before. If the client continues the small project the complexity of the project will increase as well.

Hopefully you see how advantageous starting small can be.

Big projects can be intimidating but starting small will reduce the chance of being overwhelmed. The big idea is always there but chunking it off into smaller pieces is a better approach for everyone involved. There is nothing wrong with starting small. Afterall, Rome wasn’t built in a day.

(photo by Engin Akyurt from Pexels)

Chris Francis
Software Developer, BiteSite

Software developers of the world, thank you.

software software development business

Something dawned on me the other day - something pretty amazing.

So I've been a Software Engineer, Developer - whatever you want to call it, a good chunk of my life. My dad introduced me to coding back in the early 90s, I studied Software Engineering in University, and now I run my own Custom Software shop. So software development has been a big part of my life and I would say I'm pretty proud of where I am.

However, something dawned on me the other day. I've really been standing on the shoulder of giants and what's crazy is - so many of us have.

Let me explain.

If you don't develop software, what you should understand, is that software is built in layers of abstraction. As a developer, you don't need to know how electrons zip back and forth on a computer motherboard because someone built a layer on top of that that abstracts the details away from you. As a developer, you don't need to understand how certain low-level operating system operations work because someone built a layer on top of that.

But it doesn't stop there.

Let's take Ruby on Rails for example. This is my web framework of choice. Because of Ruby on Rails, I don't need to understand when someone types in a URL in their browser, how that sends a request to my code with all the information I need to build a web application. I just use that information and I'm on my way.

But it doesn't stop there.

Many other developers have developed libraries that makes it infinitely easier to send fancy email notifications, create realtime web applications, render fancy charts and more.

And you know what, it doesn't stop there.

This is the thing - as a software developer, you leverage other people's work so much. You use frameworks, libraries, code snippets - you name it. Let's take a closer look.

As an informal, non-scientific experiment, I decided to pick one of BiteSite's biggest projects and I wanted to see how much code was ours and how much wasn't. Remember, this is pretty informal, but it should give you an idea

  • Our code (~4MB)
  • Library & framework code (~527 MB)

(for the technical readers, I took the size of our Ruby on Rails app folder for "Our code" and took the "node_modules" and "gems" folders for "Library and framework code")

That's 0.75% for our code! 99.25% of the code that's being used was written by other developers.

Now this is not super scientific, and granted we're not using every bit of the framework - but it's just a small indication of how much code we're using that we didn't create ourselves. I would also say that this is probably quite typical of most applications.

Aside from the exact percentages, which only represent the code that was written - there's so much research and development that went into creating these libraries and frameworks. Not to mention that a lot of these libraries and frameworks contain the work of some of the best engineers in the world. Think about that.

I can't count the number of times a client has come to me and said "Can you do this?", I say "Probably", Google it and within minutes have an amazing solution because some developer out there coded an amazing library.

And it doesn't stop there.

The benefit goes way beyond making a software developer's life easy or making them look impressive. Because of these libraries and frameworks, the developers using them are able to build incredible apps that are the foundation for amazing businesses and companies that change the world.

And here's the kicker. They do it for free.

That's what really blew my mind as I thought about this more and more. A lot of massive, super successful companies that change the world build their software, their company, their success using free frameworks and libraries.

We have all this amazing technology at our disposal because a huge number of software developers love developing amazing technology, putting it out there, and ask nothing in return.

Don't get me wrong. I know there is sometimes a bigger business motive for some of these libraries and frameworks, but for the most part, I think software developers have developed such an incredible culture of sharing and collaborating for the greater good. I can't think of another industry that does it so well.

I've taken it for granted for so long that so much of what I do and so much of what the tech industry does is based on (and forgive the phrase) the kindness of strangers - the kindness of developers who love their craft and want to share it with the world.

So with that - I think it's time we all thank this incredible group of people who continue to provide answers to our problems, who allow us to build amazing world-changing products and businesses, and who ask for nothing in return.

Thank you to all the incredible software developers out there who have allowed me and so many others to get to where we are.

Casey Li
CEO & Founder, BiteSite

Ruby on Rails QuickTip: Wrapping simple chunks of code in a method to give it meaning

ruby ruby on rails software

Let's say you have a model of a User. Let's say that if the user's birth year is 1990 or before, then you can't edit that user's first name. So you might have something like this:

# User Class
class User < ApplicationRecord
end

and you might have something like this

# User controller
class UsersController < ApplicationController
  def edit
    if user.birth_year <= 1990
       # don't allow editing of first name
    end
end

However, I would argue, that even though it's a really simply check, that you should put this in its own method:

# User Class
class User < ApplicationRecord
  def first_name_locked?
    birth_year <= 1990
  end
end
# User controller
class UsersController < ApplicationController
  def edit
    if user.first_name_locked?
       # don't allow editing of first name
    end
end

Very simple implementation and achieves the same result, but here are some advantages

  • The condition is now given meaning so that developers who are using it can understand its intent
  • Any developer reading any code that uses that method will understand more easily what's happening
  • If the business logic changes for locking the first name, you'll have to edit only one piece of code

So even the simplest pieces of code can benefit from wrapping them in a method.

Casey Li
CEO & Founder, BiteSite

Ruby on Rails QuickTip: Adding Parameters to your Methods Safely

ruby ruby on rails software

This is going to be a quick post and applies to a lot of different languages, but it's something we've been doing in our Rails projects a decent amount.

If you're ever working on a larger codebase and you decide you want to add a parameter to a method, but are afraid to do so because it might break code elsewhere, consider simply adding a default value.

Take the following setup for example

class MyModel < ApplicationRecord
  def my_method(user_id)
    user = User.find user_id
    user
  end
end

Now, let's say you want to modify this method so you can add another parameter. Here's a safe way you can do it:

class MyModel < ApplicationRecord
  def my_method(user_id, new_parameter=nil)
    user = User.find user_id
    if new_parameter
      do something
    end
    user
  end
end

Now that new parameter's default value could have been anything, but the key here:

  1. Have a default value so that any code calling this method with the original parameters will still work
  2. When the new parameter is not passed in, have the method behave the exact same way it did before

With these two principles you'll be able to extend your code without breaking any existing code.

Casey Li
CEO & Founder, BiteSite