External meeting authorization service

Overview

You can restrict access to a meeting in several ways. One option is to hand the decision to a service of your own, so the rules your systems already enforce decide who may join, whether those rules live in a customer portal, a patient record, or a case file.

This page describes how to build that service.

Using this page with an AI assistant

If you are using an AI coding assistant, copy the text below into it and add a sentence describing what you want to build. AI assistants accessing this page receive a separate, token-optimized version of our full developer documentation, specifically tailored by our engineers for AI coding assistants.

Before writing any code, read this page:
https://www.veeting.com/en/developer-documentation/external-meeting-authorization-service

It documents how to authorize meeting participants with my own
service. Keep these five rules in mind:

1. The token exchange is server to server. The URL contains a shared
   secret, so never call that URL from a browser and never log it.
2. The exchange succeeded only if responseCode is 0 AND
   data.responseState is "VALID" AND data.accessToken is not null.
   An expired or reused request token still returns HTTP 200 and
   responseCode 0. Never redirect with a null token.
3. Redirect back to /meeting/<MEETING-TOKEN>/join with
   meetingAccessToken as a query parameter.
4. The exchange uses the 24-character meetingId. The redirect back
   uses the dashed meetingToken. They are not interchangeable.
5. The request token proves the platform sent the person to me. It
   does NOT authenticate them. I still have to do that.

What I want to build:

How the exchange works

The flow is a redirect handshake between three parties, similar in shape to OAuth.

  1. A participant opens a restricted meeting. The platform mints a request token and redirects the browser to your service, passing that token along.
  2. Your service authenticates the person however you like and decides whether they may join.
  3. If they may join, your service exchanges the request token for an access token in a server-to-server call.
  4. Your service redirects the browser back to the meeting with the access token attached. That token grants the browser session access to the meeting.

A browser opens a restricted meeting, is redirected to your service with a request token, your service authenticates the person and exchanges the token for an access token, then redirects the browser back to the meeting

The request token proves the platform sent the participant to you. The access token proves you sent them back.

Step 1: Receive the redirect

Implement an endpoint that responds to HTTP GET. We redirect the browser to that endpoint, appending at least these query parameters:

ParameterDescription
hostnameThe hostname of the meeting platform, for example meeting.example.org
meetingIdThe 24-character meeting ID, for example 5f521a93c20ff6721fbb6a6c
meetingTokenThe dashed meeting number, for example 0000-0000-0000-0000
requestTokenA long random string that is valid for this meeting only

We may append additional parameters, so read the ones you need by name rather than by position.

If your endpoint is https://external.example.org/auth, the browser arrives at:

https://external.example.org/auth?hostname=webmeeting.example.com&meetingId=5f521a93c20ff6721fbb6a6c&meetingToken=8320-2640-2482-3499&requestToken=dedf1722-661f-4004-9aaf-d3e56c498859-a27fd10f-b697-4c83-bca0-cb764cfd6c43

Step 2: Exchange the request token

After you decide that the participant may join, call us to exchange the request token for an access token:

GET https://<HOSTNAME>/api/v6/meeting-room/auth/<SECRET>/access-token/<MEETING-ID>/<REQUEST-TOKEN>
PlaceholderValue
HOSTNAMEThe hostname from the redirect
SECRETThe shared secret you configured (see below)
MEETING-IDThe 24-character meetingId from the redirect
REQUEST-TOKENThe requestToken from the redirect

We check that the request token was issued for that meeting. On success:

{
  "responseCode": 0,
  "data": {
    "meetingId": "5f521a93c20ff6721fbb6a6c",
    "accessToken": "81430667-540e-4755-b32a-b5c51f704c7b-03526573-1494-48fb-a648-e80073275976",
    "responseState": "VALID"
  }
}

Success takes three checks: responseCode is 0, data.responseState is "VALID", and data.accessToken is not null. An unknown, expired or already used request token still returns HTTP 200 and responseCode: 0, but with data.accessToken: null and data.responseState set to "EXPIRED", "INVALID_REQUEST_TOKEN" or "INVALID_CHALLENGE". A wrong secret or an unconfigured service returns -33. See API usage for the response envelope.

This call carries your secret in the URL. Make the call server to server, never from the browser, and never log the full URL.

Step 3: Redirect back to the meeting

Send the browser to the meeting room with the access token attached:

https://<HOSTNAME>/meeting/<MEETING-TOKEN>/join?meetingAccessToken=<ACCESS-TOKEN>
PlaceholderValue
HOSTNAMEThe hostname from the redirect
MEETING-TOKENThe dashed meeting number from the redirect, not the 24-character one
ACCESS-TOKENThe accessToken you just received

You can add query parameters to smooth the arrival, most usefully the values you already know from authenticating the person:

https://<HOSTNAME>/meeting/<MEETING-TOKEN>/join?meetingAccessToken=<ACCESS-TOKEN>&participantName=<NAME>&participantEmail=<EMAIL>

Configuring the platform

In Platform Settings, open System Configuration, then Meeting Room. Set the default authentication type for meetings to External Service. Two fields appear.

The external authorization service fields in the platform's system configuration

Enter the full URL of your endpoint in "URL of external authentication server" and your chosen secret in "API Key of external authentication server". We store the secret as a hash and check every token exchange against it.

The secret is yours to invent. Treat it like a password: make it long and random, and rotate it if it ever leaks.

A minimal implementation

const express = require("express");
const app = express();
const port = 3000;

// Invent this secret yourself and configure the same value in the platform.
const SECRET = process.env.VEETING_SHARED_SECRET;

app.get("/auth", async (req, res) => {
  const { hostname, meetingId, meetingToken, requestToken } = req.query;

  // Decide here whether this person may join, using your own session,
  // directory or customer database. Redirect them away if they may not.
  if (!(await mayJoin(req, meetingId))) {
    return res.status(403).send("Not authorized to join this meeting");
  }

  // Server to server: this URL contains the shared secret.
  const url = `https://${hostname}/api/v6/meeting-room/auth/${SECRET}`
    + `/access-token/${meetingId}/${requestToken}`;

  const response = await fetch(url).then((r) => r.json());

  // HTTP 200 and responseCode 0 are not enough on their own: an expired
  // or reused request token also returns them, with a null accessToken.
  const data = response.data;
  if (response.responseCode !== 0 || !data
      || data.responseState !== "VALID" || !data.accessToken) {
    return res.status(502).send("Could not obtain an access token");
  }

  const accessToken = data.accessToken;

  res.redirect(`https://${hostname}/meeting/${meetingToken}/join`
    + `?meetingAccessToken=${encodeURIComponent(accessToken)}`);
});

app.listen(port);

Common mistakes

  • Redirecting to the wrong path: the meeting room is at /meeting/<MEETING-TOKEN>/join.
  • Using the 24-character meetingId in the redirect back, which needs the dashed meetingToken. The exchange call is the other way around.
  • Treating HTTP 200 and responseCode: 0 on the exchange as success. Also check that data.responseState is "VALID" and data.accessToken is not null.
  • Calling the exchange endpoint from the browser, which exposes your secret.
  • Logging the exchange URL, which also exposes your secret.
  • Waving the person through at your endpoint without actually authenticating them: the request token proves where they came from, not who they are.

Not sure how to best implement your project?

Contact our team to discuss the details.