Use Authorization Code + PKCE for RingCentral API in Client App

Embbnux Ji
RingCentral Developers
5 min readAug 6, 2020

RingCentral APIs use OAuth 2.0 for authorization. But which grant flow is the best practice for client side apps, such as desktop, mobile app and web (Single Page Apps)? The answer to that is authorization code with Proof Key for Code Exchange. In this article, I will introduce and show you how to implement authorization code with PKCE flow in Single Page Apps.

Useful Links

  1. RingCentral APIs reference: Authorization in RingCentral APIs
  2. IETF link: Proof Key for Code Exchange by OAuth Public Clients

Authorization code and Implicit grant flow

Authorization code grant flow:

We can get full steps of authorization code grant flow in following diagram. A Third party app will need the RingCentral client id and client secret to exchange and refresh the access token. Third party app will stay authorized if it refreshes the RingCentral access token before the refresh token has expired, and will get a new refresh token and access token when it refreshes.

authorization code flow of RingCentral

Implicit grant flow

We can see the full steps of the implicit grant flow in the following diagram. It is designed for client-side app. Third party app only need client id. The login server will return access token to browser directly. Third party app can’t refresh access token. To get new token, user need to visit login server again. We can use a hidden iframe to implement token refreshing flow. The login server use Browser session to remember user login status. The Browser session will be expired in half hour. So if third party app don’t refresh in half hour, third party app will be unauthorized after access token expired.

Implicit grant flow of RingCentral APIs

Why we need Authorization Code with PKCE

In a client-side app such as Single Page Apps — the app will need to implement auth flow on the client side without server. Therefore, authorization code grant flow is not a suitable option for a client-side app, because we need to store app client id and secret on the client side. It is not safe to restore client secret on the client side.

The implicit grant flow is designed for a client-side app. But in the implicit grant flow, a user will be unauthorized if the user closes the app for more than a half hour. The implicit grant flow returns the access token in the redirect URI. So the access token is exposed in the Browser history which may cause security issues.

Authorization code with PKCE grant flow is a new solution for a client-side app. It is a security enhancement for the authorization code flow. The third party app will only need the client id. But when the user starts the authorization, it will generate a cryptographically-random string code_verifier. And encrypt the code_verifier into the code_challenge. The app will pass the code_challenge into the RingCentral login web page. In the code exchanging step, the app will use the authorization code and the code_verifier to exchange the access token and refresh token. The RingCentral API server will encode the code_verifier and verify it with the code_challenge that passed previously. The code_verifier will be changed for every authorization request.

authorization code with PKCE of RingCentral APIs

Compared to the authorization code flow, it doesn’t store the app secret on the app side, so it can work with a client-side app. And compared to an implicit grant flow, it can refresh the access token by refresh_token. The authorization code is exposed in the Browser history, but the code can’t be used without the code_verifier .

Implement Authorization Code with PKCE flow with RingCentral JS SDK

We have implemented the Authorization code with the PKCE flow in the RingCentral JS SDK v4.

To use the PKCE flow, just pass the usePKCE into the `loginUrl` function, and everything will be the same as the authorization code or implicit flow:

var rcsdk = new RingCentral.SDK({
server: RingCentral.SDK.server.production,
clientId: 'yourClientId',
redirectUri: 'yourRedirectURI'
});
// generate login url with PKCE
var loginUrl = rcsdk.loginUrl({ usePKCE: true });
rcsdk.loginWindow({url: loginUrl}) // will popup login window
.then(function (loginOptions){
return rcsdk.login(loginOptions);
})
.then(...)
.catch(...);

The SDK will help generate the code_verifier and the code_challenge, and also will pass them to the RingCentral server.

You can get the demo code here.

How we implement PKCE from authorization code flow:

Firstly, we need to generate code_verifier. The code_verifier is just a random string:

import {randomBytes, createHash} from 'crypto';function _generateCodeVerifier() {
let codeVerifier: any = randomBytes(32);
codeVerifier = codeVerifier
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
return codeVerifier;
}

We need to make sure the string don’t include “+”, “/” and “=”.

code_challenge is `sha256` encoded value from code_verifier :

this._codeVerifier = '';
if (usePKCE) {
this._codeVerifier = this._generateCodeVerifier();
query.code_challenge = createHash('sha256')
.update(this._codeVerifier)
.digest()
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
query.code_challenge_method = 'S256';
}
return this.createUrl(`${this._authorizeEndpoint}?${qs.stringify(query)}`, {addServer: true});

The code_verifier need to generated for every authorization request. We update it when we create authorization URI. And save it in local, and generate code_challenge from code_verifier . code_challenge and code_challenge_method need to be added into authorization URI query.

After user login successfully in authorization URI, we will get authorization code from redirect URI.

Then we need to exchange access token with “authorization code” and code_verifier :

body.grant_type = 'authorization_code';
body.code = code;
body.redirect_uri = this._redirectUri;
if (this._codeVerifier && this._codeVerifier.length > 0) {
body.code_verifier = this._codeVerifier;
body.client_id = this._clientId;
skipAuthHeader = true;
}
response = await this._tokenRequest(tokenEndpoint, body, skipAuthHeader);

We also need to pass client id in request body, and skip authorization header.

As we can only exchange token with `authorization code` and `code_verifier`, so it can keep safe even your `authorization code` is leaked.

To get full code for PKCE implement, please go here.

Conclusion

In this article, we introduced how to authorize RingCentral APIs with the authorization code flow using PKCE, and compared it with the authorization code and implicit grant flow. If you are implementing a client-side(desktop, mobile and web single page apps) app with the RingCentral API, it is recommended to use the PKCE flow which can make your app more stable and safe.

Please let us know what you think by leaving your questions and comments below. To learn even more about other features we have make sure to visit our developer site and if you’re ever stuck make sure to go to our developer forum.

Want to stay up to date and in the know about new APIs and features? Join our Game Changer Program and earn great rewards for building your skills and learning more about RingCentral!

--

--