How to Authenticate Users with Your Plugin: A Complete Guide to the OpenAI Plugins Repository

You can authenticate users with your plugin using OAuth 2.0 with PKCE, JWT signatures, OIDC platform tokens, or CLI-generated bearer tokens depending on the target service, as implemented in the openai/plugins repository.

The openai/plugins repository collects production-ready skills for connecting ChatGPT to external platforms, and understanding how to authenticate users with your plugin is critical for secure API access. Each service-specific skill documents its own identity flow—ranging from Zoom's multi-modal OAuth to Wix's command-line tokens—so you can delegate authorization to the provider rather than building custom identity systems from scratch.

OAuth 2.0 and PKCE in the Zoom Plugin

The Zoom plugin provides the most comprehensive authentication examples in the repository, supporting web-based OAuth, In-Client PKCE, and Server-to-Server flows. The core references are plugins/zoom/skills/zoom-apps-sdk/references/oauth.md and plugins/zoom/skills/zoom-apps-sdk/examples/in-client-oauth.md.

Web-Based Authorization Code Flow

In the standard Zoom OAuth flow, your plugin redirects the user to Zoom's authorization endpoint. After consent, Zoom sends a temporary code to your whitelisted redirect_uri, which your backend exchanges for an access token. The request/response handling is demonstrated in plugins/zoom/skills/zoom-apps-sdk/examples/quick-start.md.

The required environment variables are defined in plugins/zoom/skills/zoom-apps-sdk/references/environment-variables.md and must never be hard-coded:

  • ZOOM_APP_CLIENT_ID
  • ZOOM_APP_CLIENT_SECRET
  • ZOOM_APP_REDIRECT_URI

In-Client OAuth with PKCE

For embedded Zoom apps, the repository implements PKCE (Proof Key for Code Exchange) to prevent interception attacks. The backend generates a code_challenge and code_verifier pair, then exposes them via an endpoint that the Zoom client consumes.

According to plugins/zoom/skills/zoom-apps-sdk/examples/in-client-oauth.md, the client calls zoomSdk.authorize() with the challenge instead of performing a full browser redirect:

// client.js — runs inside the Zoom embedded browser
async function doAuth() {
  const challenge = await fetch('/api/auth/challenge').then(r => r.json());
  await zoomSdk.authorize({
    codeChallenge: challenge.codeChallenge,
    state: challenge.state,
  });
}

The backend then exchanges the returned authorization code for tokens using the verifier stored server-side.

Server-to-Server and JWT Authentication

For background or admin-level operations that do not require a user context, the Zoom plugin supports Server-to-Server OAuth. This flow directly obtains an access token using client credentials, skipping the authorization code step entirely.

Zoom's Meeting SDK additionally uses short-lived JWTs signed with your SDK key and secret. The JWT proves application identity rather than end-user identity, and is generated server-side before being passed to the client's InitSDK call. Details on both patterns are documented in plugins/zoom/skills/meeting-sdk/references/authorization.md.

For common token error scenarios, consult plugins/zoom/skills/oauth/references/oauth-errors.md.

Vercel OIDC and API Token Authentication

The Vercel plugin demonstrates platform-native authentication through OIDC tokens that Vercel automatically injects into serverless functions at runtime.

As documented in plugins/vercel/skills/vercel-api/SKILL.md, you can access the pre-authenticated token directly from process.env:

// Vercel serverless function
export default async (req, res) => {
  const token = process.env.VERCEL_TOKEN;
  const resp = await fetch('https://api.vercel.com/v9/projects', {
    headers: { Authorization: `Bearer ${token}` },
  });
  const data = await resp.json();
  res.json(data);
};

For local development, the same file recommends setting a static VERCEL_QUEUE_API_TOKEN or similar personal token in your local environment.

Supabase OAuth 2.1 Authentication

The Supabase plugin follows the standard OAuth 2.1 flow for end-user authentication. The plugin redirects the user to Supabase's /authorize endpoint, receives an access_token and optional refresh_token, and stores the token for subsequent Data API calls.

Supabase also supports JWT-based service roles for server-side operations that do not require a user session.

The full flow is outlined in plugins/supabase/skills/supabase/SKILL.md.

Wix CLI Token Authentication

Wix headless plugins use a CLI-generated, account-scoped token rather than a browser-based OAuth flow. After running npx @wix/cli@latest login, the CLI caches credentials in ~/.wixrc.

