Migrate to PKCE

View as Markdown

PKCE (Proof Key for Code Exchange) adds a one-time secret to the OAuth authorization code exchange, protecting against intercepted authorization codes. It is now part of ShipBob’s OAuth flow. This guide walks through adding it to an OAuth app that was built before PKCE.

What changes

PKCE adds two parameters to the authorize request and one to the token exchange. Nothing else about your OAuth flow changes - the client_id, client_secret, redirect_uri, and scopes all stay the same, and the client_secret is still required.

StepBeforeAfter
Authorize requestclient_id, redirect_uri, …adds code_challenge + code_challenge_method=S256
Token exchangeclient_id, client_secret, code, …adds code_verifier

Migration steps

1

Generate a code verifier and challenge

For each authorization request, generate a fresh PKCE pair:

  • code_verifier – a cryptographically random string, 43-128 characters, using only the URL-safe characters A-Z, a-z, 0-9, -, ., _, ~. Keep it secret and store it for the token exchange (Step 3).
  • code_challenge – the Base64-URL-encoded (no padding) SHA-256 hash of the code_verifier.

Always use the S256 challenge method. Never use the plain method in production.

1import crypto from "crypto";
2
3function base64UrlEncode(buffer) {
4 return buffer
5 .toString("base64")
6 .replace(/\+/g, "-")
7 .replace(/\//g, "_")
8 .replace(/=+$/, "");
9}
10
11// 32 random bytes -> 43-character verifier
12const codeVerifier = base64UrlEncode(crypto.randomBytes(32));
13
14const codeChallenge = base64UrlEncode(
15 crypto.createHash("sha256").update(codeVerifier).digest()
16);
17
18console.log({ codeVerifier, codeChallenge });
2

Add the challenge to your authorize request

Append code_challenge and code_challenge_method=S256 to the authorize URL you already build:

https://auth.shipbob.com/connect/authorize
?client_id=YOUR_CLIENT_ID
&redirect_uri=https%3A%2F%2Fwww.myapp.com%2Fintegrate%2Fshipbob%2Fcallback
&response_type=code
&scope=openid%20offline_access%20channels_read
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
3

Add the verifier to your token exchange

Include the matching code_verifier in the token request. The client_secret is still required - PKCE is an additional layer, not a replacement.

$curl -X POST "https://auth.shipbob.com/connect/token" \
> --data-urlencode "grant_type=authorization_code" \
> --data-urlencode "client_id=YOUR_CLIENT_ID" \
> --data-urlencode "client_secret=YOUR_CLIENT_SECRET" \
> --data-urlencode "redirect_uri=https://www.myapp.com/integrate/shipbob/callback" \
> --data-urlencode "code=AUTHORIZATION_CODE" \
> --data-urlencode "code_verifier=code_verifier_from_step_1"

The code_verifier must match the code_challenge you sent on the authorize request in Step 2. Generate a new pair for every authorization - never reuse a code_verifier.

PKCE applies only to the initial authorization code exchange. You do not need to send a code_verifier or code_challenge when refreshing tokens.

For the full OAuth walkthrough, see the Authentication guide.