PKCE OAuth and the Plugin SDK for Windows

View as Markdown

Authorization Code with Proof Key for Code Exchange (PKCE) is an OAuth flow similar to the standard Authorization Code flow, except it doesn't require that you have a backend server to get the authorization token.

Prerequisites

  • Understanding of C++ or C#.
  • Visual Studio 2019 or later.
  • Zoom Plugin SDK for Windows.
  • Zoom Plugin SDK and OAuth credentials for a Marketplace app with the scopes your plugin needs.

After you've created your project, downloaded the Plugin SDK files, and integrated the SDK into your app, set up a custom URL scheme. Then authenticate the user with PKCE and use the returned access token to call the Zoom REST APIs your plugin's scopes authorize.

Set up a custom URL scheme

Follow the steps on Microsoft's web site to set up a custom URL scheme for Windows .

Authenticate users with PKCE

Now that your app is configured to handle your custom URI scheme, start implementing the PKCE OAuth flow.

Generate the code verifier and challenge

First, generate a code verifier using the code below, then hash the verifier to create a code challenge. Host these in a dedicated CodeChallengeHelper class. A field inside the class will also be needed to store the verifier, since the same verifier used to generate the challenge must be provided when requesting an access token.

Within that same class, define methods createCodeVerifier to generate a new verifier, and getCodeChallenge to create a code challenge using the verifier.

class CodeChallengeHelper
{
public:
 static std::string verifier;
 static void createCodeVerifier();
 static std::string getCodeChallenge();
};

Send a request to authentication server

After you've created the challenge, include it as a query parameter on the request to the authorization server.

string url_encode(const string& value) {
    ostringstream escaped;
    escaped.fill('0');
    escaped << hex;

    for (string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) {
        string::value_type c = (*i);

        // Keep alphanumeric and other accepted characters intact
        if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
            escaped << c;
            continue;
        }

        // Any other characters are percent-encoded
        escaped << uppercase;
        escaped << '%' << setw(2) << int((unsigned char)c);
        escaped << nouppercase;
    }

    return escaped.str();
}

string getAuthURL()
{
    std::string url = "https://zoom.us/oauth/authorize";
    url += "?response_type=code";
    url += "&client_id=" + CLIENT_ID or public client ID;
    url += "&code_challenge=" + CodeChallengeHelper::getCodeChallenge();
    url += "&redirect_uri=" + url_encode(REDIRECT_URI);
    url += "&code_challenge_method=" + string("S256");
    return url;
}

Launch the URL in a browser to allow the user to authenticate

Finally, create a Windows process to allow the user to authenticate through the default browser.

#include <shellapi.h>

void launchBrowserForAuth(){
    std::string oAuthUrl = getAuthURL();
    ShellExecuteW(NULL, L"open", std::wstring(oAuthUrl.begin(), oAuthUrl.end()).c_str(), NULL, NULL, SW_SHOW);
}

Handle the response

After your app executes the request in the previous step, the user will see the Zoom login in a browser. Once they've logged in and consented to your app's scopes, the Windows Desktop Application you set up earlier in the Setup URI Scheme step will be started. From here, you will need to exchange the authorization code for an access token and include the code verifier to prove that you were the one who initiated the authorization.

#include "cpr/cpr.h"
#include "json/json.h"

string requestAccessToken(string code)
{
  cpr::Response r = cpr::Post(
      cpr::Url{ "https://zoom.us/oauth/token" },
      cpr::Authentication{ CLIENT_ID, CLIENT_SECRET, cpr::AuthMode::BASIC },
      cpr::Payload{
          {"grant_type", "authorization_code"},
          {"code", code},
          {"redirect_uri", REDIRECT_URI},
          {"code_verifier", CodeChallengeHelper::verifier}
      });

    if (r.status_code == 200) {
        Json::Value root;
        Json::Reader reader;
        if (reader.parse(r.text, root))
            return root.get("access_token", "").asString();
    }
    return string();
}

Two third party libraries are used in the code. Install them with vcpkg.

./vcpkg install cpr
./vcpkg install jsoncpp

To get the code used as the code query param, parse it from the URL used to redirect to your app. These snippets rely on credentials obtained from the Zoom Marketplace.

  string parseCode(std::string dataUri)
  {
      size_t queryStart = dataUri.find('?');
      if (queryStart == std::string::npos)
          return string();

      string queryString = dataUri.substr(queryStart + 1);
      // Parse all query params
      std::map<string, string> params;
      std::istringstream stream(queryString);
      string pair;
      while (std::getline(stream, pair, '&'))
      {
          size_t eq = pair.find('=');
          if (eq != string::npos)
              params[pair.substr(0, eq)] = pair.substr(eq + 1);
      }

      if (params.count("error"))
      {
          handleAuthError(params["error"]);
          return string();
      }

      if (params.count("code"))
          return params["code"];

      return string();
  }
  int main(int argc, char** argv)
  {
      if (argc > 1)
      {
          std::string code = parseCode(argv[1]);
      }
      // ...
  }

The returned access_token is a user-level OAuth 2.0 Bearer token. Send it on Zoom REST API requests as Authorization: Bearer <access_token>. It will only be able to call endpoints covered by the scopes granted to your Marketplace app.