How to Handle Authentication in OpenAI Plugins: Three Proven Patterns from the Zoom Reference
OpenAI plugins support three authentication patterns—User OAuth 2.0, Server-to-Server OAuth, and legacy JWT—implemented via environment variables and token-caching wrappers as demonstrated in the Zoom reference implementation.
The openai/plugins repository provides a production-ready reference for handling authentication when your plugin needs to call external APIs. The Zoom integration in plugins/zoom/skills/rest-api/references/authentication.md illustrates exactly how to secure outbound requests using OAuth flows, manage token lifecycle, and declare authentication requirements in your plugin manifest.
Authentication Patterns in OpenAI Plugins
The Zoom reference implementation defines three distinct methods for handling authentication in OpenAI plugins. Choose the pattern that matches your plugin's architecture and user-interaction model.
User OAuth 2.0 (Three-Legged Flow)
User OAuth 2.0 is required when your plugin performs actions on behalf of a specific user, such as reading their meetings or recordings. The flow redirects the user to the provider's oauth/authorize endpoint, where they approve the scopes declared in your Marketplace app. The provider returns an authorization code, which your backend exchanges for an access token and refresh token. Every subsequent API request includes this token in the Authorization: Bearer … header.
Server-to-Server OAuth (Backend-Only)
Server-to-Server OAuth suits plugins that run purely backend logic—batch jobs, analytics, or webhook processing—without requiring user consent. You create a "Server-to-Server OAuth" app in the provider's Marketplace, then request a token using the grant_type=account_credentials flow. According to plugins/zoom/skills/rest-api/references/authentication.md, these tokens are valid for one hour and must be refreshed automatically before expiration.
JWT (Legacy)
JWT authentication exists only for legacy integrations. New plugins should avoid this method, as it involves signing a JSON Web Token with an API key/secret pair and is actively being deprecated by most providers in favor of OAuth flows.
Implementing Server-to-Server OAuth in Node.js
The reference implementation in plugins/zoom/skills/rest-api/references/authentication.md provides a reusable ZoomAuth class that handles token caching, refresh logic, and request signing. This pattern can be adapted for any service requiring Server-to-Server OAuth.
const axios = require('axios');
class ZoomAuth {
constructor(accountId, clientId, clientSecret) {
this.accountId = accountId;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.token = null;
this.tokenExpiry = null;
}
// Obtain a fresh token if the cached one is missing or close to expiring
async getAccessToken() {
if (this.token && this.tokenExpiry > Date.now() + 60_000) {
return this.token; // cached token still valid
}
const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64');
const response = await axios.post(
'https://zoom.us/oauth/token',
`grant_type=account_credentials&account_id=${this.accountId}`,
{
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
}
);
this.token = response.data.access_token;
this.tokenExpiry = Date.now() + response.data.expires_in * 1000;
return this.token;
}
// Generic wrapper for Zoom REST calls
async apiRequest(method, endpoint, data = null) {
const token = await this.getAccessToken();
const config = {
method,
url: `https://api.zoom.us/v2${endpoint}`,
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
};
if (data) config.data = data;
return axios(config);
}
}
// Example usage
const zoom = new ZoomAuth(
process.env.ZOOM_ACCOUNT_ID,
process.env.ZOOM_CLIENT_ID,
process.env.ZOOM_CLIENT_SECRET,
);
(async () => {
const users = await zoom.apiRequest('GET', '/users');
console.log(users.data);
})();
Key implementation details:
- The
getAccessTokenmethod implements the account-credentials flow usinggrant_type=account_credentials. - Tokens are cached in memory and automatically refreshed when they are within one minute of expiry (the
60_000millisecond buffer). - The
apiRequestmethod guarantees every outbound call includes theAuthorization: Bearerheader with a valid token.
Handling In-Client OAuth for Zoom Apps (PKCE)
If your plugin is a Zoom App that runs inside the Zoom client, you must use In-Client OAuth with PKCE instead of redirect-based flows. As documented in plugins/zoom/skills/zoom-apps-sdk/references/oauth.md and plugins/zoom/skills/zoom-apps-sdk/examples/in-client-oauth.md, the flow works as follows:
- Generate a code challenge and random state value on your backend.
- Pass these to the frontend and invoke
zoomSdk.authorize({ codeChallenge, state }). - Zoom displays an authorization popup inside the client; upon approval, it returns an authorization code via event.
- Exchange that code for an access token using the same token endpoint (with
grant_type=authorization_code). - Store the token and use it for subsequent SDK or REST calls.
Configuring Plugin Manifests and Environment Variables
Before deploying, declare your authentication requirements in the plugin manifest and secure your credentials using environment variables.
The plugins/zoom/.codex-plugin/plugin.json file demonstrates how to signal OAuth requirements to the Codex runtime using the oauth keyword. Never hard-code credentials; instead, reference environment variables such as ZOOM_ACCOUNT_ID, ZOOM_CLIENT_ID, ZOOM_CLIENT_SECRET, and ZOOM_APP_REDIRECT_URI as shown in the Zoom SDK documentation.
Summary
- OpenAI plugins handle authentication using three patterns: User OAuth 2.0, Server-to-Server OAuth, and legacy JWT.
- Server-to-Server OAuth is ideal for backend-only plugins and requires the
grant_type=account_credentialsflow with automatic token refresh. - User OAuth 2.0 requires a three-legged flow with user consent, returning access and refresh tokens stored in
Authorization: Bearerheaders. - Reference implementations in
plugins/zoom/skills/rest-api/references/authentication.mdprovide production-ready Node.js classes for token caching and API request wrapping. - Plugin manifests must declare OAuth requirements using the
oauthkeyword in.codex-plugin/plugin.json.
Frequently Asked Questions
How do I choose between User OAuth and Server-to-Server OAuth for my plugin?
User OAuth 2.0 is required when your plugin acts on behalf of a specific user (e.g., accessing their personal data), while Server-to-Server OAuth is used for backend automation that does not require user consent. The Zoom reference in plugins/zoom/skills/rest-api/references/authentication.md provides implementations for both flows.
Where should I store OAuth client secrets in an OpenAI plugin?
Store client secrets exclusively in environment variables such as ZOOM_CLIENT_ID and ZOOM_CLIENT_SECRET, and never commit them to source code. The Zoom reference implementation retrieves these via process.env, and the plugins/zoom/.codex-plugin/plugin.json manifest references them without exposing values.
How do I automatically refresh OAuth tokens before they expire?
Implement a caching mechanism that checks tokenExpiry against the current time with a safety buffer (e.g., 60 seconds) before each request. The getAccessToken method in the reference ZoomAuth class automatically requests a new token when the cached one is near expiration, ensuring uninterrupted API access.
Can I use the same authentication code for different external APIs?
Yes. The ZoomAuth class pattern demonstrated in plugins/zoom/skills/rest-api/references/authentication.md is generic—simply replace the Zoom-specific endpoints (https://zoom.us/oauth/token and https://api.zoom.us/v2) with your target provider's URLs while maintaining the same token caching and refresh logic.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →