Schema for Plugin Authentication in OpenAI Plugins: OAuth 2.0 Implementation Guide

The schema for plugin authentication in the openai/plugins repository follows a standard OAuth 2.0 specification supporting Server-to-Server OAuth, Authorization Code, PKCE, and Device Code flows, with token management handled via Client ID, Client Secret, and configurable scopes.

The openai/plugins repository implements a robust schema for plugin authentication that enables secure API access for external services. This authentication framework is built on standard OAuth 2.0 principles and is extensively documented in the Zoom plugin implementation, serving as the reference pattern for all plugins requiring protected API access.

Core Elements of the Plugin Authentication Schema

The authentication schema is defined in plugins/zoom/skills/rest-api/concepts/authentication-flows.md and consists of the following standardized elements:

Element Description Example
Flow Determines how credentials are exchanged. Supported flows are Server-to-Server OAuth, Authorization Code, Authorization Code with PKCE, and Device Code. Server-to-Server OAuth for backend services
Client ID Public identifier for the app provided in the app portal. process.env.ZOOM_CLIENT_ID
Client Secret Private secret used with the Client ID to obtain a token. process.env.ZOOM_CLIENT_SECRET
Account ID For Server-to-Server OAuth, the service account identifier. process.env.ZOOM_ACCOUNT_ID
Scopes Granular permissions requested for the token. meeting:write:admin
Token Endpoint HTTP endpoint where the app exchanges credentials for an access token. https://zoom.us/oauth/token
Access Token Bearer token returned by the token endpoint; used in Authorization: Bearer <token> headers. eyJhbGciOiJIUzI1NiJ9…
Refresh Token Optional long-lived token used to obtain a new access token without user interaction. REFRESH_TOKEN
Token Lifetime Typically 1 hour for access tokens; refresh tokens can last years. expires_in: 3600

How the Plugin Authentication Schema Works

Implementing the schema requires following a standardized token lifecycle:

  1. Select the appropriate flow that matches the plugin's usage scenario. Use Server-to-Server OAuth for backend automation, Authorization Code for user-facing applications, or Device Code for input-constrained devices.

  2. Configure credentials by storing the Client ID, Client Secret, and Account ID (if applicable) in environment variables or a secure vault. Never hard-code these values in source code.

  3. Request a token from the token endpoint using a POST request with the grant_type parameter appropriate to the selected flow.

  4. Cache the token until expires_in minus 60 seconds to prevent using expired credentials, then refresh automatically.

  5. Include the token in the Authorization: Bearer <token> header of every API call to authenticate requests.

Implementation Examples

Node.js Server-to-Server OAuth

The following implementation from plugins/zoom/skills/rest-api/concepts/authentication-flows.md demonstrates automatic token management:

class ZoomS2SAuth {
  constructor(accountId, clientId, clientSecret) {
    this.accountId = accountId;
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.token = null;
    this.tokenExpiry = 0;
  }

  async getAccessToken() {
    if (this.token && Date.now() < this.tokenExpiry - 60000) return this.token;

    const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64');
    const response = await fetch('https://zoom.us/oauth/token', {
      method: 'POST',
      headers: {
        Authorization: `Basic ${credentials}`,
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: `grant_type=account_credentials&account_id=${this.accountId}`,
    });

    if (!response.ok) {
      const err = await response.json();
      throw new Error(`Token error: ${err.error} - ${err.reason}`);
    }

    const data = await response.json();
    this.token = data.access_token;
    this.tokenExpiry = Date.now() + data.expires_in * 1000;
    return this.token;
  }

  async request(method, path, body = null) {
    const token = await this.getAccessToken();
    const response = await fetch(`https://api.zoom.us/v2${path}`, {
      method,
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json',
      },
      body: body ? JSON.stringify(body) : undefined,
    });

    if (!response.ok) {
      const err = await response.json().catch(() => ({}));
      throw new Error(`Zoom API ${response.status}: ${JSON.stringify(err)}`);
    }
    return response.status === 204 ? null : response.json();
  }
}

