Description
After migrating from JWT to the server-to-server OAuth app, we are facing random 401 errors when calling the Zoom API. For example, when using the endpoint “GET https://api.zoom.us/v2/users” it sometimes returns a 401 error and sometimes it succeeds. We are always generating the token right before we make a request to the endpoint “GET https://api.zoom.us/v2/users”, which means that the token is never expired. We need to make recurrent checks in Zoom to make sure the Licenses are assigned to the users who are going to have a meeting, which means we are making a lot of requests to Zoom. This feature is part of the core of our application, which means that this is a critical issue for us.
The following is the code we are using to request the endpoint “GET https://api.zoom.us/v2/users”:
async getUsers(nextPageToken?: string): Promise<any> {
let url = `${this.url}/users?status=active`;
if (nextPageToken) {
url = url + `&next_page_token=${nextPageToken}`;
}
try {
const { data } = await axios.default.get(url, {
headers: { Authorization: `Bearer ${await this.refreshToken()}` },
});
return data;
} catch (err) {
this.errorLogger(err.message);
return {
users: [],
};
}
}
async refreshToken(): Promise<string> {
const authorizationHeader = Buffer.from(
`${this.zoomClientId}:${this.zoomClientSecret}`,
).toString("base64");
const { data } = await axios.default.post(
`https://zoom.us/oauth/token?grant_type=account_credentials&account_id=${this.zoomAccountId}`,
{},
{ headers: { Authorization: `Basic ${authorizationHeader}` } },
);
return data.access_token;
}
Take into account that the following attributes are defined within the class that contains those functions and they have been checked many times, besides the fact that it sometimes succeeds, which means there is no problem with their values:
- zoomAccountId
- zoomClientId
- zoomClientSecret
- url
Remember that it was just one example, it is not the only endpoint failing randomly.
Besides, we added all the scope types to the server-to-server OAuth app even when not all of them were needed, just to make sure it was not a problem with missing scopes.
Error
This is the entire error received in the cases when it fails (omitting the token value and using instead):
Error: Request failed with status code 401
at createError (/opt/nodejs/node_modules/axios/lib/core/createError.js:16:15)
at settle (/opt/nodejs/node_modules/axios/lib/core/settle.js:17:12)
at IncomingMessage.handleStreamEnd (/opt/nodejs/node_modules/axios/lib/adapters/http.js:244:11)
at IncomingMessage.emit (node:events:525:35)
at IncomingMessage.emit (node:domain:489:12)
at endReadableNT (node:internal/streams/readable:1358:12)
at processTicksAndRejections (node:internal/process/task_queues:83:21) {
config: {
url: 'https://api.zoom.us/v2/users?status=active',
method: 'get',
headers: {
Accept: 'application/json, text/plain, */*',
Authorization: 'Bearer <token>',
'User-Agent': 'axios/0.20.0'
},
transformRequest: [ [Function: transformRequest] ],
transformResponse: [ [Function: transformResponse] ],
timeout: 0,
adapter: [Function: httpAdapter],
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
maxBodyLength: -1,
validateStatus: [Function: validateStatus],
data: undefined
},
request: <ref *1> ClientRequest {
_events: [Object: null prototype] {
error: [Array],
abort: [Function (anonymous)],
aborted: [Function (anonymous)],
connect: [Function (anonymous)],
socket: [Function (anonymous)],
timeout: [Function (anonymous)],
finish: [Function: requestOnFinish]
},
_eventsCount: 7,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
destroyed: false,
_last: true,
chunkedEncoding: false,
shouldKeepAlive: false,
maxRequestsOnConnectionReached: false,
_defaultKeepAlive: true,
useChunkedEncodingByDefault: false,
sendDate: false,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
strictContentLength: false,
_contentLength: 0,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
_closed: false,
socket: TLSSocket {
_tlsOptions: [Object],
_secureEstablished: true,
_securePending: false,
_newSessionPending: false,
_controlReleased: true,
secureConnecting: false,
_SNICallback: null,
servername: 'api.zoom.us',
alpnProtocol: false,
authorized: true,
authorizationError: null,
encrypted: true,
_events: [Object: null prototype],
_eventsCount: 10,
connecting: false,
_hadError: false,
_parent: null,
_host: 'api.zoom.us',
_closeAfterHandlingError: false,
_readableState: [ReadableState],
_maxListeners: undefined,
_writableState: [WritableState],
allowHalfOpen: false,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: undefined,
_server: null,
ssl: [TLSWrap],
_requestCert: true,
_rejectUnauthorized: true,
parser: null,
_httpMessage: [Circular *1],
[Symbol(res)]: [TLSWrap],
[Symbol(verified)]: true,
[Symbol(pendingSession)]: null,
[Symbol(async_id_symbol)]: 432,
[Symbol(kHandle)]: [TLSWrap],
[Symbol(lastWriteQueueSize)]: 0,
[Symbol(timeout)]: null,
[Symbol(kBuffer)]: null,
[Symbol(kBufferCb)]: null,
[Symbol(kBufferGen)]: null,
[Symbol(kCapture)]: false,
[Symbol(kSetNoDelay)]: false,
[Symbol(kSetKeepAlive)]: false,
[Symbol(kSetKeepAliveInitialDelay)]: 0,
[Symbol(kBytesRead)]: 0,
[Symbol(kBytesWritten)]: 0,
[Symbol(connect-options)]: [Object],
[Symbol(RequestTimeout)]: undefined
},
_header: 'GET /v2/users?status=active HTTP/1.1\r\n' +
'Accept: application/json, text/plain, */*\r\n' +
'Authorization: Bearer <token>\r\n' +
'User-Agent: axios/0.20.0\r\n' +
'Host: api.zoom.us\r\n' +
'Connection: close\r\n' +
'\r\n',
_keepAliveTimeout: 0,
_onPendingData: [Function: nop],
agent: Agent {
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
defaultPort: 443,
protocol: 'https:',
options: [Object: null prototype],
requests: [Object: null prototype] {},
sockets: [Object: null prototype],
freeSockets: [Object: null prototype] {},
keepAliveMsecs: 1000,
keepAlive: false,
maxSockets: Infinity,
maxFreeSockets: 256,
scheduling: 'lifo',
maxTotalSockets: Infinity,
totalSocketCount: 1,
maxCachedSessions: 100,
_sessionCache: [Object],
[Symbol(kCapture)]: false
},
socketPath: undefined,
method: 'GET',
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
path: '/v2/users?status=active',
_ended: true,
res: IncomingMessage {
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 3,
_maxListeners: undefined,
socket: [TLSSocket],
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: '1.1',
complete: true,
rawHeaders: [Array],
rawTrailers: [],
aborted: false,
upgrade: false,
url: '',
method: null,
statusCode: 401,
statusMessage: 'Unauthorized',
client: [TLSSocket],
_consuming: false,
_dumped: false,
req: [Circular *1],
responseUrl: 'https://api.zoom.us/v2/users?status=active',
redirects: [],
[Symbol(kCapture)]: false,
[Symbol(kHeaders)]: [Object],
[Symbol(kHeadersCount)]: 54,
[Symbol(kTrailers)]: null,
[Symbol(kTrailersCount)]: 0,
[Symbol(RequestTimeout)]: undefined
},
aborted: false,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null,
reusedSocket: false,
host: 'api.zoom.us',
protocol: 'https:',
_redirectable: Writable {
_writableState: [WritableState],
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
_options: [Object],
_ended: true,
_ending: true,
_redirectCount: 0,
_redirects: [],
_requestBodyLength: 0,
_requestBodyBuffers: [],
_onNativeResponse: [Function (anonymous)],
_currentRequest: [Circular *1],
_currentUrl: 'https://api.zoom.us/v2/users?status=active',
[Symbol(kCapture)]: false
},
[Symbol(kCapture)]: false,
[Symbol(kBytesWritten)]: 0,
[Symbol(kEndCalled)]: true,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype] {
accept: [Array],
authorization: [Array],
'user-agent': [Array],
host: [Array]
},
[Symbol(kUniqueHeaders)]: null
},
response: {
status: 401,
statusText: 'Unauthorized',
headers: {
date: 'Tue, 06 Jun 2023 15:46:50 GMT',
'content-type': 'application/json;charset=UTF-8',
'content-length': '46',
connection: 'close',
'x-zm-trackingid': 'v=2.0;clid=us06;rid=WEB_fc4672f24a921f1e8f9f304ddafc763b',
'x-content-type-options': 'nosniff',
'cache-control': 'no-cache, no-store, must-revalidate, no-transform',
pragma: 'no-cache',
expires: 'Thu, 01 Jan 1970 00:00:00 GMT',
'set-cookie': [Array],
'x-zm-zoneid': 'VA2',
'cf-cache-status': 'DYNAMIC',
'report-to': '{"endpoints":[{"url":"https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=ZKz4pwclXvE%2Bhb2oeMls4VZBPeZg2NJkKhUzZn9AyTu%2FKEKhEYVQsPT2Ye%2Ful0GkDObOmm6gi6v8EmaMDk1nd1lvRIFDdDW42%2BxXRqlGXPorA6SWZW5ZnUKHEUiG"}],"group":"cf-nel","max_age":604800}',
nel: '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}',
server: 'cloudflare',
'cf-ray': '7d31ca5acd4d1b84-DUB',
'alt-svc': 'h3=":443"; ma=86400'
},
config: {
url: 'https://api.zoom.us/v2/users?status=active',
method: 'get',
headers: [Object],
transformRequest: [Array],
transformResponse: [Array],
timeout: 0,
adapter: [Function: httpAdapter],
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
maxBodyLength: -1,
validateStatus: [Function: validateStatus],
data: undefined
},
request: <ref *1> ClientRequest {
_events: [Object: null prototype],
_eventsCount: 7,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
destroyed: false,
_last: true,
chunkedEncoding: false,
shouldKeepAlive: false,
maxRequestsOnConnectionReached: false,
_defaultKeepAlive: true,
useChunkedEncodingByDefault: false,
sendDate: false,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
strictContentLength: false,
_contentLength: 0,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
_closed: false,
socket: [TLSSocket],
_header: 'GET /v2/users?status=active HTTP/1.1\r\n' +
'Accept: application/json, text/plain, */*\r\n' +
'Authorization: Bearer <token>\r\n' +
'User-Agent: axios/0.20.0\r\n' +
'Host: api.zoom.us\r\n' +
'Connection: close\r\n' +
'\r\n',
_keepAliveTimeout: 0,
_onPendingData: [Function: nop],
agent: [Agent],
socketPath: undefined,
method: 'GET',
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
path: '/v2/users?status=active',
_ended: true,
res: [IncomingMessage],
aborted: false,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null,
reusedSocket: false,
host: 'api.zoom.us',
protocol: 'https:',
_redirectable: [Writable],
[Symbol(kCapture)]: false,
[Symbol(kBytesWritten)]: 0,
[Symbol(kEndCalled)]: true,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype],
[Symbol(kUniqueHeaders)]: null
},
data: { code: 124, message: 'Invalid access token.' }
},
isAxiosError: true,
toJSON: [Function: toJSON]
}
How To Reproduce
We shared the code before, just use some server-to-server OAuth credentials to run it.