Skip to main content
This guide explains how to generate and use JWTs for authentication, so that you can securely interact with Ampersand APIs from your frontend and your customers will only have access to their own Ampersand installations. When you first start using Ampersand’s prebuilt UI components or headless UI, using a frontend API key is convenient since it does not require you to implement any server logic. However, as you move into production, we recommend that you stop using frontend API keys and start using JWT authentication for better security.
Do not do JWT signing in the frontend. It invalidates the security model.

Overview

The overall flow uses RSA-based JWT tokens with RS256 signing. It consists of:
  1. RSA Key Pair Generation - Create a public/private key pair.
  2. Key Registration - Register the public key with Ampersand.
  3. JWT Token Generation - Create signed JWT tokens in your server.
  4. API Authentication - Use JWTs to authenticate API requests made by your frontend code, by providing a getToken method to the Ampersand UI library.

1. Generate RSA key pair

First, generate an RSA key pair for signing JWTs. You can use any crypto library that can generate an RSA key pair. Only RS256 algorithm is supported. The example below uses the Node.js library crypto. You only need to do this step when you first implement JWT auth, and every time you want to rotate keys.
const crypto = require('crypto');
const fs = require('fs');

// Generate RSA key pair (2048-bit recommended)
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
  modulusLength: 2048,
  publicKeyEncoding: {
    type: 'spki',
    format: 'pem'
  },
  privateKeyEncoding: {
    type: 'pkcs1',
    format: 'pem'
  }
});

// Persist the keys somewhere
fs.writeFileSync('private_key.pem', privateKey);
fs.writeFileSync('public_key.pem', publicKey);

console.log('RSA key pair generated successfully!');

2. Register public key with Ampersand

Once you have the public key, register it with Ampersand using the Create a new JWT key endpoint.
const fs = require('fs');

async function registerJWTKey(projectId, label) {
  const publicKeyPem = fs.readFileSync('public_key.pem', 'utf8');
  
  const requestBody = {
    label: label,                    // Descriptive label for the key
    algorithm: 'RS256',              // Only RS256 is supported
    publicKeyPem: publicKeyPem       // PEM-formatted public key
  };

  const response = await fetch(`https://api.withampersand.com/v1/projects/${projectId}/jwt-keys`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': 'MY_AMPERSAND_API_KEY'
    },
    body: JSON.stringify(requestBody)
  });

  if (!response.ok) {
    throw new Error(`Failed to register key: ${response.status}`);
  }

  const result = await response.json();
  console.log('Key registered with ID:', result.kid);
  return result.kid; // This is your Key ID for JWT signing in the next step
}

// Call the function
const keyId = await registerJWTKey('my-project-id', 'My Frontend App Public Key');

3. Generate JWT tokens in server

Create JWT tokens for a particular groupRef and consumerRef combo using public and private keys generated in step 1. You can use any library of choice that can sign JWT keys. The example below uses the Node.js library jsonwebtoken. You need to then expose an API endpoint that your frontend code can call to fetch a JWT token.
const jwt = require('jsonwebtoken');
const fs = require('fs');

// Function for generating JWT
function generateJWT(keyId, claims, ttlSeconds = 600) {
  const privateKey = fs.readFileSync('private_key.pem', 'utf8');
  
  // JWT payload with required claims
  const payload = {
    sub: claims.subject,
    groupRef: claims.groupRef,
    consumerRef: claims.consumerRef,
    iat: Math.floor(Date.now() / 1000), // Issued at time
    // Expiry time, the max TTL allowed is 600 seconds.
    exp: Math.floor(Date.now() / 1000) + ttlSeconds
  };

  // Sign the token
  const token = jwt.sign(payload, privateKey, {
    algorithm: 'RS256',
    header: {
      kid: keyId  // Key ID returned by Ampersand in step 2.
    }
  });

  return token;
}

// Expose an API that your frontend code can call.
// This example assumes you are using Express.
// You need to adapt this code to your server framework.
const app = express();
app.use(express.json());

app.post('/api/generate-jwt', async (req, res) => {
  try {
    const { groupRef, consumerRef } = req.body;
    
    const token = generateJWT('your-key-id', {
      groupRef: groupRef,
      consumerRef: consumerRef,
      // The subject can be any string that helps you with debugging,
      // such as username, email, etc. Ampersand doesn't pay attention
      // to this field.
      subject: 'user@example.com',
    }, 300); // 5 minute expiration

    res.json(token);
  } catch (error) {
    res.status(500).json({
      error: 'Internal server error',
      message: 'Failed to generate JWT token'
    });
  }
});

4. Fetch JWT token in frontend

This functionality is only available in @amp-labs/react version 2.8.7 and above.
In AmpersandProvider, instead of using the apiKey property, provide a getToken method that calls your backend to fetch a JWT token. The UI library will call it whenever it needs to generate a new token, or when the existing token expires. The getToken method must have the following type signature: (params: {consumerRef: string, groupRef: string}) => Promise<string>
import { AmpersandProvider } from '@amp-labs/react';
import '@amp-labs/react/styles';

const options = {
  project: 'my-ampersand-project',
  getToken: ({consumerRef, groupRef}) => {
    const token = await getTokenFromMyBackend(consumerRef, groupRef);
    return token;
  }
};

function App() {
  return (
    <AmpersandProvider options={options}>
        // You can use any of the Ampersand components here.
    </AmpersandProvider>
  )
}

Key management API endpoints

The backend provides these endpoints for managing JWT keys:

Important security notes

  1. Private Key Security: Never expose private keys in client-side code. Store them securely on your backend servers.
  2. Token Expiration: Tokens can have a maximum TTL of 10 minutes (600 seconds). The Ampersand UI library will automatically call the getToken function you provide when the token expires.
  3. Key Rotation: Regularly rotate your RSA key pairs for better security.
I