Repository: auth0/auth0-spa-js (ts)

Issue:
code_challenge is empty
During Login Redirect the generated url contains a code_challange query parameter with empty value.

https://auth.brucke.club/login?state=g6Fo2SBGaHVTN196OGlVdFltNDVLQTZZRzFHT1Q1RTF1elBZZKN0aWTZIDVFbmJadWxiOHBNdDhCSkRxOHlCT2RLSGk5MWRDdTlzo2NpZNkgd0xTSVA0N3dNMzl3S2REbU9qNlpiNWVTRXczSlZoVnA&client=wLSIP47wM39wKdDmOj6Zb5eSEw3JVhVp&protocol=oauth2&scope=openid%20profile%20email&response_type=code&response_mode=query&nonce=.FS1wxtuxKVMDZ0S8f3~aZN3Hka48tP3V0qjoKYJjU3&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F&code_challenge=&code_challenge_method=S256&auth0Client=eyJuYW1lIjoiYXV0aDAtc3BhLWpzIiwidmVyc2lvbiI6IjEuMi4zIn0%3D

File: Auth0Client.ts
Line: 207

const code_challenge = bufferToBase64UrlEncoded(code_challengeBuffer);

After looking a bit closer. This line https://github.com/auth0/auth0-spa-js/blob/32690bb10dc08497467d54eefe78c0be23d1259d/src/Auth0Client.ts#L203
const code_challengeBuffer = await sha256(code_verifier);
Fills the code_challengeBuffer however that value is lost in the next line:
const code_challenge = bufferToBase64UrlEncoded(code_challengeBuffer);

Going into bufferToBase64UrlEncoded function in this line https://github.com/auth0/auth0-spa-js/blob/32690bb10dc08497467d54eefe78c0be23d1259d/src/utils.ts#L134
the content is lost.

Hope this helps.


Pull request description:
Pkce challenge code empty
### Description

Fix to resolve #216 by updating utility method that is causing a blank `challenge_code` to be provided to the authorize endpoint.


### Testing

Test added to verify `bufferToBase64UrlEncoded` returns correct value. It was returning a blank string previously.

- [x] This change adds test coverage for new/changed/fixed functionality

### Checklist

- [ ] I have added documentation for new/changed functionality in this PR or in auth0.com/docs
- [x] All active GitHub checks for tests, formatting, and security are passing
- [ ] The correct base branch is being used, if not `master`.


Patch:
diff --git a/src/utils.ts b/src/utils.ts
index 348764a..6c0d5a4 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -108,8 +108,22 @@ export const createQueryParams = (params: any) => {
     .join('&');
 };
 
-export const sha256 = (s: string) =>
-  window.crypto.subtle.digest({ name: 'SHA-256' }, new TextEncoder().encode(s));
+export const sha256 = async (s: string) => {
+  const response = await Promise.resolve(
+    window.crypto.subtle.digest(
+      { name: 'SHA-256' },
+      new TextEncoder().encode(s)
+    )
+  );
+  // msCrypto (IE11) uses the old spec, which is not Promise based
+  // https://msdn.microsoft.com/en-us/expression/dn904640(v=vs.71)
+  // Instead of returning a promise, it returns a CryptoOperation
+  // with a `result` property in it
+  if ((<any>response).result) {
+    return (<any>response).result;
+  }
+  return response;
+};
 
 const urlEncodeB64 = (input: string) => {
   const b64Chars = { '+': '-', '/': '_', '=': '' };
@@ -131,7 +145,7 @@ export const urlDecodeB64 = (input: string) =>
   decodeB64(input.replace(/_/g, '/').replace(/-/g, '+'));
 
 export const bufferToBase64UrlEncoded = input => {
-  const ie11SafeInput = new Uint8Array(Array.from(input));
+  const ie11SafeInput = new Uint8Array(input);
   return urlEncodeB64(
     window.btoa(String.fromCharCode(...Array.from(ie11SafeInput)))
   );


Test patch:
diff --git a/__tests__/utils.test.ts b/__tests__/utils.test.ts
index 0cb30b1..72bf678 100644
--- a/__tests__/utils.test.ts
+++ b/__tests__/utils.test.ts
@@ -151,6 +151,25 @@ describe('utils', () => {
       const result = await sha256('test');
       expect(result).toBe(true);
     });
+    it('handles ie11 digest.result scenario', async () => {
+      (<any>global).crypto = {
+        subtle: {
+          digest: jest.fn((alg, encoded) => {
+            expect(alg).toMatchObject({ name: 'SHA-256' });
+            expect(Array.from(encoded)).toMatchObject([116, 101, 115, 116]);
+            return { result: true };
+          })
+        }
+      };
+      const result = await sha256('test');
+      expect(result).toBe(true);
+    });
+  });
+  describe('bufferToBase64UrlEncoded ', () => {
+    it('generates correct base64 encoded value from a buffer', async () => {
+      const result = await bufferToBase64UrlEncoded([116, 101, 115, 116]);
+      expect(result).toBe('dGVzdA');
+    });
   });
   describe('openPopup', () => {
     it('opens the popup', () => {