Subchapter 114.57
use-cases/saas-app-oauth-integration.mdMarkdown5 KBView on GitHub
Create a multi-tenant SaaS application that integrates with Zoom using User OAuth for per-user authorization.
References
App TypesYou’re building a meeting scheduling SaaS that needs to:
User Browser → Your SaaS App → Zoom OAuth → Zoom APIs
↓
Database (user tokens)Configure app in Zoom Marketplace:
https://yourapp.com/oauth/callbackmeeting:write, meeting:read, user:readSee: oauth/concepts/oauth-flows.md#user-authorization-oauth
// Redirect user to Zoom authorization
app.get('/connect-zoom', (req, res) => {
const state = generateSecureState();
req.session.oauthState = state;
res.redirect(
`https://zoom.us/oauth/authorize?` +
`response_type=code&` +
`client_id=${CLIENT_ID}&` +
`redirect_uri=${REDIRECT_URI}&` +
`state=${state}`
);
});See: oauth/examples/user-oauth-mysql.md
Store user tokens with encryption:
// After OAuth callback
const { access_token, refresh_token } = await exchangeCode(code);
await db.users.update({
zoom_access_token: encrypt(access_token),
zoom_refresh_token: encrypt(refresh_token),
token_expiry: Date.now() + 3600000
}, { where: { id: userId } });See: oauth/concepts/token-lifecycle.md#user-oauth--device-flow-token-lifecycle
// Automatically refresh expired tokens
const zoomTokenMiddleware = async (req, res, next) => {
const user = await db.users.findByPk(req.session.userId);
if (isTokenExpired(user.token_expiry)) {
const newTokens = await refreshToken(user.zoom_refresh_token);
await updateUserTokens(user.id, newTokens);
req.zoomToken = newTokens.access_token;
} else {
req.zoomToken = decrypt(user.zoom_access_token);
}
next();
};See: oauth/examples/token-refresh.md
app.post('/api/meetings', zoomTokenMiddleware, async (req, res) => {
const meeting = await axios.post(
'https://api.zoom.us/v2/users/me/meetings',
{
topic: req.body.topic,
type: 2, // Scheduled meeting
start_time: req.body.start_time,
duration: req.body.duration
},
{
headers: { Authorization: `Bearer ${req.zoomToken}` }
}
);
res.json(meeting.data);
});See: zoom-rest-api skill for endpoint documentation
MUST encrypt tokens at rest:
const crypto = require('crypto');
function encrypt(text) {
const cipher = crypto.createCipher('aes-256-cbc', process.env.CIPHER_KEY);
return cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
}See: oauth/examples/user-oauth-mysql.md#token-encryption
Prevent CSRF attacks:
const state = crypto.randomBytes(16).toString('hex');
req.session.oauthState = state; // Verify in callbackSee: oauth/concepts/state-parameter.md
Listen for deauthorization webhook:
app.post('/webhooks/zoom', (req, res) => {
if (req.body.event === 'app_deauthorized') {
const userId = req.body.payload.user_id;
await deleteUserZoomTokens(userId);
}
});Chain to: webhooks skill for webhook setup
try {
await refreshToken(user.zoom_refresh_token);
} catch (error) {
if (error.code === 4735) {
// Refresh token expired - prompt re-authorization
res.redirect('/connect-zoom');
}
}See: oauth/troubleshooting/token-issues.md
meeting-automation.md - Advanced meeting schedulinguser-and-meeting-creation.md - Bulk user/meeting operationsrecording-download-pipeline.md - Download recordings via API