BUG downloading cloud recordings with access token set results in an invalid response

#1

Steps to reproduce:

  1. List recordings for a meeting
  2. Use recording meeting with query parameter access_token set with a valid access token.
  3. 200 response received with body {"status":false,"errorCode":401,"errorMessage":"java.lang.IllegalArgumentException: Scope cannot be null","result":null}

However not setting the access_token query parameter will successfully download the correct recording.

#3

Just wanted to post an update, the header is being set correctly to a 401 in that situation, my mistake. However the behaviour seems inconsistent.

Is this by design blocking a request with an access token while letting one through without one? What am I supposed to do with password protected files?

#4

Setting the recording to private and attempting to download without an access token does return a 200 response, even though it’s an html error page.

#5

And finally tried one last time with both public and private recordings, setting the authorization header returns a 401 with this response.
{"status":false,"errorCode":401,"errorMessage":"java.lang.IllegalArgumentException: urn:zoom:connect:clientid:<my client id> not support","result":null}.

#6

Hi @matt,

Are you using one of our SDK’s ? or are you using your custom implementation?

Can you please provide us with a sample request that you are trying to send?

Thanks!

#7

Hey @Ojus, I wouldn’t call sending requests to the rest api without an sdk custom, just regular usage of a web api.

Anyway here are results that can be duplicated when the recording is public or private. I have redacted all sensitive information.

Setting the access token in the url query

First is setting the access token query parameter, this is usually done with a webhook that includes a download token. But a download token is not available outside the context of the webhook so here it is being substituted by the user’s access token obtained from their refresh token.

curl -v "https://api.zoom.us/recording/download/recording/download/<< download id>>?access_token=<< access token obtained from refresh token>>"
*   Trying 52.202.62.237...
* TCP_NODELAY set
* Connected to api.zoom.us (52.202.62.237) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: OU=Domain Control Validated; CN=*.zoom.us
*  start date: Mar 25 19:38:42 2019 GMT
*  expire date: Mar 25 19:38:42 2021 GMT
*  subjectAltName: host "api.zoom.us" matched cert's "*.zoom.us"
*  issuer: C=US; ST=Arizona; L=Scottsdale; O=GoDaddy.com, Inc.; OU=http://certs.godaddy.com/repository/; CN=Go Daddy Secure Certificate Authority - G2
*  SSL certificate verify ok.
> GET /recording/download/<< download id>>?access_token=<< access token obtained from refresh token>> HTTP/1.1
> Host: api.zoom.us
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< Date: Thu, 16 May 2019 20:27:34 GMT
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Server: ZOOM
< x-zm-trackingid: WEB_39d8a5536bcac863bc669f7ae0edbe9c
< Set-Cookie: _zm_mtk_guid=<<cookie stuff>>; Domain=.zoom.us; Expires=Tue, 03-Jun-2087 23:41:41 GMT; Path=/; Secure
<
* Connection #0 to host api.zoom.us left intact
{"status":false,"errorCode":401,"errorMessage":"java.lang.IllegalArgumentException: Scope cannot be null","result":null}%

What’s funky is we have all the correct scopes being set, and the access token is being validated because I messed up and forgot a letter in the access token which resulted in a different error stating that it was an invalid access token.

Setting the Authorization Header

Next is setting the Authorization header, which is how the rest of the zoom api performs, this results in a different error seen here.

