Authorization and deep linking
In this section, you'll implement the installation and authorization flows to install a chatbot in a Zoom workspace, authenticate API requests, and deep link users between a browser-based application and the Zoom desktop client. This enables seamless transitions from SaaS web apps to native collaboration environments.
By the end of this section, you’ll have:
- A secure Chat integration using OAuth 2.0
- Seamless deep linking between the browser and Zoom client
- Reusable server endpoints for message delivery and future features
Implement OAuth 2.0 authentication
Add two routes to handle the OAuth 2.0 flow: one to initiate login and another to process the callback from Zoom.
| Endpoint | Description |
|---|---|
/login | Redirects users to Zoom's OAuth consent screen using the app's client ID and redirect URI |
/callback | Handles the redirect from Zoom, validates the state parameter, exchanges the authorization code for an access token, and stores tokens in the session for later use |
File Path
routes/oauth-routes.js
Code Snippet
import express from "express";
import crypto from "crypto";
import {
buildBasicAuth,
exchangeCodeForAccessToken,
} from "../utils/zoom-api.js";
const router = express.Router();
// OAuth: start
router.get("/login", (req, res) => {
try {
const state = crypto.randomBytes(16).toString("hex");
req.session.oauth_state = state;
const url = buildBasicAuth({
clientId: process.env.ZOOM_CLIENT_ID,
redirectUri: process.env.ZOOM_REDIRECT_URI,
state,
});
console.log("Redirecting to Zoom OAuth URL:", url);
return res.redirect(url);
} catch (e) {
console.error("OAuth login error:", e);
return res.status(500).send("OAuth not configured.");
}
});
// OAuth: callback
router.get("/callback", async (req, res) => {
try {
const { code, state, error, error_description } = req.query;
if (error) {
console.warn("Zoom authorization error:", error, error_description);
return res.status(400).send(`Authorization error: ${error}`);
}
if (!code || !state)
return res.status(400).send("Missing code or state.");
if (state !== req.session.oauth_state)
return res.status(400).send("Invalid state.");
delete req.session.oauth_state; // one-time use
const tokens = await exchangeCodeForAccessToken({
code,
redirectUri: process.env.ZOOM_REDIRECT_URI,
clientId: process.env.ZOOM_CLIENT_ID,
clientSecret: process.env.ZOOM_CLIENT_SECRET,
});
// Demo: store tokens in session (use DB/secret store in prod)
req.session.zoomTokens = tokens;
// Option A: redirect to a confirmation page with a button to open in Zoom
return res.redirect("/dashboard");
// Option B: redirect straight into Zoom via a JID deep link:
// return res.redirect(process.env.ROBOT_ZOOM_BOT_JID || '[https://zoom.us/launch/chat?jid=robot_example@xmpp.zoom.us](https://zoom.us/launch/chat?jid=robot_example@xmpp.zoom.us)');
} catch (e) {
console.error("OAuth callback error:", e);
return res.status(500).send("Token exchange failed.");
}
});
export default router;
Add deep linking to the chatbot
After the user authorizes your app, use a deep link to open the chatbot directly in Chat.
File path
views/dashboard.html
Code snippet
<script>
const chatbotJid = "robot_v1nhrah1hrqhuexexb5nn5-a@xmpp.zoom.us"; // Replace with your bot JID
document.getElementById("zoom-button").addEventListener("click", () => {
const link = `https://zoom.us/launch/chat?jid=${encodeURIComponent(chatbotJid)}`;
window.open(link, "_blank", "noopener,noreferrer");
});
</script>
Transition from browser-based app to Zoom client
Use this pattern to create a smooth transition from your browser-based app to the Zoom client, enhancing user experience after authorization.