Zoom Oauth
OpenAIOfficialCreated by ZoomDistributed by OpenAI
- Skill
- Tool integration
- Automation and integrations
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured approach to implementing Zoom OAuth, covering various app types including user-level, account-level, and server-to-server OAuth. It assists developers in selecting the correct grant flow, managing token lifecycles, and diagnosing common authentication errors. The skill emphasizes secure practices, such as handling refresh tokens as single-use values and maintaining proper scope architecture.
What it does
- Guides selection of appropriate OAuth grant flows like PKCE or authorization code.
- Provides workflows for identifying app types and required scopes.
- Offers best practices for secure token management and refresh token rotation.
- Assists in troubleshooting common OAuth errors and redirect URI issues.
- Clarifies the distinction between classic and granular scope usage.
- Defines standard environment variables required for Zoom API integration.
When to use it
- When setting up authentication for a new Zoom Marketplace application.
- When debugging failed authorization requests or token refresh issues.
- When determining the correct OAuth scopes for specific API endpoints.
- When transitioning from classic scopes to granular scopes.
- When implementing server-to-server OAuth for automated account-level tasks.
Advantages
- Reduces integration errors by providing clear flow selection criteria.
- Promotes security by enforcing single-use refresh token patterns.
- Simplifies scope management through structured lookup workflows.
- Centralizes configuration requirements and environment variable standards.
Usage examples
- “How do I implement authorization code flow with PKCE for my public Zoom app?”
- The agent will outline the steps to generate a code challenge, initiate the authorization request, and exchange the code for an access token while ensuring PKCE security.
- “I am getting an invalid scope error when calling the meeting API. How do I fix this?”
- The agent will guide you to verify your app's configured scopes against the endpoint requirements and check if you are using deprecated classic scopes instead of granular ones.
- “What is the correct way to handle Zoom refresh tokens?”
- The agent will explain the requirement to persist the new refresh token returned by every refresh response and discard the old one to maintain security.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/openai/zoom-oauth/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip zoom-oauth.zip -d .agents/skills
Documentation by Zoom
Zoom OAuth
Use this skill for concrete Zoom authentication implementation and troubleshooting. Prefer setup-zoom-oauth for the first-pass setup plan, then return here for exact flows, scope behavior, refresh handling, and error diagnosis.
Workflow
Identify the app type and actor: user-level OAuth, account-level OAuth, server-to-server OAuth where officially supported, SDK JWT, or Build-platform credentials.
Confirm the target API, SDK, or app surface, because scopes and token audiences differ by surface.
Choose the grant flow: authorization code with PKCE for public clients, authorization code for confidential web apps, device authorization where appropriate, or account credentials for supported account-level automation.
Store refresh tokens as single-use values: persist the replacement refresh token returned by each refresh response before reusing the old one.
Validate requests against redirect URI, account ID, scopes, app publication state, and token expiration before changing application code.
For local development, keep access tokens out of logs and treat refresh tokens as single-use when rotating credentials.
References
Full preserved guide: references/full-guide.md
OAuth flows: concepts/oauth-flows.md
Token lifecycle: concepts/token-lifecycle.md
Scope architecture: concepts/scopes-architecture.md
Granular scopes: references/granular-scopes.md
OAuth errors: references/oauth-errors.md
Redirect URI issues: troubleshooting/redirect-uri-issues.md
Files
- ATTRIBUTION.mdAdded by AutoGineer862 B
- LICENSELicence file added by AutoGineer1.1 KiB
- RUNBOOK.md3.3 KiB
- SKILL.md1.8 KiB
- agents/openai.yaml131 B
- concepts/oauth-flows.md22.2 KiB
- concepts/pkce.md13.8 KiB
- concepts/scopes-architecture.md6.7 KiB
- concepts/state-parameter.md8.1 KiB
- concepts/token-lifecycle.md15.1 KiB
- examples/device-flow.md371 B
- examples/pkce-implementation.md379 B
- examples/s2s-oauth-basic.md375 B
- examples/s2s-oauth-redis.md6.6 KiB
- examples/token-refresh.md373 B
- examples/user-oauth-basic.md376 B
- examples/user-oauth-mysql.md376 B
- references/classic-scopes.md2.5 KiB
- references/environment-variables.md863 B
- references/full-guide.md31.7 KiB
- references/granular-scopes.md1.8 KiB
- references/oauth-errors.md4.8 KiB
- troubleshooting/common-errors.md458 B
- troubleshooting/redirect-uri-issues.md229 B
- troubleshooting/scope-issues.md222 B
- troubleshooting/token-issues.md222 B
concepts/state-parameter.md
State Parameter (CSRF Protection)
The state parameter prevents Cross-Site Request Forgery (CSRF) attacks in OAuth flows.
What is CSRF in OAuth?
Attack Scenario (Without State)
1. Attacker initiates OAuth flow for victim's account
2. Attacker gets authorization code in callback
3. Attacker tricks victim into visiting callback URL with attacker's code
4. Victim's app exchanges code and links attacker's Zoom account to victim's app account
5. Attacker now has access to victim's app dataProtection (With State)
1. App generates random state before redirecting to OAuth
2. App stores state in user's session
3. Zoom includes state in callback
4. App verifies state matches session
5. If state doesn't match → Reject (CSRF detected)Implementation
Step 1: Generate Random State
const crypto = require('crypto');
app.get('/auth', (req, res) => {
// Generate cryptographically secure random state
const state = crypto.randomBytes(16).toString('hex');
// Store in session (server-side)
req.session.oauthState = state;
const authURL = new URL('https://zoom.us/oauth/authorize');
authURL.searchParams.set('response_type', 'code');
authURL.searchParams.set('client_id', process.env.ZOOM_CLIENT_ID);
authURL.searchParams.set('redirect_uri', process.env.ZOOM_REDIRECT_URL);
authURL.searchParams.set('state', state); // Include state
res.redirect(authURL.toString());
});Step 2: Verify State in Callback
app.get('/callback', async (req, res) => {
const { code, state } = req.query;
const sessionState = req.session.oauthState;
// Verify state matches
if (state !== sessionState) {
return res.status(403).send('Invalid state parameter - possible CSRF attack');
}
// Clean up state (one-time use)
delete req.session.oauthState;
// Proceed with token exchange
const tokens = await exchangeCodeForToken(code);
// ...
});State Parameter Flow
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ Your App │ │ User Session│ │ Zoom OAuth │
└──────┬──────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
│ 1. Generate state │ │
│ state = "abc123" │ │
│ │ │
│ 2. Store in session │ │
│────────────────────────────>│ session.oauthState = "abc123"
│ │ │
│ 3. Redirect to authorize with state │
│https://zoom.us/oauth/authorize?state=abc123 │
│───────────────────────────────────────────────────────────>│
│ │ │
│ 4. User authorizes │ │
│ │ │
│ 5. Redirect to callback with state │
│ /callback?code=xyz&state=abc123 │
│<───────────────────────────────────────────────────────────│
│ │ │
│ 6. Retrieve session state │ │
│<────────────────────────────│ sessionState = "abc123" │
│ │ │
│ 7. Verify state === sessionState │
│ "abc123" === "abc123" ✓ │ │
│ │ │
│ 8. Exchange code for token │ │
│───────────────────────────────────────────────────────────>│
│ │ │Common Mistakes
1. Not Verifying State
// ❌ WRONG: Accepting any state
app.get('/callback', async (req, res) => {
const { code } = req.query;
// No state verification!
await exchangeCodeForToken(code);
});// ✅ CORRECT: Verifying state
app.get('/callback', async (req, res) => {
const { code, state } = req.query;
if (state !== req.session.oauthState) {
return res.status(403).send('CSRF detected');
}
await exchangeCodeForToken(code);
});2. Using Predictable State
// ❌ WRONG: Predictable state
const state = Date.now().toString(); // Attacker can predict!// ✅ CORRECT: Cryptographically random state
const state = crypto.randomBytes(16).toString('hex');3. Reusing State
// ❌ WRONG: Not deleting state after use
if (state === req.session.oauthState) {
// State remains in session - can be reused!
}// ✅ CORRECT: Delete state after verification
if (state === req.session.oauthState) {
delete req.session.oauthState; // One-time use
}State + PKCE Together
For maximum security, use both:
app.get('/auth', (req, res) => {
// Generate state (CSRF protection)
const state = crypto.randomBytes(16).toString('hex');
req.session.oauthState = state;
// Generate PKCE (authorization code interception protection)
const { code_verifier, code_challenge } = generatePKCE();
req.session.pkceVerifier = code_verifier;
const authURL = new URL('https://zoom.us/oauth/authorize');
authURL.searchParams.set('response_type', 'code');
authURL.searchParams.set('client_id', CLIENT_ID);
authURL.searchParams.set('redirect_uri', REDIRECT_URI);
authURL.searchParams.set('state', state); // CSRF protection
authURL.searchParams.set('code_challenge', code_challenge); // PKCE
authURL.searchParams.set('code_challenge_method', 'S256');
res.redirect(authURL.toString());
});
app.get('/callback', async (req, res) => {
const { code, state } = req.query;
// Verify state (CSRF)
if (state !== req.session.oauthState) {
return res.status(403).send('CSRF detected');
}
// Exchange with code_verifier (PKCE)
const code_verifier = req.session.pkceVerifier;
const tokens = await exchangeCode(code, code_verifier);
// Clean up
delete req.session.oauthState;
delete req.session.pkceVerifier;
});Mobile Apps
For mobile apps without server-side sessions:
// Store state in secure storage
const state = generateRandomString();
await SecureStore.setItemAsync('oauth_state', state);
// Verify in callback
const storedState = await SecureStore.getItemAsync('oauth_state');
if (receivedState !== storedState) {
throw new Error('CSRF detected');
}
// Clean up
await SecureStore.deleteItemAsync('oauth_state');Best Practices
Always use state for User OAuth and Device Flow
Generate cryptographically random state (not timestamps, sequential IDs)
Store state server-side in session (not client-side cookies)
Delete state after verification (one-time use)
Combine with PKCE for mobile/SPA apps
Next Steps
Implement state + PKCE → ../examples/pkce-implementation.md
Understand PKCE → pkce.md
Fix OAuth errors → ../troubleshooting/common-errors.md
Requirements and compatibility
Written for: OpenAI Codex
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Zoom
- Publisher
- OpenAI (Official)
- Distributed by
- OpenAI
- Source
- openai/plugins · plugins/zoom/skills/oauth · commit d420d13
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from plugins/zoom/LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Zoom or OpenAI.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download zoom-oauth.zip