Skip to content
Search

Proof Key for Code Exchange

lastmod 2024-01-05created 2023-08-172 edits

What is PKCE (Proof Key for Code Exchange)? It is an OAuth 2.0 security extension for public clients on mobile devices, designed to prevent malicious programs that break into the same computer from intercepting the authorization code. (loginradius)

About

Simply put, PKCE replaces the Client Secret used in the Authorization Code Grant flow with a one-time password.

Known as PKCE (Proof Key for Code Exchange), the approach of using a provable key when exchanging the code for authentication is a way to prevent malicious attacks and carry out the Authorization Code Grant flow securely.

It is mainly useful for replacing the Client Secret used by client applications that use a Client Secret or by the Authorization Code Grant flow.

This approach basically works with three parameters: code_verifier, code_challenge, and code_challenge_method.

A picture will be easier to understand than an explanation in words. The picture below shows the Authorization Code Grant + PKCE flow well.

Examples

generatePKCEVerifier and generatePKCEChallenge

function dec2hex(dec: number) {
  return ('0' + dec.toString(16)).substr(-2)
}

// Functions below taken from: https://stackoverflow.com/questions/63309409/creating-a-code-verifier-and-challenge-for-pkce-auth-on-spotify-api-in-reactjs
export function generatePKCEVerifier() {
  const verifierLength = 56
  const array = new Uint32Array(verifierLength)
  if (typeof crypto === 'undefined') {
    const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'
    const charSetLen = charSet.length
    let verifier = ''
    for (let i = 0; i < verifierLength; i++) {
      verifier += charSet.charAt(Math.floor(Math.random() * charSetLen))
    }
    return verifier
  }
  crypto.getRandomValues(array)
  return Array.from(array, dec2hex).join('')
}

async function sha256(randomString: string) {
  const encoder = new TextEncoder()
  const encodedData = encoder.encode(randomString)
  const hash = await crypto.subtle.digest('SHA-256', encodedData)
  const bytes = new Uint8Array(hash)

  return Array.from(bytes)
    .map((c) => String.fromCharCode(c))
    .join('')
}

function base64urlencode(str: string) {
  return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}

export async function generatePKCEChallenge(verifier: string) {
  const hasCryptoSupport =
    typeof crypto !== 'undefined' &&
    typeof crypto.subtle !== 'undefined' &&
    typeof TextEncoder !== 'undefined'

  if (!hasCryptoSupport) {
    console.warn(
      'WebCrypto API is not supported. Code challenge method will default to use plain instead of sha256.'
    )
    return verifier
  }
  const hashed = await sha256(verifier)
  return base64urlencode(hashed)
}

See also

Favorite site

author Zer0 sitemap