How to Handle Auth for Embedded React Components

Use case & requirements

More and more developer products are being delivered as react embeds. We recently built out our infrastructure to support delivery of Speakeasy as a series of embedded react components. Why embedded components? The embedded components enable our customers to include Speakeasy functionality in their existing developer portals, surfacing their API request data to their users. 

Because our embeds are exposing API data to our customers’ users, authentication is a critically important feature to get right. In this blog post I will walk through how we decided to implement authentication for our embedded react components, in the hope it helps someone else building react embeds that handle data. 

The elevator-pitch for this feature is: our users should be able to safely embed our widgets into their web applications. The list of requirements for this feature looks something like this:

  1. Data Segmentation: Because our customers’ web applications may be user-facing, our customers need to be able to dictate what portion of the data gets rendered into the components.
  2. Manageable: Customers need to be able to restrict access to someone who was previously authenticated.
  3. Read Only: Embeds might be exposed to people who shouldn’t be able to modify the existing state (upload an OpenAPI schema or change API labels, for example), so they should have restricted permissions.

The TL;DR is that we evaluated two authentication options: API Keys, and Access Tokens. We ended up going with access tokens. Although they were more work to implement we felt they provided superior flexibility. Read on for the details of the discussion.

How to Solve - Option 1: API Key?

At the point that we were initially discussing this feature, we already had the concept of an api-key, which permits programmatic access to our API. An obvious first question is whether or not we could re-use that functionality to authenticate users of the embedded resources. Let’s imagine what that might look like in the use case of our react embeds.

That was easy! But does it meet our requirements?

  1. Data Segmentation: Maybe, but it would take some work. We’d need to have some means of relating an api-key with a customer, but that’s not too bad. 
  2. Manageable: Again, maybe. What happens when an api key needs to be revoked: unless a separate api key is produced for each user (imagine that one customer has multiple people using the embedded component), everyone using the newly-revoked token loses access. If a separate api key is produced for each user, then the api-key becomes state that needs to be held, somewhere, and since Speakeasy isn’t a CRM, that’s now a table in your database that you need to manage.
  3. Read Only: Not yet. We’d need to add some functionality to the api keys to distinguish between read-only and write-capable api keys.

How does this live up to our requirements?

Could work, but would require some work to meet the requirements. Maybe we can do better.

How to Solve - Option 2: Access Token

What would a separate “embed access token” implementation look like? Let’s take a look at what tools we’ve already built that we might take advantage of. 

  • We know that our sdk is in your API 
  • We know we have the concept of a customer ID 
  • We also know that you know how we’re identifying your customers (you assigned the customerID to each request)
  • We know that you probably already have an auth solution that works for you.

From the looks of it, we don’t really need to understand who your users are or how you assign customerIDs to requests, and chances are, you don’t want another service in which to manage your customers, because you probably have that logic somewhere already. Since the authentication logic for your API is probably reliably available from within your API, we just need to make it easy for you to give that authentication result to us.

This is where we can take advantage of the Speakeasy server-side sdk that is already integrated into your API code, because it’s authenticated with the Speakeasy API by means of the api key (which is workspace specific). 


// Endpoint in api server with speakeasy sdk integration
controller.get("/embed-auth", (req, res)  => {
    const authToken = req.headers["x-auth"];
    const user = userService.getByAuthHeader(authToken);

    const filters = [{
      key: "customer_id",
      operator: "=",
      value: user.companyId
    }];

    const accessToken = speakeasy.getEmbedAccessToken(filters)
    res.end(JSON.stringify({ access_token: accessToken}));
});

That takes care of understanding how to segment the data, but how do we actually make that work? There are myriad reasons that you might want to control the scope of data exposed to your customers. We already have a filtering system for the Speakeasy Request Viewer. If we build the access token as a JWT, we can bake those filters into the JWT so that they cannot be modified by the end-user, and we can coalesce the filters from the JWT and the URL, maintaining the existing functionality of the Speakeasy Request Viewer filtering system.

Putting this all together, the resulting flow looks like:

  1. You authenticate the user
  2. You translate the result of authentication into a series of filters
  3. You pass the filters into some method exposed by the Speakeasy sdk
  4. We encode those filters into a JWT on our server
  5. You return that JWT to your React application.

How does this live up to our requirements?

  1. Data Segmentation: Yeah, beyond even just by customer ID.
  2. Manageable: JWTs are intentionally ephemeral. We already have logic to refresh JWTs for our Saas platform that we can re-use for the embed access tokens.
  3. Read Only: This can be implemented with an additional JWT claim alongside the filters. 

Loose ends

Requiring a new endpoint in your API to get the embedded components (option 2) working is more work (potentially split over teams) than api-key based authentication. The consequence is that the endpoint has to be deployed before the embedded component can even be tested. To ameliorate this disadvantage, we added a Create Access Token button directly in our webapp, which generates a short-lived access token that can be hard-coded in the react code for debugging, testing, or previewing.

Final Conclusion

Access Tokens take a little more setup, but it’s considerably more flexible than the original api key idea. It also works with whatever authentication flow you already have, whether it’s through a cloud provider or your hand-rolled auth service. Additionally, because JWTs are ephemeral and can’t be modified, this solution is more secure than the api-key method, which would require manual work to revoke, whereas the moment that a user can’t authenticate using your existing authentication, they can no longer authenticate with a Speakeasy embedded component.

Building @ Speakeasy