# How to Implement OAuth for OpenAI Plugin Authentication: A Complete Guide

> Implement OAuth for OpenAI plugins. Master the Authorization Code flow with PKCE for secure authentication, exchanging codes for tokens and attaching bearer tokens to API requests.

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

---

**Implement OAuth for OpenAI plugin authentication by configuring the Authorization Code flow with PKCE, exchanging authorization codes for tokens via your backend, and attaching bearer tokens to OpenAI API requests.**

The OpenAI plugins repository demonstrates production-ready authentication patterns using the Zoom Apps SDK as a reference implementation. To implement OAuth for OpenAI plugin authentication, you must generate PKCE challenges, handle browser redirects, and securely exchange codes for access tokens according to the OAuth 2.0 specification. The following implementation details derive directly from the official source code in the `openai/plugins` repository.

## Choose the Right OAuth Flow for OpenAI Plugins

The authentication strategy depends on your application architecture and user environment. According to [`plugins/zoom/skills/oauth/SKILL.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/oauth/SKILL.md), the repository supports four distinct OAuth 2.0 flows:

- **Web-Based Redirect (Authorization Code + PKCE)**: Use this for first-time installations requiring explicit user consent in a browser. This flow redirects users to the authorization endpoint, returning a `code` and `state` parameter protected by a PKCE challenge.

- **In-Client OAuth (PKCE Popup)**: Implement this for seamless re-authorization within an embedded client. The OAuth consent appears in a popup without external browser round-trips, providing superior user experience.

- **Server-to-Server OAuth**: Select this for backend microservices that act on behalf of an entire tenant. This flow uses client credentials (ID and secret) to obtain bearer tokens directly without user interaction.

- **Device-Code Flow**: Use this variant for headless CLI tools or IoT devices where browser access is unavailable. Users authenticate on a secondary device using a short-lived code.

PKCE (Proof Key for Code Exchange) prevents interception attacks by requiring a cryptographic challenge during token exchange. As detailed in [`plugins/zoom/skills/oauth/concepts/pkce.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/oauth/concepts/pkce.md), the `code_challenge` parameter ensures that only the original requesting client can exchange the authorization code for tokens.

## Configure Application Credentials

Before implementing the flow, register your application and secure your credentials:

1. Create an application in the marketplace and navigate to the App Credentials page.

2. Record the **Client ID**, **Client Secret**, and define a **Redirect URI** (for example, `https://your-domain.com/auth`).

3. Add the redirect URI to the OAuth Allow List under the Feature tab.

Store these values as environment variables exposed only to your backend, as documented in [`plugins/zoom/skills/zoom-apps-sdk/references/environment-variables.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/zoom-apps-sdk/references/environment-variables.md):

```text
ZOOM_APP_CLIENT_ID=<your-client-id>
ZOOM_APP_CLIENT_SECRET=<your-client-secret>
ZOOM_APP_REDIRECT_URI=https://your-domain.com/auth

```

## Implement the OAuth Backend

The backend handles PKCE generation, redirects, and token exchange. The following implementations reference [`plugins/zoom/skills/zoom-apps-sdk/references/oauth.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/zoom-apps-sdk/references/oauth.md).

### Generate a PKCE Challenge

Create a cryptographically random verifier and its SHA256 hash challenge:

```javascript
// plugins/zoom/skills/oauth/examples/pkce-implementation.md
const crypto = require('crypto');

function generatePKCE() {
  const verifier = crypto.randomBytes(32).toString('hex');
  const challenge = crypto
    .createHash('sha256')
    .update(verifier)
    .digest()
    .toString('base64url');
  return { verifier, challenge };
}

```

Store the `verifier` securely; you will need it during token exchange.

### Redirect to the Authorization Endpoint

Initiate the OAuth flow by redirecting the user with the PKCE challenge:

```javascript
// plugins/zoom/skills/zoom-apps-sdk/references/oauth.md
app.get('/auth', (req, res) => {
  const { challenge } = generatePKCE();
  const params = new URLSearchParams({
    response_type: 'code',
    client_id: process.env.ZOOM_APP_CLIENT_ID,
    redirect_uri: process.env.ZOOM_APP_REDIRECT_URI,
    state: crypto.randomUUID(),
    code_challenge: challenge,
    code_challenge_method: 'S256',
  });
  res.redirect(`https://zoom.us/oauth/authorize?${params}`);
});

```

### Exchange the Authorization Code for Tokens

After the user authorizes your app, exchange the returned `code` for access and refresh tokens:

```javascript
// plugins/zoom/skills/zoom-apps-sdk/references/oauth.md
app.post('/auth/token', async (req, res) => {
  const { code, codeVerifier } = req.body;
  const tokenResponse = await axios.post(
    'https://zoom.us/oauth/token',
    null,
    {
      params: {
        grant_type: 'authorization_code',
        code,
        redirect_uri: process.env.ZOOM_APP_REDIRECT_URI,
        code_verifier: codeVerifier,
      },
      auth: {
        username: process.env.ZOOM_APP_CLIENT_ID,
        password: process.env.ZOOM_APP_CLIENT_SECRET,
      },
    }
  );
  // Persist tokenResponse.data (access_token, refresh_token, expires_at)
  res.json(tokenResponse.data);
});

