0

I am making a client to authorize via OAuth2 but I keep on getting an "Code verifier is invalid" error.

async function encode(input: string): Promise<string> {
  return btoa(string).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
}

async function digestMessage(message) {
  const msgUint8 = new TextEncoder().encode(message);                           // encode as (utf-8) Uint8Array
  const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8);           // hash the message
  const hashArray = Array.from(new Uint8Array(hashBuffer));                     // convert buffer to byte array
  const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string
  return hashHex;
}

function generateRandomString (len?: number) {
  var arr = new Uint8Array((len || 40) / 2)
  window.crypto.getRandomValues(arr)
  return Array.from(arr, dec2hex).join('')
}

async function pkceChallengeFromVerifier(v: string) {
  let hash = await digestMessage(v)
  let challenge = await encode(hash)
  return challenge
}

No matter what verifier I put in, it keeps on giving me the error.

Epic Programmer
  • 383
  • 3
  • 12

1 Answers1

0

Do not convert it into a string! That changes the base64 output value entirely! Instead, base64 encode the raw hash buffer from crypto.subtle.digest.

Using this solution for getting the base64 of an array buffer:

async function hashSHA256(str: string): Promise<ArrayBuffer> {
  const utf8 = new TextEncoder().encode(str);
  const hashBuffer = await crypto.subtle.digest('SHA-256', utf8);
  return hashBuffer;
}

async function base64_arraybuffer(data: ArrayBuffer): Promise<string> {
  // Use a FileReader to generate a base64 data URI
  const base64url: string = await new Promise((r) => {
      const reader = new FileReader()
      reader.onload = () => r(reader.result as string)
      reader.readAsDataURL(new Blob([data]))
  })

  /*
  The result looks like
  "data:application/octet-stream;base64,<your base64 data>",
  so we split off the beginning:
  */
  return base64url.split(",", 2)[1]
}


async function encode(input: ArrayBuffer): Promise<string> {
  return (await base64_arraybuffer(input)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
}

async function pkceChallengeFromVerifier(v: string) {
  let hash = await hashSHA256(v)
  let challenge = encode(hash)
  return challenge
}

Also make sure that the input verifier string is within the length range of 43 and 128 characters long.

Epic Programmer
  • 383
  • 3
  • 12
  • Can you explain the replaces in encode? I've been trying to find a solution for a different bug, and the only thing that solved it for me was implementing your replacements. – jumpsplat120 Jun 18 '23 at 01:13
  • @jumpsplat120 I honestly don't know why those are needed, as I borrowed that piece of code from the service I was attempting to integrate OAuth with. My theory is that the normal base64 is not url-safe, and so there is a url-safe base64 flavor which is achieved by doing the replaces. – Epic Programmer Jun 18 '23 at 01:57
  • You'd think that the server receiving the code challenge would be expecting standard URI escaping (ie, %2F for /), rather than just swapping them to other characters. In any case, it works, so thank you for that. *shakes fist at Google* – jumpsplat120 Jun 18 '23 at 03:06