As described in plugins/wix/skills/wix-headless/references/shared/AUTHENTICATION.md, you retrieve a site-scoped bearer token programmatically and attach it to API requests:

TOKEN=$(npx @wix/cli@latest token --site "$SITE_ID")
curl -H "Authorization: Bearer $TOKEN" https://www.wixapis.com/...

This pattern eliminates the need to manage client secrets or redirect URIs for Wix integrations.

Reusable Node.js Boilerplate to Authenticate Users with Your Plugin

Below is a minimal Express backend adapted from the Zoom In-Client OAuth implementation in plugins/zoom/skills/zoom-apps-sdk/examples/in-client-oauth.md. You can repurpose this pattern for any OAuth-based service by updating the token endpoint and client credentials.

// server.js — Express backend for OAuth with PKCE
import express from 'express';
import axios from 'axios';
import crypto from 'crypto';
import dotenv from 'dotenv';
dotenv.config();

const app = express();
app.use(express.json());

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

/* 2. Endpoint: return challenge to client */
app.get('/api/auth/challenge', (req, res) => {
  const { verifier, challenge } = generatePKCE();
  // Store verifier server-side (e.g., Redis) keyed by session
  res.json({ codeChallenge: challenge, state: crypto.randomBytes(16).toString('hex') });
});

/* 3. Endpoint: exchange authorization code for tokens */
app.post('/api/auth/token', async (req, res) => {
  const { code, codeVerifier } = req.body;
  try {
    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,
        },
        auth: {
          username: process.env.ZOOM_APP_CLIENT_ID,
          password: process.env.ZOOM_APP_CLIENT_SECRET,
        },
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      }
    );
    res.json(tokenResponse.data); // access_token, refresh_token, expires_in
  } catch (e) {
    console.error('Token exchange error:', e.response?.data || e.message);
    res.status(500).json({ error: 'Token exchange failed' });
  }
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Auth server listening on ${PORT}`));

Summary

  • The Zoom plugin supports three distinct flows: web-based OAuth 2.0, In-Client OAuth with PKCE, and JWT-based Meeting SDK auth, all documented under plugins/zoom/skills/zoom-apps-sdk/references/oauth.md.
  • Vercel relies on automatic OIDC tokens available at runtime via process.env.VERCEL_TOKEN, as shown in plugins/vercel/skills/vercel-api/SKILL.md.
  • Supabase uses standard OAuth 2.1 redirects and refreshable access tokens, with service-role JWTs for server contexts.
  • Wix simplifies authentication via CLI-generated bearer tokens, avoiding manual OAuth redirects entirely.
  • Regardless of the provider, store credentials in environment variables—never hard-code secrets in source control—and validate the state parameter to prevent CSRF attacks.

Frequently Asked Questions

How do I choose the right authentication method for my plugin?

Choose OAuth 2.0 with PKCE when your plugin runs inside a client application like Zoom and requires user consent. Choose Server-to-Server OAuth or JWT when your plugin performs background or admin-level operations without a user present. Use platform-native tokens such as Vercel's OIDC or Wix's CLI tokens when the provider manages identity for you.

Where should I store client secrets and refresh tokens?

Store ZOOM_APP_CLIENT_SECRET, API keys, and refresh tokens in environment variables or a dedicated secret manager, never in your repository. The Zoom plugin explicitly references this requirement in plugins/zoom/skills/zoom-apps-sdk/references/environment-variables.md, and the same principle applies to every skill in the repository.

What is PKCE and why does Zoom require it?

PKCE (Proof Key for Code Exchange) is an OAuth extension where the client generates a secret code_verifier and sends a hashed code_challenge to the authorization server. Zoom's In-Client OAuth requires PKCE to prevent authorization code interception in embedded or mobile environments, as detailed in plugins/zoom/skills/zoom-apps-sdk/examples/in-client-oauth.md.

Can I reuse the same authentication server for multiple plugins?

Yes. The Node.js boilerplate pattern in plugins/zoom/skills/zoom-apps-sdk/examples/in-client-oauth.md demonstrates a generic PKCE and token-exchange handler. You can adapt the endpoints, token URLs, and client credentials to support multiple services from a single backend, provided you scope each provider's tokens and secrets separately.

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 →