```

### Handle Token Refresh

Access tokens expire periodically. Implement automatic refresh using stored refresh tokens, as shown in [`plugins/zoom/skills/oauth/examples/token-refresh.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/oauth/examples/token-refresh.md):

```javascript
// plugins/zoom/skills/oauth/examples/token-refresh.md
async function refreshAccessToken(refreshToken) {
  const resp = await axios.post(
    'https://zoom.us/oauth/token',
    null,
    {
      params: {
        grant_type: 'refresh_token',
        refresh_token: refreshToken,
      },
      auth: {
        username: process.env.ZOOM_APP_CLIENT_ID,
        password: process.env.ZOOM_APP_CLIENT_SECRET,
      },
    }
  );
  return resp.data;
}

```

## Frontend Integration with In-Client OAuth

For applications running inside the Zoom client, use the SDK's `authorize` method to handle PKCE transparently. This implementation follows [`plugins/zoom/skills/zoom-apps-sdk/examples/in-client-oauth.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/zoom-apps-sdk/examples/in-client-oauth.md).

First, fetch the challenge from your backend:

```javascript
// plugins/zoom/skills/zoom-apps-sdk/examples/in-client-oauth.md
const { codeChallenge, state } = await fetch('/api/auth/challenge')
  .then(r => r.json());

await zoomSdk.authorize({ codeChallenge, state });

```

Then listen for the authorization completion event:

```javascript
zoomSdk.addEventListener('onAuthorized', async () => {
  const token = await fetch('/api/auth/token', { method: 'POST' })
    .then(r => r.json());
  // Store token for subsequent API calls
});

```

## Authorize Requests to OpenAI Plugin Endpoints

Once authenticated, attach the bearer token to every OpenAI plugin API request:

```http
GET /v1/plugins/<plugin-id>/resources HTTP/1.1
Authorization: Bearer <access_token>

```

If routing through the AI Gateway, the token automatically forwards when you specify an OpenAI model slug such as `"openai/gpt-5.4"`. See [`plugins/vercel/skills/ai-gateway/SKILL.md`](https://github.com/openai/plugins/blob/main/plugins/vercel/skills/ai-gateway/SKILL.md) for gateway-specific configuration.

## Summary

- **Choose the appropriate flow**: Use Authorization Code with PKCE for user authentication, or Server-to-Server OAuth for backend services.
- **Secure your credentials**: Store Client ID and Secret in environment variables, never exposing them to frontend code.
- **Implement PKCE correctly**: Generate a SHA256 hash challenge in [`plugins/zoom/skills/oauth/examples/pkce-implementation.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/oauth/examples/pkce-implementation.md) and verify it during token exchange.
- **Handle token lifecycle**: Exchange codes for tokens in your backend, persist refresh tokens, and implement automatic refresh logic before expiration.
- **Attach tokens to requests**: Include the bearer token in the `Authorization` header for all OpenAI plugin endpoints, or leverage the AI Gateway for automatic forwarding.

## Frequently Asked Questions

### How does PKCE prevent attacks in OpenAI plugin OAuth?

PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks by binding the token exchange to the original client. As implemented in [`plugins/zoom/skills/oauth/examples/pkce-implementation.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/oauth/examples/pkce-implementation.md), the client generates a cryptographically random `code_verifier` and sends its SHA256 hash (`code_challenge`) during authorization. The OAuth server verifies that the same `code_verifier` is present during token exchange, ensuring only the legitimate client can obtain tokens.

### What is the difference between Web-Based Redirect and In-Client OAuth?

**Web-Based Redirect** requires opening an external browser to `https://zoom.us/oauth/authorize`, making it suitable for first-time installations where users must explicitly grant permissions. **In-Client OAuth**, documented in [`plugins/zoom/skills/zoom-apps-sdk/examples/in-client-oauth.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/zoom-apps-sdk/examples/in-client-oauth.md), opens a consent popup within the application itself using the Zoom SDK, eliminating browser context switches and improving user experience for repeat authorizations.

### How do I handle token expiration in production?

Store the `refresh_token` returned during the initial token exchange in a secure database. Implement the refresh logic from [`plugins/zoom/skills/oauth/examples/token-refresh.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/oauth/examples/token-refresh.md) to automatically obtain new access tokens when the current token expires. Monitor the `expires_in` field to trigger refreshes proactively, typically refreshing tokens when 80% of the expiration time has elapsed.

### Can I use Server-to-Server OAuth for OpenAI plugins?

Yes, the Server-to-Server OAuth flow is supported for backend services that need to act on behalf of an entire tenant rather than individual users. This flow uses the Client Credentials grant type with your Client ID and Secret to obtain bearer tokens directly from `https://zoom.us/oauth/token`, as referenced in [`plugins/zoom/skills/oauth/SKILL.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/oauth/SKILL.md). This is ideal for automated batch processing or administrative functions that do not require interactive user consent.