Node.js dependencies

Token management architecture approaches

Managing API access tokens efficiently is crucial for enterprise applications, especially when working with a Server-to-Server OAuth app. A well-designed token management architecture minimizes unnecessary requests, prevents downtime, and optimizes API consumption. In this blog post, we’ll explore two key approaches—central token cache and token rotation with graceful expiry—along with some JavaScript sample code to help with token handling.

The enterprise challenge: a real-world use case

One of our enterprise customers shared their challenge that inspired this post:

"Ours is an enterprise application which retrieves large amounts of data every 4-6 hours, and having the maximum possible validity will be helpful in avoiding multiple calls for a token."

Given Server-to-Server OAuth tokens have a fixed expiration of one hour and no refresh token mechanism, applications that require long-running operations or periodic batch request jobs must proactively manage token retrieval. The following approaches can help mitigate disruptions and optimize API calls.

Central token cache

Instead of each process or scheduled job independently requesting tokens, implement a centralized token manager that handles token generation, caching, and expiry tracking. This ensures that all requests retrieve tokens from the cache instead of making redundant calls to the OAuth endpoint.

Process:

  1. A scheduled job or service requests a token from the token manager
  2. Token manager checks cache (e.g., Redis, Memcached)
  3. If a valid token exists, return it
  4. If expired or missing, generate a new token, cache it, and return

Benefits:

  • ✅ Reduces redundant token requests to the OAuth server
  • ✅ Maintains token validity across scheduled request jobs
  • ✅ Simplifies retries and error handling

Token rotation with graceful expiry

Set up a proactive token renewal mechanism that requests a new token before the current one expires, preventing gaps in API access.

Process:

  1. Token manager generates a token with a 1-hour validity
  2. 5-10 minutes before expiry, it preemptively requests a new token
  3. The new token replaces the old one without causing service disruption

Benefits:

  • ✅ Eliminates API request failures due to token expiration
  • ✅ Ensures seamless API access for long-running jobs
  • ✅ Improves system reliability with minimal performance impact

Server-to-Server OAuth token management with Javascript

Let's create a simple Javascript app that incorporates both central token cache and token rotation with graceful expiry in support of efficient token management. The key features of the app that help achieve this will be the token cache (Redis) and a DIY token manager.

Step 1. Set up your environment

  • Via your computer or IDE terminal shell, install Node.js (if not already installed)
  • In your preferred location, create a new project and initialize it:
mkdir zoom-token-manager && cd zoom-token-manager
npm init -y

Step 2. Install dependencies

  • Add the required packages to the project:
npm install axios base-64 ioredis dotenv

Package purposes:

  • axios for making HTTP requests to Zoom’s OAuth service
  • base-64 for encoding client credentials
  • ioredis for caching tokens in Redis
  • dotenv for managing environment variables

Step 3. Create application files

  • In the project root, create the following files:
touch index.js tokenManager.js .env .gitignore

Your file structure should now look something like this:

zoom-token-manager/
│── .gitignore             # Prevent sensitive information from being pushed to a public repo
│── .env                   # Environment variables (API credentials, Redis host)
│── package.json           # Project dependencies and metadata
│── package-lock.json      # Lockfile for dependency management
│── index.js               # Main entry point to test token retrieval
│── tokenManager.js        # Token management logic (caching, rotation, retrieval)

Step 4. Set up .gitignore and .env files

  • In the .gitignore file add:
# Node.js dependencies
node_modules/
package-lock.json
# Environment variables
.env
# Operating system generated files
.DS_Store
Thumbs.db
  • In the .env file add:
# Replace values with actual credentials from your Server-to-Server OAuth app on the Zoom App Marketplace.
ZOOM_CLIENT_ID=your_client_id
ZOOM_CLIENT_SECRET=your_client_secret
ZOOM_ACCOUNT_ID=your_account_id
REDIS_HOST=localhost

Step 5. Set up Redis (token cache)

  • In the terminal, confirm Redis is installed and running:
brew install redis  # macOS (Homebrew)
sudo apt install redis-server  # Ubuntu
  • Start the Redis server:
redis-server

Step 6. Set up token manager in tokenManager.js

  • Copy the following code into the file:
