# 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](/docs/internal-apps/s2s-oauth/). 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](https://redis.io/solutions/caching/)) and a DIY token manager. **Step 1. Set up your environment** - Via your computer or IDE terminal shell, install [Node.js](https://nodejs.org/en/download) (if not already installed) - In your preferred location, create a new project and initialize it: ```shell mkdir zoom-token-manager && cd zoom-token-manager npm init -y ``` **Step 2. Install dependencies** - Add the required packages to the project: ```shell 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: ```shell touch index.js tokenManager.js .env .gitignore ``` Your file structure should now look something like this: ```shell 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: ```gitignore # Node.js dependencies node_modules/ package-lock.json # Environment variables .env # Operating system generated files .DS_Store Thumbs.db ``` - In the `.env` file add: ```ini # 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: ```shell brew install redis # macOS (Homebrew) sudo apt install redis-server # Ubuntu ``` - Start the Redis server: ```shell redis-server ``` **Step 6. Set up token manager in `tokenManager.js`** - Copy the following code into the file: ```javascript # 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](https://www.geeksforgeeks.org/what-are-in-memory-caches/) 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!