diff --git a/packages/core/auth-js/src/lib/helpers.ts b/packages/core/auth-js/src/lib/helpers.ts index 7402899537..fefad1ca62 100644 --- a/packages/core/auth-js/src/lib/helpers.ts +++ b/packages/core/auth-js/src/lib/helpers.ts @@ -259,13 +259,12 @@ export function retryable( } function dec2hex(dec: number) { - return ('0' + dec.toString(16)).substr(-2) + return ('0' + dec.toString(16)).slice(-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 @@ -275,6 +274,11 @@ export function generatePKCEVerifier() { } return verifier } + // Each byte maps to two hex characters, so a 56-byte buffer yields a + // 112-character verifier (within RFC 7636's 43-128 range). A Uint8Array + // requests exactly the entropy consumed; a Uint32Array would draw 4x the + // bytes and discard three of every four. + const array = new Uint8Array(verifierLength) crypto.getRandomValues(array) return Array.from(array, dec2hex).join('') } diff --git a/packages/core/auth-js/test/helpers.test.ts b/packages/core/auth-js/test/helpers.test.ts index d0a62eb2f2..8db5d9e7a4 100644 --- a/packages/core/auth-js/test/helpers.test.ts +++ b/packages/core/auth-js/test/helpers.test.ts @@ -4,6 +4,7 @@ import { decodeJWT, generateCallbackId, generatePKCEFlowId, + generatePKCEVerifier, getAlgorithm, getItemAsync, parseParametersFromURL, @@ -275,6 +276,24 @@ describe('getAlgorithm', () => { }) }) +describe('generatePKCEVerifier', () => { + it('should generate a verifier within the RFC 7636 length range', () => { + const verifier = generatePKCEVerifier() + expect(verifier.length).toBeGreaterThanOrEqual(43) + expect(verifier.length).toBeLessThanOrEqual(128) + }) + + it('should only use RFC 7636 unreserved characters', () => { + const verifier = generatePKCEVerifier() + // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + expect(verifier).toMatch(/^[A-Za-z0-9\-._~]+$/) + }) + + it('should generate a unique verifier on each call', () => { + expect(generatePKCEVerifier()).not.toEqual(generatePKCEVerifier()) + }) +}) + describe('getCodeChallengeAndMethod', () => { const testCases = [ {