How to Build a Custom Procore Application with Ruby on Rails

I used to work at Procore as a software engineer. Since leaving I’ve begun working more with the public API integrating custom applications. The Procore API is a powerful way for users to interact with their project and company data in ways not possible through the Procore website today. Procore is focusing heavily on being the best platform for construction professionals and supporting applications, much like Shopify does for e-commerce. Both platforms have support for third-party developers to build applications that integrate with their public APIs. To support this goal, Procore has steadily increased the availability and stability of their public APIs so that outside developers can build applications that further their mission. Procore can’t solve every problem on their own, so attracting independent developers is a huge plus for them! Digitization of the construction industry is still in the early phases and it’s especially still early for large, open platforms in the space that developers can build upon. There are a lot of opportunities for new applications in the Procore Marketplace if only you know how to build them. While Procore has a lot of great documentation, I know from experience that it can be difficult to identify the most important pieces for getting started. The goal of this blog post is to cut right to the point, showing you the most critical pieces required to create a third-party application that can make requests to the Procore API on behalf of a Procore user.

Here are some of the things I hope you get from following along with this post:

  • How to sign up for a Procore Developer account
  • How to create an application in the Procore Developer portal
  • How to create an app manifest
  • How to install your app in a sandbox account
  • What a quick Ruby on Rails application setup for this purpose looks like
  • How to utilize the procore-iframe-helpers library and OAuth to get an authorization code
  • How to exchange an OAuth authorization code for a Procore Access Token
  • How to make calls to the Procore API using an access token and the Procore Ruby SDK

How to sign up for a Procore Developer account

First thing’s first: you need to create a developer account to create applications. Head on over to developers.procore.com and click the sign-up button in the top right corner. All you need to provide is a name, email, and password. After that, you’ll receive an email with a button you need to click for verification, and then sign in to your new account. On first sign-in, you’ll be prompted to accept the terms and conditions of utilizing the Procore API. Accept the terms and you’ll be redirected to your new dashboard with a nice big “Create New App” button.

Developer Signup Page Empty Developer Dashboard

How to create your application and get a sandbox account

After you’ve signed up for and signed into your Procore developer’s account, we can start the process by creating a new application. Creating this application will also create a Sandbox account which will allow us to test our application in a live environment. If you’re unfamiliar with the term “sandbox”, it means a live testing environment where we are free to test and break things without ruining anyone’s real-life data. It takes a little bit for Procore to spin up a new sandbox account for us so it’s good to do this step before we start developing. On the developer dashboard as seen above, click the big “Create New App” button and provide a name for your new test application. It doesn’t matter what you name it now, you can always change it later.

Create Introductory App

After clicking submit, you’ll be redirected to your application’s detail page where you can manage your app including changing the name, updating the marketplace listing details, etc. At this point, Procore has begun making a sandbox account for you and you’ll need to wait until that process has finished. When it has finished, you’ll receive an email with instructions for setting a password on your new sandbox account.

New Sandbox App Email

After setting up your password and logging in, Procore will put you through their standard new user flow. You now have a sandbox account and can access it to install your app and authenticate once we’ve got it up and running.

How to create a Procore App Manifest

Procore manages application versions with something that they call a manifest. A manifest is more or less a description of all the components that your application needs to function. When you first publish a manifest, it will only work for sandbox accounts. When it’s time to launch your new application version to production you “promote” a sandbox manifest. At a minimum, we need to specify how the application will authorize with the API. If creating an embedded application, you can also include an iFrame source URL. I’ll cover making an embedded app in another post. For now, we’ll just go with the minimal manifest. Click the “Create New Version” button under the “Manage Manifests” section of your app in the Procore Developer portal.

Create New Manifest Version

Replace the code in the modal that pops up with the following JSON and click “create”.

{
  "components": {
    "oauth": {
      "instances": [
        {
          "grant_type": "authorization_code"
        }
      ]
    }
  }
}

Manifest Contents

Once you’ve created a manifest, more information will be available in the section labeled “Sandbox Account” including a Client ID, an editable box labeled Redirect URI, and a hidden Client Secret. The Client ID and Secret will go into our application so it can identify itself to the Procore API. The Redirect URI should be pre-populated with something like http://localhost. The Redirect URI is where Procore will send the authorization code and redirect the user once they’ve finished authenticating via OAuth. That means it needs to be a URL accessible from the user’s browser to your application (so it supports localhost for testing). Let’s change this to http://localhost:3000/oauth_callback and have our application listen on port 3000 when we run the server. We’ll implement this endpoint later.

Populated Sandbox Account Details

How to install a custom application in your sandbox account

