index.browser.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. let crypto;
  2. crypto = globalThis.crypto; // web browsers
  3. /**
  4. * Creates an array of length `size` of random bytes
  5. * @param size
  6. * @returns Array of random ints (0 to 255)
  7. */
  8. async function getRandomValues(size) {
  9. return (await crypto).getRandomValues(new Uint8Array(size));
  10. }
  11. /** Generate cryptographically strong random string
  12. * @param size The desired length of the string
  13. * @returns The random string
  14. */
  15. async function random(size) {
  16. const mask = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~";
  17. const evenDistCutoff = Math.pow(2, 8) - Math.pow(2, 8) % mask.length;
  18. let result = "";
  19. while (result.length < size) {
  20. const randomBytes = await getRandomValues(size - result.length);
  21. for (const randomByte of randomBytes) {
  22. if (randomByte < evenDistCutoff) {
  23. result += mask[randomByte % mask.length];
  24. }
  25. }
  26. }
  27. return result;
  28. }
  29. /** Generate a PKCE challenge verifier
  30. * @param length Length of the verifier
  31. * @returns A random verifier `length` characters long
  32. */
  33. async function generateVerifier(length) {
  34. return await random(length);
  35. }
  36. /** Generate a PKCE code challenge from a code verifier
  37. * @param code_verifier
  38. * @returns The base64 url encoded code challenge
  39. */
  40. export async function generateChallenge(code_verifier) {
  41. const buffer = await (await crypto).subtle.digest("SHA-256", new TextEncoder().encode(code_verifier));
  42. // Generate base64url string
  43. // btoa is deprecated in Node.js but is used here for web browser compatibility
  44. // (which has no good replacement yet, see also https://github.com/whatwg/html/issues/6811)
  45. return btoa(String.fromCharCode(...new Uint8Array(buffer)))
  46. .replace(/\//g, '_')
  47. .replace(/\+/g, '-')
  48. .replace(/=/g, '');
  49. }
  50. /** Generate a PKCE challenge pair
  51. * @param length Length of the verifer (between 43-128). Defaults to 43.
  52. * @returns PKCE challenge pair
  53. */
  54. export default async function pkceChallenge(length) {
  55. if (!length)
  56. length = 43;
  57. if (length < 43 || length > 128) {
  58. throw `Expected a length between 43 and 128. Received ${length}.`;
  59. }
  60. const verifier = await generateVerifier(length);
  61. const challenge = await generateChallenge(verifier);
  62. return {
  63. code_verifier: verifier,
  64. code_challenge: challenge,
  65. };
  66. }
  67. /** Verify that a code_verifier produces the expected code challenge
  68. * @param code_verifier
  69. * @param expectedChallenge The code challenge to verify
  70. * @returns True if challenges are equal. False otherwise.
  71. */
  72. export async function verifyChallenge(code_verifier, expectedChallenge) {
  73. const actualChallenge = await generateChallenge(code_verifier);
  74. return actualChallenge === expectedChallenge;
  75. }