curl -v -H "Authorization: Bearer <<access token obtained from client refresh token>>" "https://api.zoom.us/recording/download/<<download id>>"
*   Trying 52.202.62.238...
* TCP_NODELAY set
* Connected to api.zoom.us (52.202.62.238) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: OU=Domain Control Validated; CN=*.zoom.us
*  start date: Mar 25 19:38:42 2019 GMT
*  expire date: Mar 25 19:38:42 2021 GMT
*  subjectAltName: host "api.zoom.us" matched cert's "*.zoom.us"
*  issuer: C=US; ST=Arizona; L=Scottsdale; O=GoDaddy.com, Inc.; OU=http://certs.godaddy.com/repository/; CN=Go Daddy Secure Certificate Authority - G2
*  SSL certificate verify ok.
> GET /recording/download/<<download id>> HTTP/1.1
> Host: api.zoom.us
> User-Agent: curl/7.54.0
> Accept: */*
> Authorization: Bearer <<access token obtained from client refresh token>>
>
< HTTP/1.1 401 Unauthorized
< Date: Thu, 16 May 2019 20:31:17 GMT
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Server: ZOOM
< x-zm-trackingid: WEB_bfeee8f55376baf1272152960b86a627
< Set-Cookie: _zm_mtk_guid=<<cookie stuff>>; Domain=.zoom.us; Expires=Tue, 03-Jun-2087 23:45:24 GMT; Path=/; Secure
<
* Connection #0 to host api.zoom.us left intact
{"status":false,"errorCode":401,"errorMessage":"java.lang.IllegalArgumentException: urn:zoom:connect:clientid:<<my client id>> not support","result":null}%

What’s crazy here is it even returned my client id.

Success

Here is a correct one for a public recording, this is nothing special as I have no auth provided.

curl -v "https://api.zoom.us/recording/download/<<download id>>"
*   Trying 52.202.62.238...
* TCP_NODELAY set
* Connected to api.zoom.us (52.202.62.238) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: OU=Domain Control Validated; CN=*.zoom.us
*  start date: Mar 25 19:38:42 2019 GMT
*  expire date: Mar 25 19:38:42 2021 GMT
*  subjectAltName: host "api.zoom.us" matched cert's "*.zoom.us"
*  issuer: C=US; ST=Arizona; L=Scottsdale; O=GoDaddy.com, Inc.; OU=http://certs.godaddy.com/repository/; CN=Go Daddy Secure Certificate Authority - G2
*  SSL certificate verify ok.
> GET /recording/download/<<download id>> HTTP/1.1
> Host: api.zoom.us
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Thu, 16 May 2019 20:23:18 GMT
< Content-Type: txt;charset=UTF-8
< Content-Length: 495
< Connection: keep-alive
< Server: ZOOM
< x-zm-trackingid: WEB_0c253a7085e655c599d8bbfdcfc85bc6
< Set-Cookie: _zm_mtk_guid=<<more cookie stuff>>; Domain=.zoom.us; Expires=Tue, 03-Jun-2087 23:37:24 GMT; Path=/; Secure
< X-Robots-Tag: noindex, nofollow
< X-Content-Type-Options: nosniff
< Set-Cookie: cred=<<cookie stuff>>; Path=/; Secure; HttpOnly
< Set-Cookie: _zm_page_auth=<<hide this>>; Domain=.zoom.us; Path=/; Secure; HttpOnly
< p3p: CP="NOI ADM DEV PSAi COM NAV OUR OTR STP IND DEM"
< Set-Cookie: _zm_ssid=<<more cookie stuff>>; Domain=.zoom.us; Path=/; Secure; HttpOnly
< Content-Disposition: attachment;filename=GMT20190513-223817_-No-title-.transcript.vtt
< Strict-Transport-Security: max-age=31536000
< X-XSS-Protection: 1; mode=block
<
WEBVTT

<< web vtt file stuff>>

* Connection #0 to host api.zoom.us left intact

So far the only time I can download a private recording on behalf of a user is when the webhook provides a download token, and a public one can only be downloaded by that token or without authentication.

#8

Hi @matt,

Thank you for sending a detailed response.

It is expected behavior that a download token is required to download a recording.
If the recording is a private recording, it is only available internally (i.e. for account members only). The download token is nothing but an OAuth Token, which allows you to access the user’s recording without exposing his password. The other way that you can access a private recording is by logging in as the user.

Since public recording is available to everyone (who has the download link), you do not need to authenticate. You still need the token to access the recording.

If you need to keep a recording public, but want to share it only with a limited number of people, I would recommend you enable the password protect option.

I hope that answers your question and resolves the issue.

Thanks,

#9

Hey @Ojus, not particularly. How would I get a download token if this is not coming from a webhook? I cannot seem to find any documentation for getting a download token.

#10

The webhook includes the download link. Are you saying that the download link in the weebhook is not working?

This is what the payload for a recording complete looks like:

{
  "event": "string",
  "payload": {
    "account_id": "string",
    "object": {
      "id": "string",
      "uuid": "string",
      "host_id": "string",
      "topic": "string",
      "type": "integer",
      "start_time": "string",
      "timezone": "string",
      "duration": "integer",
      "share_url": "string",
      "total_size": "string",
      "recording_count": "integer",
      "recording_files": [
        {
          "id": "string",
          "meeting_id": "string",
          "recording_start": "string",
          "recording_end": "string",
          "file_type": "string",
          "file_size": "number",
          "play_url": "string",
          "download_url": "string",
          "status": "string",
          "recording_type": "string"
        }
      ]
    }
  }
}

You can find the recording webhook events here: https://marketplace.zoom.us/docs/api-reference/webhook-reference/recording-events