Authorization code flow with Proof Key for Code Exchange

Last updated: 2024-02-16Contributors
Edit this page

The Authorization code flow with Proof Key for Code Exchange, or simply "Auth code flow with PKCE" is the recommended form of authenticating RingCentral users and exchanging tokens in client-side applications. It is considered a more secure version of the more widely used Authorization code flow. The flow is as follows:

PKCE Flow}

What are the benefits of using the authorization code flow?

As you can see from the diagram above, the two protocols are nearly identical, with the following differences and benefits:

  • two additional tokens are introduced to the flow to combat XSS forgeries
  • the client secret is never revealed during the flow

Authorization code flow with PKCE in detail

Step 0. Generate code verifier and code challenge

Before we initiate an authorization request to RingCentral, we need to generate two strings: a code verifier and a code challenge. The code verifier in particular should be a cryptographically-random string, without the '+', '/', and '=' characters.

The code challenge is then derived from code challenge string generated above. For devices that can perform a SHA256 hash, the code challenge is a base 64, URL-encoded string of the SHA-256 hash of the code verifier. Clients that do not have the ability to perform a SHA-256 hash are permitted to use the plain code verifier string as the challenge.

The code below shows how to generate these two strings:

Javascript developers can install the crypto modile like so:

$ npm install crypto

Then your code will look like this:

var rcsdk = new RC({
    'server':       process.env.RC_SERVER_URL,
    'clientId':     process.env.RC_CLIENT_ID,
    'clientSecret': process.env.RC_CLIENT_SECRET,
    'redirectUri':  process.env.RC_REDIRECT_URI
});
var platform = rcsdk.platform();

console.log( "Login URL: ", platform.loginUrl({
    "state": "1234567890",
    "usePKCE": true
}) )

Python developers can install the pkce module like so:

$ pip install pkce

Then in their code it is really simple:

import pkce
code_verifier = pkce.generate_code_verifier(length=128)
code_challenge = pkce.get_code_challenge(code_verifier)
<?php
function base64url_encode($plainText)
{       
    $base64 = base64_encode($plainText);
    $base64 = trim($base64, "=");
    $base64url = strtr($base64, '+/', '-_');
    return ($base64url);
}
$random = bin2hex(openssl_random_pseudo_bytes(32));
$verifier = base64url_encode(pack('H*', $random));
$challenge = base64url_encode(pack('H*', hash('sha256', $verifier)));
?>  

Ruby developers can install the pkce_challenge Gem like so:

$ gem install pkce_challenge

Then in their code:

PkceChallenge.challenge(char_length: 128)
pkce_challenge.code_verifier 
pkce_challenge.code_challenge 

Example code verifier and challenge strings

When all is said and done, you will have generated two strings that look similar to the following: A cryptographically-random example string:

# Verifier
pIUgx4tiqFpaOUz0HMc_QbIyQlL901w8mRmkrmhEJ_E

# Challenge
_drLS7o5FwkfUiBhlq2hwJnK_SC6yE7sKOde5O1fdzk

Step 1. Compose a "request authorization" URL

When your application needs to access a user's data, redirect the user to the RingCentral API server. The authorization URL is same as URL from Authorization Code Flow Step 1, and PKCE flow will need additional parameters code_challenge and code_challenge_method in authorization URL:

Parameter Type Description
response_type string Required. Must be set to code.
client_id string Required. The client ID of the application making the request.
redirect_uri URL Required. The URL the response will be sent to. is is a callback URI which determines where the response will be sent to. The value of this parameter must exactly match one of the URIs you provided when creating your app.
state string Optional. A pass-through value used by the client to maintain state between the request and callback. RingCentral ignore the values of this parameter.
scope string Ignored. Common to other OAuth apps, the scope parameter is ignored by RingCentral deferring to the permissions set by the developer when the app was created. It can be safely omitted.
brandId integer Optional. A number identifying what branding should be displayed on the login page.
display string This is for RingCentral-use only and can be safely omitted.
prompt string This is for RingCentral-use only and can be safely omitted.
code_challenge string Required. Generated from code challenge.
code_challenge_method string Required. The code challenge method, either plain or S256, depending on whether the challenge is the plain verifier string or the SHA256 hash of the string. If this parameter is omitted, the server assumes plain.

Example Login URL

Below is an example login URL to initiate the PKCE authorization flow. We recommend developers use an SDK to generate this URL in a more automated fashion.

https://platform.ringcentral.com/restapi/oauth/authorize?response_type=code
   &redirect_uri=<my_uri>&client_id=<client_id>&display=&prompt=
   &code_challenge=<code_challenge_string>&code_challenge_method=S256

Using an SDK to generate a login URL

We recommend developers use an SDK to generate a login URL to ensure it is composed properly.

const RC = require('@ringcentral/sdk').SDK

var rcsdk = new RC({
    'server':       process.env.RC_SERVER_URL,
    'clientId':     process.env.RC_CLIENT_ID,
    'clientSecret': process.env.RC_CLIENT_SECRET,
    'redirectUri':  process.env.RC_REDIRECT_URI
});
var platform = rcsdk.platform();

console.log( "Login URL: ", platform.loginUrl({
    "state": "1234567890",
    "usePKCE": true
}) )

This step is same as its counterpart in the authorization code flow. After a user logs in and authorizes the application, RingCentral will redirect the user's browser to the redirect_uri provided in the login URL created above. At the same time, RingCentral will append the following query parameters to the redirect URI, which your application will need in subsequent steps.

Parameter Type Description
code string The authorization code returned for your application.
expires_in integer The remaining lifetime of the authorization code.
state string The verbatim value of the state parameter received via the login URL.

Example OAuth redirect

HTTP/1.1 302 Found
Location: https://myapp.example.com/oauth2Callback?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz&expires_in=60

Step 3. Exchange auth code for access token

The 'code' your application receives at your Redirect URI is a temporary authorization code that is used to obtain an access token to call the API. If the token is not redeemed in the alotted time, the user will need to go through the login and authorization process again. This is the final step in the process before your app can call the RingCentral API.

To exchange an auth code for an access token, developers will call the RingCentral API similarly to how it is done in the authorization code flow, but with the following key differences:

  • Clients do not need to transmit client authentication credentials in an Authorization header
  • Clients need to transmit an additional code_verifier parameter in the request body

Auth token request

HTTP Headers

Header Value
Content-type application/x-www-form-urlencoded

POST Parameters

Parameter Type Description
grant_type string Required. Must be set to authorization_code for authorization code flow
code string Required. Provide your authorization code received in the previous step
client_id string Required. Enter your application key (Production or Sandbox) here
redirect_uri URI Required. This is a callback URI which determines where the response is sent. The value of this parameter must exactly match one of the URIs you have provided for your app upon registration.
access_token_ttl integer Optional. Access token lifetime in seconds; the possible values are from 600 sec (10 min) to 3600 sec (1 hour). The default value is 3600 sec. If the value specified exceeds the default one, the default value is set. If the value specified is less than 600 seconds, the minimum value (600 sec) is set
refresh_token_ttl integer Optional. Refresh token lifetime in seconds. The default value depends on the client application, but as usual it equals to 7 days. If the value specified exceeds the default one, the default value is applied
code_verifier string Required. Code verifier generated in Step 0.

Sample Request

POST /restapi/oauth/token HTTP/1.1 
Accept: application/json 
Content-Type: application/x-www-form-urlencoded 

code=U0pDMTFQMDFQQVMwM
  XxBQUJfTVpHWk5lM29zNVFmWnNHQ01MSmJuMHJmNGlRcnRaeEptTWlPS0MzUTdYRDdSTURiaHBuWHZINGM2WTdqaWlBOE
  VhRHNxRWdJVUNYQjd4dmJsWHJoVVlWQVN2SFo2YWJPanJsRkFWZk9SMm5lek0tWnF5d3h8C3AnYOPxO0flEwO6Ffoq9Tl
  qs1s&grant_type=authorization_code&client_id=asdsadsadasdadsa&code_verifier=pIUgx4tiqFpaOUz0H
  Mc_QbIyQlL901w8mRmkrmhEJ_E&redirect_uri=https%3A%2F%2Fmyapp.acme.com%2Foauth2redirect

Auth token response

The server responds with an access token which can presented in subsequent requests in the HTTP Authorization header to authenticate API Calls. The response will contain the following parameters:

Parameter Type Description
access_token string Access token to pass to subsequent API requests
expires_in integer Issued access token TTL (time to live), in seconds
refresh_token string Refresh token to get a new access token, when the issued one expires
refresh_token_expires_in integer Issued refresh token TTL (time to live), in seconds
scope string List of permissions allowed with this access token, white-space separated
token_type string Type of token. The only possible value supported is 'Bearer'. This value should be used when specifying access token in Authorization header of subsequent API requests
owner_id string Extension identifier

Sample Response

HTTP/1.1 200 OK
Content-Type: application/json

{
    "access_token" : "U1BCMDFUMDRKV1MwMXxzLFSvXdw5PHMsVLEn_MrtcyxUsw",
    "token_type" : "bearer",
    "expires_in" : 7199,
    "refresh_token" : "U1BCMDFUMDRKV1MwMXxzLFL4ec6A0XMsUv9wLriecyxS_w",
    "refresh_token_expires_in" : 604799,
    "scope" : "AccountInfo CallLog ExtensionInfo Messages SMS",
    "owner_id" : "256440016"
}