DEV Community

Cover image for Get started with security: Vite, React, and Ably Token Requests for API access
ablydevin for Ably

Posted on • Originally published at ably.com

Get started with security: Vite, React, and Ably Token Requests for API access

SaaS APIs typically require some kind of authentication to allow access. While there are many ways that APIs can implement authentication, one popular choice is using secret keys as the scheme.

Exposing secret keys directly to a client application however can create security risks, so how can client applications based on libraries like React securely access SaaS services? Many APIs, including Ably, offer an additional client-oriented token authentication system. Tokens have the benefit of being short-lived and allow you to include access control information, meaning you can give different clients different levels of access to your system.

Ably offers an additional token scheme called Token Requests. Using an Ably SDK, a TokenRequest is generated from your server and returned to the client-side SDK instance. The client-side SDK instance then uses the TokenRequest to request an Ably Token directly from Ably.

Token Requests have several advantages over passing tokens directly to clients. They are generated securely by your servers without communicating with Ably so your secret API key is never passed between your server application, Ably or your client applications. Additionally, Ably Token Requests cannot be tampered with due to being signed, must be used soon after creation, and can only be used once.

In this blog post, I’ll show you how you can use Ably SDK’s in a React application to authenticate an Ably client using a Token Request to avoid exposing your Ably secret keys. If you want to skip straight to the code check out this GitHub repository, otherwise lets get building!

Vite Project Setup

Start building the application by using Vite, a popular frontend build tool to scaffold a basic React and Javascript application:

npm create vite@latest ably-tokenrequest-sample --template react-js
Enter fullscreen mode Exit fullscreen mode

Once Vite has finished scaffolding follow its instructions to finish installing the project dependencies and run the project:

cd ably-tokenrequest-sample
npm install
npm run dev
Enter fullscreen mode Exit fullscreen mode

The project will run, starting a local web server that you can browse to.

Project running on local web server

Now let's add an API endpoint that generates and returns a Token Request.

Create API routes

Because server-side applications can access environment variables, we can use an API endpoint running on the server (and therefore has access to our Ably secret key) to generate a Token Request for the client.

By default, Vite comes with no server components but there are multiple plugins that add API route capabilities to a Vite project. In our project we’ll use vite-plugin-api a plugin that adds API routing to Vite by basing it on directory structure. It uses Express as its underlying server.

Use NPM to install the vite-plugin-api and express packages:

npm install vite-plugin-api express
Enter fullscreen mode Exit fullscreen mode

Add the plugin to vite.config.js:

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { pluginAPI } from "vite-plugin-api";
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
    pluginAPI({
      // Configuration options go here
    }),
  ],
});
Enter fullscreen mode Exit fullscreen mode

Next, create a place to store the environment variables used in our project. Vite uses dotenv to load environment variables from .env files into your environment directory.

​​In the root of your project, create a new file named .env.local. The .local extension tells dotenv to prioritize these values over other .env files it may load.

To prevent accidentally leaking env variables to the client, only variables prefixed with VITE_ are exposed to your Vite-processed code. Add a variable for your Ably secret key:

VITE_ABLY_API_KEY="[YOUR_ABLY_API_KEY]"
Enter fullscreen mode Exit fullscreen mode

We’re now ready to create an endpoint and generate a Token Request.

Generating a Token Request

Ably has several ways to create tokens but one way is using an Ably SDK to have your server application generate a Token Request and pass this to the client. To do that start using NPM to install the Ably SDK:

npm install ably
Enter fullscreen mode Exit fullscreen mode

Under the /src/ filter create an /api/ably/ folder. This serves as your API root. To that folder add a token.js file. Insert the following code:

import Ably from "ably/promises";

export const GET = async (req, res) => {

  // Your application should perform some type of user 
 // authorization to validate that the user is allowed 
 // to receive a token before fulfilling the token request 
 // if (requesting_user.isAuthenticated) {

    const client = new Ably.Rest(import.meta.env.VITE_ABLY_API_KEY);
    const tokenRequestData = await client.auth.createTokenRequest({
      clientId: 'Random Client ID',
    });
    console.log(`Request: ${JSON.stringify(tokenRequestData)}`);
    return res.json(tokenRequestData);

  //} else {
 // res.status(401).json({ 'error':'User is not authorized' })
  //}
};
Enter fullscreen mode Exit fullscreen mode

This code defines an endpoint that serves GET requests, uses the createTokenRequest function from the Ably SDK to generate a TokenRequest, and returns that TokenRequest as a JSON-encoded object.

ℹ️
Note that right now this code will return a valid TokenRequest to any client that sends a GET request to the endpoint. As called out in the code comments, for most applications you will want to include in this endpoint some type of user authorization to validate the request is coming from an application user allowed to generate TokenRequests.

Additionally, validating the user making the request allows you to include access controls in the Token Request to give the user the smallest number of required permissions.

Save the file and if needed restart your Vite project by running npm run dev. Load the route http://localhost:5173/api/ably/token and you should see the JSON encoded TokenRequest returned.

JSON encoded TokenRequest returned

Congratulations! Your client applications can now request TokenRequests they can use to authenticate with Ably. To do that, they can provide the API route as the value of the authUrl property:

const client = Ably.Realtime.Promise({ authUrl: '/api/ably/token/' });
Enter fullscreen mode Exit fullscreen mode

Alternatively, use the authCallback property to provide a function that gives you more direct control of the request to the API:

const client = Ably.Realtime.Promise({ authCallback: async (data, callback) => {
  try {
    const response = await fetch("/api/ably/token/");
    const tokenRequest = await response.json();
    callback(null, tokenRequest)
  } catch (e) {
    callback(e, null)
  }
}
Enter fullscreen mode Exit fullscreen mode

Either method allows the Ably client to receive the TokenRequest object it can use to authenticate with Ably.

Wrapup

Allowing client applications to use authenticated services can complicate how you think about securing your application. You need to avoid passing service secrets to the client, but still allow them a way to authenticate. Many services like Ably solve this problem by providing you with a mechanism for creating short-lived access tokens in your server application, and passing them to the client.

One way to allow client applications to retrieve a token is via an API endpoint. One way to allow client applications to retrieve a token is via an API endpoint. In a Vite project, the vite-plugin-api is a simple way to add API routes to your application. The plugin enhances the default routing in a Vite project by adding a simple directory structure-based mechanism for API routes.

In this demo we created a new route that uses the Ably SDK to generate a new Ably Token Request. What are your favorite ways to add API routes to a React app? Let us know by reaching out on Twitter or drop me an email.

Top comments (0)