There’s one other action we need to take before we can access a sandbox application in our account. Applications can only query a company’s information once they’ve been installed for the company and/or project that you want to access data from. To do this, copy the App Version Key from the “Manage Manifests” section of your developer portal, and log into your sandbox account.

App Version Key from developer portal

Once there, click the dropdown in the top right of the screen labeled “Apps”, and then click “App Management”.

Access the App Management Section

From there, click the orange button labeled “Install App” which will open a dropdown, and in that dropdown click “Install Custom App”. This will open a box where you can paste the App Version Id that you copied from the developer portal earlier.

Install Custom App Menu

Paste the App Version Key in this field and click “Install”, then confirm by clicking “Install” again.

Paste App Id to Create

After a few seconds on this same page, you should now see a new entry for your app in the table with a “View” button next to it. Congratulations! Your Procore Application is now installed, and once we get the server running on localhost, we’ll be able to authenticate into this company and make API requests.

This process of installing your application is the same process you would use to install someone else’s custom app that wasn’t added to the Procore Marketplace.

How to get a minimal Ruby on Rails application making Procore API Requests

We can finally start the part developers love: building an application. For this example, I’m going to build with Rails which will make it possible for us to use some very convenient tools developed by Procore employees to make this process go faster. I’ll get to those tools later but for now, let’s spin up a Rails 6 application. For this portion, ensure you have both ruby 2.6.3 installed as well as NodeJS 14.16.0 either directly or via your favorite version manager like RVM or RBenv for ruby or nvm for Node. Also, ensure you have the bundler gem installed. This tutorial assumes you’re using a Unix-based operating system for command-line commands.

First, from the terminal create a new directory, change into it, and create a new Gemfile with bundler:

mkdir example-procore-app && cd example-procore-app
bundle init

Open your new Gemfile and add the following line:

gem 'rails', '~> 6.1', '>= 6.1.3.2'

Close the file and run bundle install. This will likely take a few minutes as Rails has a lot of dependencies to install. Then create a new rails app in the current directory. It will ask if you want to overwrite the Gemfile, enter “Y” for yes, and hit enter.

rails new .

This will take a few minutes again as it will install new dependencies both on the ruby side and the node side of things. Let’s run a smoke test by starting the server with bin/rails s and visiting localhost:3000 in your browser of choice. You should see the following screen.

Rails smoke test

Great! We’ve got our most basic scaffolding set up and now we get to the real fun. If you ran into issues getting to this point, email me and let’s figure out what’s missing: mike@strukchur.com.

Authenticating with Procore via OAuth and making API Requests

To make calls to the Procore API on behalf of a user, we need to obtain an access token using the OAuth 2.0 Protocol. For more details on how this works in the context of Procore apps, see the developer documentation. This access token will verify that a user has given our app permission to make queries on their behalf. It will permit us to see all the data that user can see and take any actions they have permission to take.

To enable an authorization code grant type OAuth flow for our application, we need a few things:

  1. A page for users to begin the authentication flow using an iFrame
  2. Our Procore Application Client credentials (from the developer portal)
  3. An endpoint where Procore can send the authorization code after the user authenticates with their Procore account
  4. A way to exchange the authorization code we receive for an access token which will allow us to make queries
  5. Someplace to store the access token we receive so that we can make API requests with it later
  6. A page to redirect newly authenticated users

Let’s start by setting up an authentication page. To do this we need a new controller with an index action and a corresponding view with an authenticate button and some helper javascript. For the helper javascript, we’re going to make use of Procore’s open-source package procore-iframe-helpers.

First, create a controller file at app/controllers/authentication_controller.rb and add the following code. There are three actions we need to support this flow. See the code comments for an explanation of each.

class AuthenticationController < ApplicationController
  # Serves our authentication page
  def index; end

  # Will eventually receive authorization codes and trade them for access tokens.
  # For now, it's enough to just redirect to the success path to complete the user flow.
  def oauth_callback
    redirect_to oauth_success_path
  end

  # Renders the page that we'll redirect to from the procore login window. It will send
  # a message to the parent window that authentication was successful.
  def oauth_success; end
end

Next, create the folder app/views/authentication and create a file at app/views/authentication/index.html.erb. Inside that file, add the following code. Note that we haven’t created the authentication javascript pack yet, but we’ll get there soon! The button ID is so that we can attach a login function via javascript after the DOM has been rendered.

<%= javascript_pack_tag 'authentication' %>
<main>
  <h1>My Example Procore Application</h1>
  <button id='authentication-button'>Authenticate with Procore</button>
</main>