# Import dependencies
const axios = require("axios");
const base64 = require("base-64");
const redis = require("ioredis");
require("dotenv").config();
# Declare variables that will be used in token manager
const CLIENT_ID = process.env.ZOOM_CLIENT_ID;
const CLIENT_SECRET = process.env.ZOOM_CLIENT_SECRET;
const ACCOUNT_ID = process.env.ZOOM_ACCOUNT_ID;
const TOKEN_URL = "https://zoom.us/oauth/token";
const REDIS_HOST = process.env.REDIS_HOST || "localhost";
# Initialize redis
const redisClient = new redis({
  host: REDIS_HOST,
  port: 6379,
  retryStrategy: (times) => Math.min(times * 100, 3000),
});
# Declare variable for encoded Zoom client id and secret
const encodedCredentials = base64.encode(`${CLIENT_ID}:${CLIENT_SECRET}`);
/// TOKEN MANAGER LOGIC BELOW ///
# Generate new token and cache in Redis
async function requestNewToken() {
  try {
    console.log("Requesting new Zoom API access token...");
    const response = await axios.post(
      TOKEN_URL,
      new URLSearchParams({ grant_type: "account_credentials", account_id: ACCOUNT_ID }).toString(),
      {
        headers: { "Authorization": `Basic ${encodedCredentials}`},
        timeout: 5000,
      }
    );
    const { access_token, expires_in } = response.data;
    console.log(`✅ Token received. Expires in ${expires_in / 60} minutes.`);
    # Cache new token with 55-min TTL (time to live)
    await redisClient.set("zoom_access_token", access_token, "expires", expires_in - 300);
    return access_token;
  } catch (error) {
    console.error(`❌ Token request failed: ${error.message}`);
    throw error;
  }
}
# Retrieve valid access token from cache or trigger requestNewToken function if no valid token is available
async function getAccessToken() {
  try {
    const cachedToken = await redisClient.get("zoom_access_token");
    if (cachedToken) {
      console.log("✅ Token retrieved from cache.");
      return cachedToken;
    }
    console.log("Token expired. Requesting new one...");
    return await requestNewToken();
  } catch (error) {
    console.error(`❌ Error retrieving token: ${error.message}`);
    throw error;
  }
}
# Self-execute function to initialize token manager
(async () => {
  try {
    const access_token = await getAccessToken();
    console.log("🚀 Ready to use Zoom API with token:", access_token);
  } catch (error) {
    console.error("❌ Critical err in token management:", error.message);
  }
})();
# Export function to use in index.js file
module.exports = { getAccessToken };

The token manager in the code is represented by the combination of these two functions:

  1. requestNewToken() – Handles the retrieval of a new token from Zoom’s OAuth service and caches it in Redis.
  2. getAccessToken() – Acts as the central access point for tokens, checking Redis before deciding whether to return a cached token or request a new one.

Here's how the token manager is working:

Checks the cache first:

  • getAccessToken() first queries Redis (redisClient.get("zoom_access_token"))
  • If a valid token is found, it returns it immediately

Handles expired tokens gracefully:

  • If no valid token is found, getAccessToken() logs that the token has expired and calls requestNewToken()

Implements token rotation:

  • The key mechanism for token rotation is the TTL (Time-To-Live) set in Redis:
await redisClient.set("zoom_access_token", access_token, "EX", expires_in - 300);
  • The token is cached for (expires_in - 300) seconds, meaning 5 minutes before actual expiration, it is considered expired and a new one is requested
  • This ensures that the system never attempts to use an expired token

Self-executing function for initialization:

  • The immediately invoked function expression (IIFE) at the bottom makes it so the system is ready with a valid token when it starts

Step 7. Integrate the token manager with the application entry point file

  • In index.js import the getAccessToken function:
const { getAccessToken } = require("./tokenManager");
(async () => {
  try {
    const token = await getAccessToken();
    console.log("Using token:", token);
  } catch (error) {
    console.error("Failed to retrieve token:", error.message);
  }
})();

This makes sure your application always has a valid access token before making API calls.

Step 8. Start the application

  • node idex.js in your terminal will get everything running!

Potential challenges with this code sample

  • Cache connection issues: If Redis is down or misconfigured, token caching won't work. Set up a fallback mechanism like in-memory caching as a backup.
  • Error handling could be more granular: Right now, a general catch block handles all errors. You will need more differentiation to adequately diagnose errors for in depth set ups.

How to make this code sample better

There are a few things you can add to this app to make it more resilient toward enterprise use and further optimize its utility. Here are some ideas:

  • Advanced error handling: Retries, circuit breakers, and fallback mechanisms
  • Monitoring & alerts: Track token generation failures and usage metrics
  • Multi-region failover: Redundant caching and failover by routing requests to an alternate region if the primary one fails (e.g. Redis in us-east-1 becomes unavailable, prompt application to failover to a secondary Redis instance in us-west-2)
  • Distributed caching: Consider using AWS ElastiCache, Azure Cache, Google Memorystore or your equivalents of choice for multi-region token caching so you can fetch valid tokens from the nearest one and reduce lookup time
  • Health checks: Initiate periodic token generation tests to proactively identify failures
  • Secure token storage: Cache tokens in encrypted database with authentication enabled
  • Multiple concurrent access tokens: Since Sever-to-Server OAuth allows for multiple concurrent access tokens, you can update the code to create and store several at a time using custom assignment logic (token index) and data mapping to assign specific tokens to specific request jobs

Enterprise token management is a multi-part process

We were able to develop a foundational token management structure by combining the approaches explored in this blog. Through adopting central token caching, enterprises can reduce unnecessary token requests and enhance efficiency. Meanwhile, token rotation with graceful expiry makes sure there is always a valid access token available to minimize API interruptions. These foundational strategies help enterprise applications scale Zoom data use while creating better performance.

I'd love to hear more about how you're achieving token management in your applications! Reach out to us on the Developer Forum!