Repository: auth0/auth0-spa-js
Pull request #374 (2020-03-12 17:00:04)

handleRedirectCallback wont pass redirect_uri undefined if not set in transaction
### Description

If redirect_uri is not set as a parameter in the transaction, handleRedirectCallback will not pass an object with `redirect_uri: undefined` into utils/oauthToken. Otherwise this error could happen:

```Uncaught (in promise) Error: Invalid redirect_uri. Expected type 'string' but found type undefined.```

This could occur if an application upgraded their app from an older version (1.2.4) to a newer version (1.6.3), where the older version didn't have the redirect_uri parameter:

- users request the page at http://my-app.com/ and get a cached version, the code calls loginWithRedirect() and the old transaction cookie format (no redirect_uri) is set
- the user logs in at the universal login page, and is redirected back to the application http://my-app.com/callback?code=code&state=state
- if the browser doesn't get the cached version on the callback route, the new sdk will try to use the old transaction cookie, and it would have passed redirect_uri=undefined into the utils/oauthToken method which will cause an error.

### References
Here is an issue for this: https://github.com/auth0/auth0-spa-js/issues/373

### Testing
I added unit tests to check the arguments that get passed to oauthtoken


albertlockett: @stevehobbsdev is there anything else you need from me on this to be able to merge it?
stevehobbsdev: @albertlockett I'm not sure just yet, let me review today and let you know.

Patch:
diff --git a/src/Auth0Client.ts b/src/Auth0Client.ts
index 3c220f1..7c321b0 100644
--- a/src/Auth0Client.ts
+++ b/src/Auth0Client.ts
@@ -298,14 +298,21 @@ export default class Auth0Client {
 
     this.transactionManager.remove(state);
 
-    const authResult = await oauthToken({
+    const tokenOptions = {
       baseUrl: this.domainUrl,
       audience: this.options.audience,
       client_id: this.options.client_id,
       code_verifier: transaction.code_verifier,
-      code,
-      redirect_uri: transaction.redirect_uri
-    });
+      code
+    } as OAuthTokenOptions;
+
+    // some old versions of the SDK might not have added redirect_uri to the
+    // transaction, we dont want the key to be set to undefined.
+    if (undefined !== transaction.redirect_uri) {
+      tokenOptions.redirect_uri = transaction.redirect_uri;
+    }
+
+    const authResult = await oauthToken(tokenOptions);
 
     const decodedToken = this._verifyIdToken(
       authResult.id_token,


Test patch:
diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts
index 2d774cf..84487bd 100644
--- a/__tests__/index.test.ts
+++ b/__tests__/index.test.ts
@@ -858,6 +858,34 @@ describe('Auth0', () => {
           code_verifier: TEST_RANDOM_STRING
         });
       });
+      it('calls oauth/token with redirect uri from transaction if set', async () => {
+        const { auth0, utils, transactionManager } = await localSetup();
+        const txn = transactionManager.get.mockReturnValue({
+          code_verifier: TEST_RANDOM_STRING,
+          nonce: TEST_RANDOM_STRING,
+          audience: 'default',
+          scope: TEST_SCOPES,
+          appState: TEST_APP_STATE,
+          redirect_uri: 'http://localhost'
+        });
+        await auth0.handleRedirectCallback();
+        const arg = utils.oauthToken.mock.calls[0][0];
+        expect(arg.hasOwnProperty('redirect_uri')).toBeTruthy();
+        expect(arg.redirect_uri).toEqual('http://localhost');
+      });
+      it('calls oauth/token without redirect uri if not set in transaction', async () => {
+        const { auth0, utils, transactionManager } = await localSetup();
+        const txn = transactionManager.get.mockReturnValue({
+          code_verifier: TEST_RANDOM_STRING,
+          nonce: TEST_RANDOM_STRING,
+          audience: 'default',
+          scope: TEST_SCOPES,
+          appState: TEST_APP_STATE
+        });
+        await auth0.handleRedirectCallback();
+        const arg = utils.oauthToken.mock.calls[0][0];
+        expect(arg.hasOwnProperty('redirect_uri')).toBeFalsy();
+      });
       it('calls `tokenVerifier.verify` with the `id_token` from in the oauth/token response', async () => {
         const { auth0, tokenVerifier } = await localSetup();