3rd Party Integrations with Sharetribe Flex - a DB-free single app approach

Overview

This blog post describes a relatively simple approach for adding 3rd party server-side integrations to a Sharetribe Flex app without requiring additional persistent storage (e.g. DB) or deploying additional web apps (FTW is enough). This approach is not guaranteed to work in all cases, particularly if 3rd party service callbacks cannot be parameterized dynamically, but it has worked well for a production-quality project I’ve built that required three separate vendor integrations for features like document signing, identity verification, and custom billing. Example used in this post is ID verification with Jumio, which required a little bit of trickery to get working properly and securely.

What is Sharetribe Flex & FTW?

If you didn’t have a good answer to the question above, this section is for you. Sharetribe Flex is a hosted platform for running multi-sided marketplaces focused on services, rentals, and events. Sharetribe FTW (Flex Template for Web) is a fully functioning starter web application (NodeJS & React) that uses the Flex platform to quickly get a custom marketplace up and running. The platform has great documentation and support team, but so far hasn’t produced a guide for adding 3rd party integrations that require server-to-server communication. This is where this post comes in.

Why Server-Side?

It is definitely true that not all third party integrations require server-side customizations. For example integrations that only get data from your app, e.g. Google Analytics, typically require only client-side integration, which is usually as simple as adding a snippet of JS into your page’s markup. However, there are typically a couple of requirements that make server-side integrations necessary:

  1. Calls from a browser client are disallowed by 3rd party API for security reasons (e.g. protecting secret API credentials from being spied on by the end user)

  2. 3rd party API has an asynchronous callback that needs to be processed by your app

System Design

If you are just getting started with FTW and Flex (as I was myself a few months ago), system simplicity and ease of deployment is key. With this goal in mind, we can extend server-side rendering capabilities of FTW to provide custom server API endpoints and utilize Extended Data capabilities provided by the Marketplace API for data storage. This means no need to create a separate API server or data store to manage, each of which would introduce its own set of challenges with regard to maintenance, deployment, migration, etc.

Below is a diagram illustrating the system architecture:

fullsizeoutput_6464.jpeg

The important pieces are:

  • A custom API handler in FTW that intercepts server-side requests that match a certain prefix. If you are writing your own Flex app, then this just a matter of adding a new server-side route in your app’s framework.

  • Config values for storing API credentials and an optional checksum salt (more on that later)

  • Two-way communication with 3rd party API, which normally require credentials on both sides. In many cases one-way communication may be enough.

Show Me Some Code

This wouldn’t be a technical software engineering blog if it didn’t have some code, so here we go…

API Handler

FTW uses server-side rendering out of the box. We can piggyback on this feature by adding our own request interceptor to the server-side handling code for a URL prefix of our choosing. I chose ‘/api’ to clearly signal intent, but you can choose any prefix you like, as long as it’s not currently used by the FTW application and not likely to be used in the future.

First we define our custom API handler function, either in server/index.js or in its own file in the server directory.

const handleApiRequest = (req, res, sdk, tokenStore) => {
  // authenticate the user
  sdk.authInfo().then(authInfo => {
    if (!authInfo || authInfo.grantType !== 'refresh_token') {
      res.status(401);
      res.send({ error: 'Unauthorized' });
      return;
    }
    // do awesome server-side stuff & use res.send() to return data back to the client
  });
}

In server/index.js, we call our request interceptor function (somewhere after the sdk variable is initialized, but before renderer.render call) and return to prevent the normal server-side from kicking in.

if (req.url.startsWith('/api/')) {
  handleApiRequest(req, res, sdk, tokenStore);
  return; 
}

One thing to note is that you’ll need to run yarn dev-server rather than yarn dev to enable this functionality in your local development environment. Unfortunately, this means that you won’t get hot reloading for client-side code changes (on the flip side, server-side changes will get hot-reloaded), which is definitely a drawback and may slow down speed of development somewhat. I found the tradeoff against creating and managing a new app specifically dedicated to processing API calls to be acceptable, but your mileage may vary depending on the scope & schedule of your project, size of your team, etc.

Data Storage

