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

> Implement OAuth 2.0 for plugin authentication using OpenAI's schema. Explore Server-to-Server, Authorization Code, PKCE, and Device Code flows for secure token management.

- Repository: [OpenAI/plugins](https://github.com/openai/plugins)
- Tags: how-to-guide
- Published: 2026-06-14

---

**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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/rest-api/concepts/authentication-flows.md) demonstrates automatic token management:

```javascript
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:

```python
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:

- [`plugins/zoom/skills/rest-api/concepts/authentication-flows.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/rest-api/concepts/authentication-flows.md) – Describes the full OAuth 2.0 schema, flow selection criteria, and token handling patterns.

- [`plugins/zoom/skills/rest-api/references/authentication.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/rest-api/references/authentication.md) – Reference guide summarizing required credentials and security best practices.

- [`plugins/zoom/skills/oauth/SKILL.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/oauth/SKILL.md) – Provides concrete implementation steps and workflow diagrams for OAuth integration.

- [`plugins/convex/.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/plugins/convex/.codex-plugin/plugin.json) – Demonstrates the `"auth"` keyword usage in plugin manifests, indicating authentication support.

## 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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/plugins/convex/.codex-plugin/plugin.json) file demonstrates how the `"auth"` keyword indicates OAuth support generically across different plugin implementations.