Create another file at app/views/authentication/oauth_success.html.erb with the following code. After a user authenticates with Procore in a pop-up window, they will be redirected to this page. Once rendered, this page will call some javascript which sends a success message to our parent window.

<%= javascript_pack_tag 'oauth_success' %>

Let’s get to the actual client javascript now. Install the procore-iframe-helpers package by running this command:

yarn add @procore/procore-iframe-helpers

Create a new javascript file at app/javascript/packs/authentication.js with the following content. Don’t forget to replace <YOUR_CLIENT_ID> with your app’s client ID found in the developer portal. This file utilizes the procore-iframe-helpers library to open a new pop-up window where the user can log into their Procore account and grant us access.

import * as procoreIframeHelpers from '@procore/procore-iframe-helpers';

// Replace <YOUR_CLIENT_ID> with the Client ID found in the Procore Developer Portal.
const clientId = '<YOUR_CLIENT_ID>';
const procoreHost = 'https://login-sandbox.procore.com/oauth/authorize';
const callbackUrl = 'http://localhost:3000/oauth_callback';

const login = () => {
  const iframeHelperContext = procoreIframeHelpers.initialize();
  const authUrl = `
    ${procoreHost}?client_id=${clientId}&response_type=code&redirect_uri=${callbackUrl}
  `;

  iframeHelperContext.authentication.authenticate({
    url: authUrl,
    onSuccess() {
      // This function fires when a message is received from the child window that
      // authentication was a success.
      window.location = '/';
    },
    onFailure(error) {
      // If the child authentication window exits without sending a success message,
      // this function will execute
      alert('authentication failed!');
    },
  });
};

document.addEventListener("DOMContentLoaded", function(event){
  document.getElementById('authentication-button').addEventListener('click', (event) => {
    login();
  });
});

The last piece we need for the client is the oauth_success javascript pack at app/javascript/packs/oauth_success.js. This file will execute in the child authentication window and will send a message to its parent window when the user successfully authenticates with their Procore account.

import * as procoreIframeHelpers from '@procore/procore-iframe-helpers';

document.addEventListener('DOMContentLoaded', () => {
  procoreIframeHelpers.initialize().authentication.notifySuccess({});
});

To see when we successfully retrieved an authorization code, let’s add a new root page. Open app/controllers/application_controller.rb and add the following line:

class ApplicationController
  # Leave anything else in this controller the same
  def index; end
end

Then add a new view to app/views/application/index.html.erb

<main>
  Authorization code successfully received!
</main>

Next, add the following to your config/routes.rb file:

root 'application#index'
get '/authenticate', to: 'authentication#index'
get '/oauth_callback', to: 'authentication#oauth_callback'
get '/oauth_success', to: 'authentication#oauth_success', as: :oauth_success

Now you can start your server with bin/rails s and give it a try by visiting localhost:3000/authenticate. Once you click the “Authenticate with Procore” button, a new window will pop up where you can enter your sandbox account credentials to log into Procore. If you get the error message “Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.”, you likely need to either replace <YOUR_CLIENT_ID> in app/javascript/authentication.js or re-copy your Client Id from the Procore Developer portal and enter it as the value of the clientId variable in that file. If you get the error “The Redirect URI included is not valid.”, then you need to ensure that the Redirect URI listed in the Procore Developer portal matches the value of the callbackUrl variable in app/javascript/authentication.js, in this case: http://localhost:3000/oauth_callback.

After successfully authenticating with Procore, they’ll ask if you want to authorize the application, which you can allow to continue. After clicking ‘Allow’, the authentication window should disappear and the primary window should be redirected to our root page with the text “Authorization code successfully received!”. Congratulations! You’ve implemented about half of what’s required to utilize the authorization code grant OAuth type.

If you’re running into issues, check your code against my repository at this tag.

We can’t quite make requests to the API yet but we’re very close, let’s finish it up.

To verify that we’ve successfully authenticated, we’re going to use the Procore Ruby SDK to make a simple web request and render the result back to the client. Start by adding the procore gem to your Gemfile and creating an initializer file.

# Gemfile
gem 'procore', '~> 1.0.0'
# in config/initializers/procore.rb...

# This configuration copied from https://github.com/procore/ruby-sdk#configuration
# except the host has been changed from api.procore.com to sandbox.procore.com and
# the app name has been changed