Depending on the type of functionality provided by the 3rd party integration, you can store custom data on the listings or on the user’s profiles at different levels of visibility (public, protected, or private). In Moblhom, we use identity verification to limit certain user actions (users can’t book listings until they’re verified), so using profile private data is the proper way to go for this use case, since we don’t want to expose ID verification results to anyone else except the users themselves.

To write to user profile private data, do something like:

sdk.currentUser.updateProfile({
  privateData: {
    idVerification,
    idVerificationChecksum: generateChecksum(idVerification),
});

The checksum is only necessary if you are concerned about user tampering with their private data to gain an unfair advantage or bypass some required check. In the case of identity verification, this is certainly the case, so we add the checksum verification as an extra layer of security. I’ll leave the implementation of generateChecksum as an exercise for the reader (Hint: make sure to use a secret salt value taken from config or some other place not accessible by the user). This post can help you get started.

Callback Authentication

This is probably the trickiest part of the integration. How do you authenticate a callback from a 3rd party API as an arbitrary Flex marketplace user? The answer lies with a refresh token, which the API handler can get from the client request and pass to the 3rd party API. You get the token from the token store that was passed into the API handler.

const refreshToken = tokenStore.getToken().refresh_token;

The trick has to do with whether the 3rd party API can echo back the refresh token in the callback request. If the 3rd party API doesn’t support it and you need to store data from the callback in your marketplace, then you have probably reached the limits of this approach and you need to go ahead and get yourself an additional data store (e.g. DB). As of this writing, Flex doesn’t support calling the API without a valid end-user token. Fortunately for me, I did not run into this situation and was able to propagate the refresh token back successfully with Jumio (see below for details).

To authenticate the callback, you’ll need to exchange the refresh token for an access token. This can be done with Flex SDK by using Memory token store. After initializing the SDK with the memory store, you can call any other SDK method while impersonating the end user.

const tokenStore = sharetribeSdk.tokenStore.memoryStore();
tokenStore.setToken({
  // Set 'bearer' value. If this is missing, SDK will throw "unknown token type" error.
  token_type: 'bearer',
  refresh_token: refreshToken,
});
                     
// Create new SDK instance
const sdk = sharetribeSdk.createInstance({
  clientId: '<Your Client ID here>',
  tokenStore: tokenStore,
});
// Use SDK instance to perform actions on behalf of user

To further protect the refresh token from being leaked or stolen by the 3rd party API, use symmetric encryption to encrypt / decrypt the token before sending it over the wire.

ID Verification Flow

This flow is specific to Jumio ID verification, but touches on steps common to most 3rd party API integrations.

Step 1: Call Jumio API (Initiate Transaction)

This step is initiated from our custom client-side code that calls an API endpoint we created above. The API handler authenticates the user and dispatches a request to Jumio to initiate a verification transaction using credentials configured in the environment. Jumio then responds with a URL to load the verification iframe client-side along with a transaction reference. This is a common pattern for 3rd party integrations that provide embedded client-side flows. The API handler stores the transaction reference on the user’s profile and returns the verification frame URL back to the client.

For propagating the refresh token, we use the userReference parameter.

Step 2: Render Client-Side Flow

Once the API handler returns the URL used for verification, the client can render an iframe with that source URL. The rest of the client interaction is handled by the iframe code supplied by the vendor. When the verification finishes, it communicates with FTW client side via window.postMessage.

Step 3: Process Result Callback

For security reasons, Jumio provides verification results via a server-side callback rather client-side events. We first validate the request by checking the request origin against Jumio’s server whitelist and then get the refresh token from the customerId parameter, which is the userReference parameter from Step 1 that has been echoed back. We then get exchange the refresh token for an access token, which we use to store verification results on user’s extended data using Flex.

Step 4: Use Results

Now that we’ve gotten ID verification results from Jumio and stored them in user’s extended, we can use these results to drive UI behavior, allowing the user to proceed with a booking, for example.

Conclusion

So, there you are - a full server-side integration incorporating callbacks with just your good ole FTW and some creative API calls. I hope you found this post useful in your Flex project. If you have any comments, questions or critiques, please feel free to comment below or send me a personal message.