// Usage
const zoom = new ZoomS2SAuth(
  process.env.ZOOM_ACCOUNT_ID,
  process.env.ZOOM_CLIENT_ID,
  process.env.ZOOM_CLIENT_SECRET
);
await zoom.request('GET', '/users?page_size=300');

Python Server-to-Server OAuth

The equivalent Python implementation follows the same schema:

import requests, time, base64

class ZoomS2SAuth:
    def __init__(self, account_id, client_id, client_secret):
        self.account_id = account_id
        self.client_id = client_id
        self.client_secret = client_secret
        self.token = None
        self.token_expiry = 0

    def get_access_token(self):
        if self.token and time.time() < self.token_expiry - 60:
            return self.token
        credentials = base64.b64encode(f'{self.client_id}:{self.client_secret}'.encode()).decode()
        resp = requests.post(
            'https://zoom.us/oauth/token',
            headers={'Authorization': f'Basic {credentials}',
                     'Content-Type': 'application/x-www-form-urlencoded'},
            data=f'grant_type=account_credentials&account_id={self.account_id}'
        )
        resp.raise_for_status()
        data = resp.json()
        self.token = data['access_token']
        self.token_expiry = time.time() + data['expires_in']
        return self.token

    def request(self, method, path, json_data=None):
        token = self.get_access_token()
        resp = requests.request(
            method,
            f'https://api.zoom.us/v2{path}',
            headers={'Authorization': f'Bearer {token}'},
            json=json_data,
        )
        resp.raise_for_status()
        return resp.json() if resp.content else None

Key Files Defining the Authentication Schema

The following files in the openai/plugins repository contain the canonical definitions and implementations of the schema for plugin authentication:

Summary

  • The schema for plugin authentication in openai/plugins is built on standard OAuth 2.0 with support for four distinct flows: Server-to-Server OAuth, Authorization Code, Authorization Code with PKCE, and Device Code.

  • Core schema elements include Client ID, Client Secret, Account ID (for server-to-server), Scopes, and Token Endpoint configurations.

  • Access tokens typically expire in 1 hour and must be refreshed using the token endpoint or refresh tokens for long-running processes.

  • The Zoom plugin implementation in plugins/zoom/skills/rest-api/concepts/authentication-flows.md serves as the reference implementation for all plugins requiring protected API access.

  • Authentication state is managed through the Authorization: Bearer <token> header pattern across all supported languages and frameworks.

Frequently Asked Questions

What OAuth flows are supported by the plugin authentication schema?

The schema supports Server-to-Server OAuth for backend automation, Authorization Code for traditional web applications, Authorization Code with PKCE for mobile and single-page applications, and Device Code for input-constrained devices. Each flow is documented in plugins/zoom/skills/rest-api/concepts/authentication-flows.md with specific credential requirements and security considerations.

How long do access tokens remain valid in the plugin authentication schema?

Access tokens typically expire after 1 hour (3600 seconds) as indicated by the expires_in field in the token response. Refresh tokens, when available, can remain valid for years. Production implementations should cache tokens and refresh them 60 seconds before expiration to prevent service interruptions.

Where should Client ID and Client Secret be stored when implementing this schema?

According to the reference documentation in plugins/zoom/skills/rest-api/references/authentication.md, credentials must be stored in environment variables or secure vaults such as AWS Secrets Manager or Azure Key Vault. Never commit credentials to source code repositories or expose them in client-side code.

Can the authentication schema be used with services other than Zoom?

Yes. While the schema is documented using Zoom as the reference implementation, it follows standard OAuth 2.0 RFC specifications and can be adapted to any service provider that implements OAuth 2.0. The plugins/convex/.codex-plugin/plugin.json file demonstrates how the "auth" keyword indicates OAuth support generically across different plugin implementations.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →