PKCE: What and Why?

// By Taylor Krusen • Dec 04, 2020

You overhear a conversation between engineers and hear them mention “pixy”. No, they’re not discussing fairies. They’re referring to a new, more secure OAuth flow called PKCE, or Proof Key for Code Exchange. And while no magic is involved in this process, PKCE does provide some great security benefits for your apps, especially if your app runs on a public client.

PKCE provides dynamic client secrets, meaning your app’s client secrets can stay secret (even without a back end for your app). PKCE is better and more secure than the implicit flow (AKA the “token flow”). If you’re using the implicit flow, then you should switch to PKCE. If you use an implicit flow to authorize your Dropbox app, then PKCE is a better, more secure replacement, and you should no longer use implicit flow.

In this post, we’ll look at how PKCE works, why it’s important, and how you can start using PKCE flows in your Dropbox apps today.

PKCE Builds on OAuth 2.0 for Added Security

PKCE is a new, more secure authorization flow (based on the OAuth 2.0 spec) that was originally created to better secure mobile apps, but is valuable across all OAuth clients.

From the official OAuth 2.0 spec for PKCE:

“PKCE (RFC 7636) is an extension to the Authorization Code flow to prevent several attacks and to be able to securely perform the OAuth exchange from public clients.”

The OAuth 2.0 spec is the industry standard protocol for authorization and allows users to grant permission for apps to access their Dropbox data. Using OAuth 2.0, users can access their Dropbox data from third party applications without sensitive information (like passwords) ever changing hands.

For a more thorough breakdown of how Dropbox implements OAuth 2.0, check out our OAuth Guide. For the purposes of this post, use the diagram below to get a feel for the general authorization flow.

Basic authorization flow from a User's POV

Basic authorization flow from a User's POV 

There are a two major actions taking place here:

  • The user is authorizing an app to access their Dropbox data
  • Dropbox issues an access token to the app that can be used to access the user’s Dropbox data

The most common and secure OAuth flow is the authorization code flow. Unfortunately, that process relies on apps providing a client_secret in the final request for an access token. For certain types of apps, that makes leaking the secret an inherent, unavoidable risk, which is why they instead needed to rely on an implicit flow. These apps include, but are not limited to mobile apps, single-page apps (SPA) in JavaScript, and serverless apps.

Because these are public clients, there’s no way for them to guarantee the security of the secret used for the token exchange. Using the implicit flow solves for that, but with the added risk of exposing the access token in the redirect URI at the end of the authorization flow, which makes the flow vulnerable to different types of network and malicious app interceptions.

This tradeoff was acceptable (and necessary) back when apps couldn’t make cross-domain requests—making it impossible to complete an authorization code flow. However, now that CORS is widely supported, there’s no need for that historical compromise; apps can make direct post requests to the token endpoint.

Without the cross-origin problem, public clients can take advantage of the authorization code flow by using PKCE, which works by substituting the static client secret with a string that is dynamically generated. By doing so, the PKCE flow eliminates leaky secrets while allowing the authorization server to verify that the app requesting the access token is the same one that initiated the OAuth flow.

Let’s take a look at how PKCE makes that possible.

Understanding PKCE Implementation

Building on top of the authorization code flow, there are three new parameters used by PKCE: code_verifier, code_challenge, and code_challenge_method. Let’s define them before adding more context around the overall flow.

The code_verifier is a cryptographically random string generated by your app. This dynamically created string is used to correlate the final access token request with the initial authorization request. In other words, the code_verifier is how the Dropbox authorization server ensures that the access token is issued to the same app that requested authorization.

The code_challenge is derived from the code_verifier using one of the two possible transformations: plain and S256. Plain can only be used when S256 is not possible. For the majority of use cases, the code_challenge will be a base 64 encoding of an SHA256 hash made with the client_verifier. This string gets decrypted server-side and is used to verify that the requests are coming from the same client.

The code_challenge_method tells the server which function was used to transform the code_verifier (plain or S256). It will default to plain if left empty.

These new parameters are used to supplement the authorization code flow to create a powerful system of checks that allow the server to verify that the authorization request and token request both come from the same client.

When a user kicks off a PKCE authorization flow in your app, here’s what takes place: 

  1. Client (your app) creates the code_verifier. (RFC 7636, Section 4.1)
  2. Client creates the code_challenge by transforming the code_verifier using S256 encryption. (RFC 7636, Section 4.2)
  3. Client sends the code_challenge and code_challenge_method with the initial authorization request. (RFC 7636, Section 4.3)
  4. Server responds with an authorization_code. (RFC 7636, Section 4.4)
  5. Client sends authorization_code and code_verifier to the token endpoint. (RFC 7636, Section 4.5)
  6. Server transforms the code_verifier using the code_challenge_method from the initial authorization request and checks the result against the code_challenge. If the value of both strings match, then the server has verified that the requests came from the same client and will issue an access_token. (RFC 7636, Section 4.6)

Now that we understand the flow, let’s see what it looks like in practice.

Testing the PKCE Flow

In these samples, we’re using Node.js to generate the dynamic strings and curl to send our requests to the Dropbox API. In production, the string generation and API requests would happen in the same app. You’ll need a Dropbox app to follow along
 
Start by creating a new JavaScript file and importing the crypto module (already bundled with Node), which we’ll use for the SHA-256 encryption:
const crypto = require("crypto")

Step 1: Client creates code_verifier and subsequent code_challenge

Add the following snippet to your JavaScript file


const base64Encode = (str) => {
    return str.toString('base64')
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '');
}
const codeVerifier = base64Encode(crypto.randomBytes(32));
console.log(`Client generated code_verifier: ${codeVerifier}`)

const sha256 = (buffer) => {
    return crypto.createHash('sha256').update(buffer).digest();
}
const codeChallenge = base64Encode(sha256(codeVerifier));
console.log(`Client generated code_challenge: ${codeChallenge}`)

Run the script with node <your_filename>

The console will output the generated strings

Client generated code_verifier: kiNgBo0-r4GdQld6ShdPoxGq9SheI2m5moxtX-tFce4
Client generated code_challenge: lSEB3zK2TM-X38Baht80CvC4E_a5DnpCG52y5a7dQyk

Step 2: Client sends code_challenge and code_challenge_method to /oauth2/authorize

Manually assemble the authorization URL and replace the variables with your own information

https://www.dropbox.com/oauth2/authorize?client_id=<APP_KEY>&response_type=code&code_challenge=<CHALLENGE>&code_challenge_method=<METHOD>

(mine looks like this)

https://www.dropbox.com/oauth2/authorize?client_id=owdjktek8mg5ren&response_type=code&code_challenge=lSEB3zK2TM-X38Baht80CvC4E_a5DnpCG52y5a7dQyk&code_challenge_method=S256

Navigate to the authorization URL and click 'Allow'

Consent page for Dropbox OAuth flow

User consent page for Dropbox OAuth flow 

Step 3: Server responds with authorization code

Copy the authorization code shown after approving the app

Authorization code provided after user consent in oauth flow

Authorization code provided after user allows app connection 

Note: in production, the authorization code is usually sent to a redirect_uri passed with the authorization request, but that optional parameter is left out of this example

Step 4: Client sends authorization_code and code_verifier to /oauth2/token

Manually assemble the CURL request and replace the variables with your information

curl https://api.dropbox.com/oauth2/token \
 -d code=<AUTHORIZATION_CODE> \
 -d grant_type=authorization_code \
 -d code_verifier=<CODE_VERIFIER> \
 -d client_id=<APP_KEY>

(my request looks like this)

curl https://api.dropbox.com/oauth2/token \
 -d code=xl-pHsaK7hAAAAAAAAAAyLI4TKnv3IAInX9TvlVu76l \
 -d grant_type=authorization_code \
 -d code_verifier=kiNgBo0-r4GdQld6ShdPoxGq9SheI2m5moxtX-tFce4 \
 -d client_id=owdjktek8mg5ren

Send request to Dropbox token endpoint (/oauth2/token)

Server encrypts and compares code_verifier with code_challenge to verify source of authorization and token requests match

Server issues an access token:


{
  "access_token": "NW7lYmEWHgUAAAAAAAAAAbeutI8iL5CuBik9_CPD5r83XvcQPt-7O5diOdUUcsuX",
  "expires_in": 14399, 
  "token_type": "bearer", 
  "uid": "2589992144", 
  "account_id": "dbid:AABuXdtqD88UpveXxu7rcVSo64ADcrWnBMk", 
  "scope": "account_info.read contacts.write file_requests.read file_requests.write files.content.read files.content.write files.metadata.read files.metadata.write"
}

Step 5: Do a victory dance. You made it!partying face

Optionally, you can test your new access token and do another victory dance. 

curl -X POST https://api.dropboxapi.com/2/users/get_current_account \
 --header "Authorization: Bearer <Your_Access_Token>"

Replacing Implicit Flow with PKCE Flow

Thanks to a clever design, some string manipulation, and a couple extra parameters added to your request, apps that are public clients (mobile, SPAs, serverless, etc.) can take advantage of the enhanced security offered by the authorization code flow by using PKCE. If your app can’t guarantee the security of your client secret, then you should be using PKCE!

Using a PKCE flow is (another) great reason to build with our official Dropbox SDKs, where PKCE is already built in.

Due to the better design and overall security, we strongly recommend replacing your legacy implicit flows with PKCE. Not sure which authorization flow is best suited for your needs? Check out our OAuth Guide for more detailed information.

As always, we’re available to help! You can post your questions on our developer forum or submit a ticket for more direct support.

Build with Dropbox today at www.dropbox.com/developers.


// Copy link