require "procore"
Procore.configure do |config|
  # Base API host name. Alter this depending on your environment - in a
  # staging or test environment you may want to point this at a sandbox
  # instead of production.
  config.host = "https://sandbox.procore.com"

  # When using #sync action, sets the default batch size to use for chunking
  # up a request body. Example: if the size is set to 500, and 2,000 updates
  # are desired, 4 requests will be made. Note, the maximum size is 1000.
  config.default_batch_size = 500

  # The default API version to use if none is specified in the request.
  # Should be either "v1.0" (recommended) or "vapid" (legacy).
  config.default_version = "v1.0"

  # Integer: Number of times to retry a failed API call. Reasons an API call
  # could potentially fail:
  # 1. Service is briefly down or unreachable
  # 2. Timeout hit - service is experiencing immense load or mid restart
  # 3. Because computers
  #
  # Defaults to 1 retry. Would recommend 3-5 for production use.
  # Has exponential backoff - first request waits a 1.5s after a failure,
  # next one 2.25s, next one 3.375s, 5.0, etc.
  config.max_retries = 3

  # Float: Threshold for canceling an API request. If a request takes longer
  # than this value it will automatically cancel.
  config.timeout = 5.0

  # Instance of a Logger. This gem will log information about requests,
  # responses and other things it might be doing. In a Rails application it
  # should be set to Rails.logger
  config.logger = Rails.logger

  # String: User Agent sent with each API request. API requests must have a user
  # agent set. It is recomended to set the user agent to the name of your
  # application.
  config.user_agent = "Example Procore App"
end

Don’t forget to bundle install afterward. Once that’s done, we need to make some changes to the authentication controller. When Procore makes a request to the oauth_callback method, they include a query parameter named code which is a temporary OAuth authorization code. To get an official Access Token that will allow us to query their API, we need to exchange this authorization code for it by performing a POST request back to the Procore servers with this code and the redirect URL that we used to generate it. This ensures that our server and the Procore server have done a full ‘handshake’ to identify each other before granting access to the User’s information.

class AuthenticationController < ApplicationController
  # ... everything up until the oauth_callback method unchanged
  def oauth_callback
    # In a production app, we would store these elsewhere as app configurations
    client_id = '<YOUR_CLIENT_ID>'
    client_secret = '<YOUR_CLIENT_SECRET'

    # If successful, the exchange response will contain an access token which
    # allows us to make requests to the Procore API on behalf of the authenticated user.
    exchange_response = JSON.parse(Net::HTTP.post_form(
      URI('https://login-sandbox.procore.com/oauth/token'),
      {
        grant_type: 'authorization_code',
        client_id: client_id,
        client_secret: client_secret,
        code: params['code'],
        redirect_uri: 'http://localhost:3000/oauth_callback',
      }
    ).body)

    # The Procore Ruby SDK uses this class to make token management simpler.
    auth_token = Procore::Auth::Token.new(
      access_token: exchange_response['access_token'],
      refresh_token: exchange_response['refresh_token'],
      expires_at: Time.at(
        exchange_response['created_at'].to_i + exchange_response['expires_in'].to_i
      )
    )

    # We're storing the auth token in the user's session so that we can use it repeatedly.
    # In a production app, this is not recommended and you would want to store it somewhere
    # more reliable like in a database, key-value store, etc.
    auth_store = Procore::Auth::Stores::Session.new(session: session)
    auth_store.save(auth_token)

    # Now that we have the access token stored, we can use the Procore SDK to request
    # information about the authenticated user by querying the API!
    user_info = Procore::Client.new(
      client_id: client_id,
      client_secret: client_secret,
      store: auth_store
    ).get('/me', version: 'v1.0').body

    # Store this information in the user's session so we can access it later
    session.merge!({
      user_id: user_info['id'],
      user_email: user_info['login'],
      user_name: user_info['name']
    })

    redirect_to oauth_success_path
  end
  # ... everything after unchanged
end

Finally, let’s update our home page to display the information we retrieved about the user in app/views/application/index.html.erb.

<main>
  The authenticated user is:
  <table>
    <tr>
      <td>User ID:</td>
      <td><%= session[:user_id] %></td>
    </tr>
    <tr>
      <td>User Name:</td>
      <td><%= session[:user_name] %></td>
    </tr>
    <tr>
      <td>User Email:</td>
      <td><%= session[:user_email] %></td>
    </tr>
  </table>
</main>

That’s it! If you fire up your server now, you should be able to visit localhost:3000/authenticate, log in with your Procore credentials, and then see your user information on the home page after redirect. From here, you could go further by creating a proxy action on the server which takes the user’s credentials from their session and uses them to make any requests to the Procore API you want. While this application is far from production-ready, I hope it provides a solid enough foundation for you to go and build a new custom Procore Application!

You can reference the full implementation at this Github repository.

If you’re looking for custom Procore app development for your company, or you have an idea for a Procore Marketplace app that you would like to partner up on, please don’t hesitate to reach out via mike@strukchur.com.

comments powered by Disqus