Get started with security: Vite, React, and Ably Token Requests for API access
Tags: react, vite, authentication, webdev
Published: 2024-01-04 10:39:58

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](https://hubs.la/Q028DYXw0). Using an Ably SDK, a TokenRequest is [generated from your server](https://hubs.la/Q028DZcd0) and returned to the client-side SDK instance. The client-side SDK instance then uses the [TokenRequest](https://hubs.la/Q028DZk40) to request an [Ably Token](https://hubs.la/Q028DZqw0) 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](https://github.com/ablydevin/ably-tokenrequest-sample), otherwise lets get building!

## Vite Project Setup

Start building the application by using [Vite](https://vitejs.dev/), a popular frontend build tool to scaffold a basic React and Javascript application:

```
npm create vite@latest ably-tokenrequest-sample --template react-js
```

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
```

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

![Project running on local web server](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ow1oo3fwu0554bk5huy.png)

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](https://expressjs.com/) as its underlying server.

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

```
npm install vite-plugin-api express
```

Add the plugin to `vite.config.js`:

```javascript
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
    }),
  ],
});
```

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]"
```

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](https://hubs.la/Q028DYXw0) and pass this to the client. To do that start using NPM to install the Ably SDK:

```
npm install ably
```
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:

```javascript
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' })
  //}
};
```

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](http://localhost:5173/api/ably/token) and you should see the JSON encoded TokenRequest returned.

![JSON encoded TokenRequest returned](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w0qkrfm02cse3bydyitf.png)

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:

```javascript
const client = Ably.Realtime.Promise({ authUrl: '/api/ably/token/' });
```

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

```javascript
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)
  }
}
```

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](https://twitter.com/ablydevin) or [drop me an email](devin.rader@ably.com).