Zoom Apps SDK - OAuth Reference
OAuth flows for Zoom Apps: web-based redirect, In-Client, and third-party.
OAuth flows for Zoom Apps: web-based redirect, In-Client, and third-party.
OAuth flows for Zoom Apps: web-based redirect, In-Client, and third-party.
| Flow | UX | When to Use | |------|----|-------------| | **Web-based redirect** | Opens browser, redirect back | Initial install from Marketplace | | **In-Client OAuth** | Popup inside Zoom, no redirect | Subsequent authorizations (best UX) | | **Third-party OAuth** | External provider (Auth0, Google) | When your app needs non-Zoom auth |
All Zoom Apps OAuth must use PKCE (Proof Key for Code Exchange):
const crypto = require('crypto');
// Generate PKCE pair
const verifier = crypto.randomBytes(32).toString('hex');
const challenge = crypto.createHash('sha256')
.update(verifier)
.digest('base64url');
// verifier: stored server-side (never exposed to client)
// challenge: sent with authorization requestUser clicks "Add" in Marketplace
|
v
GET https://zoom.us/oauth/authorize
?client_id=YOUR_CLIENT_ID
&response_type=code
&redirect_uri=YOUR_REDIRECT_URI
&code_challenge=CHALLENGE
&code_challenge_method=S256
&state=RANDOM_STATE
|
v
User authorizes -> Zoom redirects to YOUR_REDIRECT_URI?code=AUTH_CODE&state=STATE
|
v
Backend validates state, exchanges code for tokens
|
v
Backend gets deeplink, redirects user to Zoom clientapp.get('/auth', async (req, res) => {
const { code, state } = req.query;
// Validate state (CSRF protection)
if (state !== req.session.state) {
return res.status(403).send('Invalid state');
}
// Exchange code for tokens
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: req.session.codeVerifier
},
headers: {
'Authorization': 'Basic ' + Buffer.from(
`${process.env.ZOOM_APP_CLIENT_ID}:${process.env.ZOOM_APP_CLIENT_SECRET}`
).toString('base64')
}
});
const { access_token, refresh_token, expires_in } = tokenResponse.data;
// Store tokens securely
req.session.tokens = { access_token, refresh_token, expires_at: Date.now() + expires_in * 1000 };
// Get deeplink to open app in Zoom
const deeplink = await axios.post('https://api.zoom.us/v2/zoomapp/deeplink',
{ action: '' },
{ headers: { 'Authorization': `Bearer ${access_token}` } }
);
res.redirect(deeplink.data.deeplink);
});No browser redirect - authorization happens inside Zoom:
// Frontend
const { codeChallenge, state } = await fetch('/api/auth/challenge').then(r => r.json());
zoomSdk.addEventListener('onAuthorized', async (event) => {
await fetch('/api/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: event.code, state: event.state })
});
});
await zoomSdk.authorize({ codeChallenge, state });See **[In-Client OAuth example](../examples/in-client-oauth.md)** for complete implementation.
POST https://zoom.us/oauth/token
Headers:
Authorization: Basic base64(CLIENT_ID:CLIENT_SECRET)
Parameters:
grant_type=authorization_code
code=AUTH_CODE
redirect_uri=YOUR_REDIRECT_URI
code_verifier=PKCE_VERIFIERResponse:
{
"access_token": "...",
"token_type": "bearer",
"refresh_token": "...",
"expires_in": 3600,
"scope": "zoomapp:inmeeting"
}Access tokens expire in 1 hour. Use refresh token to get new ones:
async function refreshTokens(refreshToken) {
const response = await axios.post('https://zoom.us/oauth/token', null, {
params: {
grant_type: 'refresh_token',
refresh_token: refreshToken
},
headers: {
'Authorization': 'Basic ' + Buffer.from(
`${process.env.ZOOM_APP_CLIENT_ID}:${process.env.ZOOM_APP_CLIENT_SECRET}`
).toString('base64')
}
});
return response.data; // { access_token, refresh_token, expires_in }
}**Note:** Refresh tokens are single-use. Each refresh returns a new refresh_token.
After web OAuth, get a deeplink to open your app in Zoom:
const response = await axios.post('https://api.zoom.us/v2/zoomapp/deeplink',
{ action: '' },
{ headers: { 'Authorization': `Bearer ${accessToken}` } }
);
const { deeplink } = response.data;
// Redirect user to this URL to open app in Zoom client| Scope | Description | |-------|-------------| | `zoomapp:inmeeting` | In-meeting functionality (most common) | | `user:read` | Read user profile | | `meeting:read` | Read meeting details | | `meeting:write` | Create/modify meetings |
| Pattern | When to Use | |---------|-------------| | **Redis** | Multi-instance production servers | | **Session cookie** | Simple single-server apps | | **Firestore** | Serverless (Firebase) | | **Encrypted database** | Complex apps with user accounts |