aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.41k stars 2.12k forks source link

Social sign-in not working with custom url scheme in Cordova #10301

Open WouterVDW12 opened 2 years ago

WouterVDW12 commented 2 years ago

Before opening, please confirm:

JavaScript Framework

Angular

Amplify APIs

Authentication

Amplify Categories

auth

Environment information

``` System: OS: Windows 10 10.0.19044 CPU: (8) x64 11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz Memory: 1.23 GB / 15.69 GB Binaries: Node: 14.15.4 - C:\Program Files\nodejs\node.EXE Yarn: 1.22.15 - C:\Program Files (x86)\Yarn\bin\yarn.CMD npm: 6.14.10 - C:\Program Files\nodejs\npm.CMD Browsers: Chrome: 105.0.5195.53 Edge: Spartan (44.19041.1266.0), Chromium (105.0.1343.27) Internet Explorer: 11.0.19041.1566 npmPackages: @angular-devkit/build-angular: 0.1102.10 => 0.1102.10 @angular/animations: 11.2.11 => 11.2.11 @angular/animations/browser: undefined () @angular/animations/browser/testing: undefined () @angular/cdk: 11.2.10 => 11.2.10 @angular/cdk/a11y: undefined () @angular/cdk/accordion: undefined () @angular/cdk/bidi: undefined () @angular/cdk/clipboard: undefined () @angular/cdk/coercion: undefined () @angular/cdk/collections: undefined () @angular/cdk/drag-drop: undefined () @angular/cdk/keycodes: undefined () @angular/cdk/layout: undefined () @angular/cdk/observers: undefined () @angular/cdk/overlay: undefined () @angular/cdk/platform: undefined () @angular/cdk/portal: undefined () @angular/cdk/scrolling: undefined () @angular/cdk/stepper: undefined () @angular/cdk/table: undefined () @angular/cdk/testing: undefined () @angular/cdk/testing/protractor: undefined () @angular/cdk/testing/testbed: undefined () @angular/cdk/text-field: undefined () @angular/cdk/tree: undefined () @angular/cli: 11.2.10 => 11.2.10 @angular/common: 11.2.11 => 11.2.11 @angular/common/http: undefined () @angular/common/http/testing: undefined () @angular/common/testing: undefined () @angular/common/upgrade: undefined () @angular/compiler: 11.2.11 => 11.2.11 (9.0.0) @angular/compiler-cli: 11.2.11 => 11.2.11 @angular/compiler/testing: undefined () @angular/core: 11.2.11 => 11.2.11 (9.0.0) @angular/core/testing: undefined () @angular/forms: 11.2.11 => 11.2.11 @angular/language-service: 11.2.11 => 11.2.11 @angular/localize: 11.2.11 => 11.2.11 @angular/localize/init: undefined () @angular/platform-browser: 11.2.11 => 11.2.11 @angular/platform-browser-dynamic: 11.2.11 => 11.2.11 @angular/platform-browser-dynamic/testing: undefined () @angular/platform-browser/animations: undefined () @angular/platform-browser/testing: undefined () @angular/router: 11.2.11 => 11.2.11 @angular/router/testing: undefined () @angular/router/upgrade: undefined () @aws-amplify/auth: 4.5.2 => 4.5.2 @babel/core: 7.2.2 => 7.2.2 (7.12.10, 7.14.6, 7.8.3, 7.15.5, 7.12.9) @compodoc/compodoc: 1.1.15 => 1.1.15 @progress/kendo-angular-buttons: 6.1.0 => 6.1.0 @progress/kendo-angular-charts: 5.1.0 => 5.1.0 @progress/kendo-angular-common: 2.0.0 => 2.0.0 @progress/kendo-angular-dateinputs: 5.1.0 => 5.1.0 @progress/kendo-angular-dialog: 5.0.0 => 5.0.0 @progress/kendo-angular-dropdowns: 5.3.0 => 5.3.0 @progress/kendo-angular-excel-export: 4.0.0 => 4.0.0 @progress/kendo-angular-grid: 5.0.2 => 5.0.2 @progress/kendo-angular-inputs: 7.1.2 => 7.1.2 @progress/kendo-angular-intl: 3.1.0 => 3.1.0 @progress/kendo-angular-l10n: 3.0.0 => 3.0.0 @progress/kendo-angular-label: 3.0.0 => 3.0.0 @progress/kendo-angular-layout: 6.1.2 => 6.1.2 @progress/kendo-angular-menu: 3.0.0 => 3.0.0 @progress/kendo-angular-notification: 3.0.0 => 3.0.0 @progress/kendo-angular-pdf-export: 3.0.0 => 3.0.0 @progress/kendo-angular-popup: 4.0.0 => 4.0.0 @progress/kendo-angular-progressbar: 2.0.0 => 2.0.0 @progress/kendo-angular-tooltip: 3.0.0 => 3.0.0 @progress/kendo-angular-treeview: 5.2.0 => 5.2.0 @progress/kendo-data-query: 1.5.4 => 1.5.4 @progress/kendo-drawing: 1.9.4 => 1.9.4 @progress/kendo-licensing: 1.1.3 => 1.1.3 @progress/kendo-theme-default: 4.33.0 => 4.33.0 @storybook/addon-actions: 6.3.2 => 6.3.2 @storybook/addon-essentials: 6.3.2 => 6.3.2 @storybook/addon-links: 6.3.2 => 6.3.2 @storybook/addon-notes: 5.3.19 => 5.3.19 @storybook/addon-viewport: 5.3.19 => 5.3.19 (6.3.2) @storybook/angular: 6.3.2 => 6.3.2 @types/hammerjs: 2.0.36 => 2.0.36 @types/jasmine: 2.8.8 => 2.8.8 @types/jasminewd2: 2.0.3 => 2.0.3 @types/karma: 5.0.0 => 5.0.0 @types/node: 12.11.1 => 12.11.1 (14.17.4, 15.12.4) @types/node-sass: 3.10.32 => 3.10.32 aws-amplify: 4.3.20 => 4.3.20 aws-sdk: 2.329.0 => 2.329.0 babel-loader: 8.0.4 => 8.0.4 (8.2.2) bsd-3-module: 0.0.0 codelyzer: 6.0.1 => 6.0.1 cordova-android: 10.1.0 => 10.1.0 cordova-ios: 5.1.1 => 5.1.1 cordova-plugin-advanced-http: 2.1.1 => 2.1.1 cordova-plugin-androidx-adapter: 1.1.3 => 1.1.3 cordova-plugin-chrome-apps-common: 1.0.7 => 1.0.7 cordova-plugin-chrome-apps-iossocketscommon: 1.0.2 => 1.0.2 cordova-plugin-chrome-apps-sockets-udp: 1.3.0 => 1.3.0 cordova-plugin-customurlscheme: 5.0.2 => 5.0.2 cordova-plugin-inappbrowser: 5.0.0 => 5.0.0 cordova-plugin-ionic-webview: 5.0.0 => 5.0.0 cordova-plugin-privacyscreen: 0.4.0 => 0.4.0 cordova-plugin-splashscreen: 5.0.3 => 5.0.3 cordova-plugin-splashscreen-test-ios: 1.0.0 cordova-plugin-splashscreen-tests: 5.0.3 cordova-plugin-statusbar: 2.4.3 => 2.4.3 cordova-plugin-statusbar-tests: 2.4.3 core-js: 2.5.7 => 2.5.7 (3.8.3, 3.15.2, 3.15.1) custom-license: 0.0.0 dayjs: 1.8.15 => 1.8.15 eslint: 6.8.0 => 6.8.0 eslint-config-airbnb: 17.1.0 => 17.1.0 eslint-config-prettier: 3.3.0 => 3.3.0 eslint-plugin-import: 2.14.0 => 2.14.0 eslint-plugin-prettier: 3.0.1 => 3.0.1 (2.7.0) hammerjs: 2.0.8 => 2.0.8 htmllint: 0.7.3 => 0.7.3 htmllint-cli: 0.0.7 => 0.0.7 husky: 1.3.1 => 1.3.1 invalid-with-comma: 0.0.0 jasmine-core: 3.5.0 => 3.5.0 jasmine-spec-reporter: 5.0.1 => 5.0.1 js-beautify: 1.8.9 => 1.8.9 jsdom: 11.12.0 => 11.12.0 json-server: 0.16.3 => 0.16.3 karma: 6.3.2 => 6.3.2 karma-chrome-launcher: 3.1.0 => 3.1.0 karma-coverage: 2.0.2 => 2.0.2 karma-coverage-coffee-example: 1.0.0 karma-coverage-istanbul-reporter: 2.1.1 => 2.1.1 karma-htmlfile-reporter: 0.3.8 => 0.3.8 karma-jasmine: 3.1.1 => 3.1.1 karma-jasmine-html-reporter: 1.5.3 => 1.5.3 karma-parallel: 0.3.1 => 0.3.1 karma-spec-reporter: 0.0.32 => 0.0.32 lib: 0.0.1 license-checker: 24.0.1 => 24.0.1 lint-staged: 11.0.0 => 11.0.0 memo-parser: 0.2.0 moment: 2.24.0 => 2.24.0 node-example: 1.0.0 phonegap-plugin-barcodescanner: 8.0.1 => 8.0.1 prettier: 1.15.3 => 1.15.3 (2.2.1) private: 0.0.0 protractor-example: 1.0.0 public-domain-module: 0.0.0 rxjs: 6.6.3 => 6.6.3 (6.6.7) rxjs-compat: 6.3.3 => 6.3.3 rxjs/ajax: undefined () rxjs/fetch: undefined () rxjs/internal-compatibility: undefined () rxjs/operators: undefined () rxjs/testing: undefined () rxjs/webSocket: undefined () sprintf-js: 1.1.2 => 1.1.2 (1.0.3) storybook-addon-designs: 6.0.1 => 6.0.1 storybook-addon-xd-designs: 5.5.0 => 5.5.0 stylelint: 13.13.1 => 13.13.1 stylelint-config-standard: 22.0.0 => 22.0.0 ts-node: 7.0.1 => 7.0.1 tslib: 2.0.0 => 2.0.0 (2.1.0, 1.14.1, 2.3.0, 2.3.1, 1.9.0) tslint: 6.1.0 => 6.1.0 tslint-config-airbnb: 5.11.1 => 5.11.1 tslint-config-prettier: 1.17.0 => 1.17.0 tslint-eslint-rules: 5.4.0 => 5.4.0 tslint-plugin-prettier: 2.0.1 => 2.0.1 typescript: 4.0.7 => 4.0.7 (4.1.5) typescript-example: 1.0.0 xlsx: 0.17.1 => 0.17.1 zone.js: 0.10.3 => 0.10.3 npmGlobalPackages: cordova: 11.0.0 ionic: 5.4.16 npm: 6.14.10 typescript: 4.6.4 ```

Describe the bug

I'm trying to figure out how I can get Social Authentication to work with a custom url scheme (cordova-plugin-customurlscheme) since I need to login from another web application. I have gotten to the point where I get redirected back to my app with the correct "code" and "state" parameters. However when the app needs to redirect I always get the same error: image My redirect URI is also set to work with the custom URL scheme I've set in the app so that I get redirected from the web app where I do the login flow. (redirectSignIn: 'commissioning://app/building'). To catch the redirect event from the custom url plugin I need to catch an event in main.ts (handleOpenURL) and from there on I've tried everything that was mentioned on existing issues that describe a similar behaviour. However I still can't get this to work within the Cordova application.

When I debug the app and open up the network tab I also see that I'm getting the token from the Social sign-in, the issue is that I can't redirect anywhere else in my app without running into the error mentioned above.

Expected behavior

As I've looked through a couple of issues on this repository surrounding similar issues I still encounter the same problem after having tried all possible solutions. Is there any clear way around this issue since I've been stuck on it for a couple of weeks and it needs to work with the Cordova app. I know that for react-native there is an urlOpener which probably resolves the issue mentioned above but this can't be used for any Cordova app as for as I'm aware of? Any help is appreciated.

I've also tried using the cordova-plugin-inappbrowser and passing that to the urlOpener but with the same results since it doesn't have the same functionality as the react-native InAppBrowser.

async function urlOpener(url, redirectUrl) {
  const browser = (<any>window).cordova.InAppBrowser.open(
    url,
    '_blank',
    'location=no,clearsessioncache=no,clearcache=no',
  );
}

....

oauth: {
      ....awsconfig.oauth,
      urlOpener,
    },

Reproduction steps

My current code to catch the handleOpenURL event from the custom url sceme plugin and to try and resolve the historyState error looks like this:

(<any>window).handleOpenURL = function(redirectUrl) {

  const params = new URL(redirectUrl).searchParams;

  if (params.has('code') && params.has('state')) {

    const url = `https://app/building?code=${params.get('code')}&state=${params.get('state')}`;

    document.location.href = url;

    (Auth as any)._handleAuthResponse(url);
  }
};

I'm then looking for any updates through the Hub.Listen

private hubListen = () => {
    Hub.listen('auth', ({ payload: { event, data } }) => {
      switch (event) {
        case 'signIn':
        case 'cognitoHostedUI':
          this.setUser(data);
          break;
        case 'signOut':
          console.log('logged out');
          break;
        case 'customOAuthState':
          this.setUser(data);
      }
    });

    Auth.currentAuthenticatedUser()
      .then(currentUser => this.setUser(currentUser))
      .catch(() => console.log('Not signed in'));
  };

Code Snippet

// Put your code below this line.

Log output

``` // Put your logs below this line ```

aws-exports.js

No response

Manual configuration

No response

Additional configuration

No response

Mobile Device

No response

Mobile Operating System

No response

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

No response

tannerabread commented 1 year ago

Hi :wave: @WouterVDW12 I am trying to reproduce this, but I'm new to Cordova and trying to figure out some config/options to create the app with.

I see that you're using Angular, but what platform are you building this on?

Another route would be if you can supply a small repo that would help me reproduce

brainplug commented 1 year ago

Hi @tannerabread ,

getting a basic Cordova app up and running should not require you to do anything with regards to config/options. But Cordova does rely on Android SDKs, tools and emulators to be present on your system. So, next to the 4 step getting started process presented on the Cordova home page, you'll also need to install Android Studio and a Java JDK 8 (you'll run into missing libraries exceptions if you opt for a more recent Java version, can be solved but easier to just install JDK 8). Point the JAVA_HOME env variable to the installed JDK folder.

We produce both android and ios platforms but for reproducing the issue you're good to go with just the android platform (so: cordova platform add android). Using Android Studio's SDK Manager you do need to install the right version of the SDK Tools or you'll get errors while trying to run your Cordova app as well. It seems Cordova needs the 30.0.3 SDK Tools (at least on my system) which you can install through the SDK Manager and then tab "SDK Tools" (check the "Show Package Details" checkbox which will allow you to select and download the required version).

At this point you might run into an issue about invalid keystores when trying cordova run android. It seems the one generated by Android Studio is incompatible with the one Cordova expects so delete the one referenced in the error at which point Cordova will generate a new one when it builds.

The final hurdle might then be the absence of an emulator, you can just launch one through the Device Manager of Android Studio and the Cordova app will then appear in that emulator.

To then easily get a basic Angular app up, I just downloaded the StackBlitz example from Angular (https://angular.io/generated/live-examples/getting-started-v0/stackblitz.html) into a separate folder, ran an "npm install" on it and then ng build --base-href . --output-path ..\your-cordova-project\www\

Run cordova run android again and you should have a basic Angular app to which can then be added amplify-js and the social signin functionality with a custom URL scheme. I can ask one of our developers to already provide a basic app with the social signin as described above but that might take some time as we're working towards a production release and, as always in IT, timing is tight.

tannerabread commented 1 year ago

@brainplug Wow thank you so much for the detailed response. I really appreciate the effort here!

I should be able to get it up and running this way. I will work on it today and get back to you

brainplug commented 1 year ago

Hi @tannerabread,

You're welcome, one last thing I forgot to mention is that I also set the ANDROID_SDK_ROOT environment variable to point to the Sdk folder where Google installs the Android SDKs (exact location depends on your OS but on my Windows-based laptop it is under C:\Users\my-user\AppData\Local\Android\Sdk).

tannerabread commented 1 year ago

@brainplug Are you using the Authenticator UI component for Angular with social sign-in or just manual signup/signin through your own functions? I have the app working and building for Android following your instructions (thank you!) but I'm running into errors trying to add Authentication to it with the UI component. If that's not the way you went about it I can pivot and go another way

brainplug commented 1 year ago

@tannerabread

We do it manually. For the moment we essentially have a "Login with external IdP" button which triggers the following code:

Auth.federatedSignIn({ customProvider: environment.cognito.federatedProviderName });

Signup is not handled in our application but entirely externally in the third-party IdP.

tannerabread commented 1 year ago

@brainplug I am still trying to reproduce this, in the meantime have you seen this section of the docs? The snippet looks similar to the error thrown

brainplug commented 1 year ago

@tannerabread

I've asked our developers to look at that snippet and see if it fixes anything. Will get back to you once I know more.

brainplug commented 1 year ago

Hi @tannerabread,

We're still looking into trying that snippet of code but for some reason currently still unclear to us we are now running into invalid_request and/or invalid_grant type of errors from Cognito's oauth2/token endpoint. It is not entirely clear where this all of a sudden is coming from as we just rebuilt the same app (code branch) where we first encountered the issue described above months ago.

tannerabread commented 1 year ago

@brainplug I'm just noticing that you have both aws-amplify and @aws-amplify/auth installed in this project. It may not be the solution here but having both aws-amplify and scoped packages such as @aws-amplify/auth in the same project is known to cause issues because of version mismatches. Sometimes it can cause 2 instances of Amplify to be initialized and only one to be configured correctly.

Instead you should just use aws-amplify and then import what you need from that package. E.g.:

import { Auth } from 'aws-amplify';

I would recommend removing the @aws-amplify/auth package from the project and seeing if it solves either of these issues

brainplug commented 1 year ago

@tannerabread We had to actually do it the other way around to get the application to compile. So: removing aws-amplify and using @aws-amplify/auth (and core which then also added cache). This then properly compiled but unfortunately did not change the error output:

ERROR] 13:25.513 OAuth - Error handling auth response. Error: invalid_request at e. (main-es2015.b00b58464a0855ce1138.js:1:4037575) at main-es2015.b00b58464a0855ce1138.js:1:4034498 at Object.next (main-es2015.b00b58464a0855ce1138.js:1:4034603) at o (main-es2015.b00b58464a0855ce1138.js:1:4033343) at l.invoke (polyfills-es2015.d2d950b2ab3b83bb6df9.js:1:7509) at i.run (polyfills-es2015.d2d950b2ab3b83bb6df9.js:1:2921) at polyfills-es2015.d2d950b2ab3b83bb6df9.js:1:13853 at l.invokeTask (polyfills-es2015.d2d950b2ab3b83bb6df9.js:1:8127) at i.runTask (polyfills-es2015.d2d950b2ab3b83bb6df9.js:1:3536) at m (polyfills-es2015.d2d950b2ab3b83bb6df9.js:1:10121)

This error happens in Amplify code (the method that does the call to oauth2/token) which then returns a 400 - Bad Request error code.

We then upgraded our dependencies to the latest versions (core to 4.7.6 and auth to 4.6.8) even though the version we're using (4.3.3) works perfectly fine in the (non-mobile) web app that is also part of the platform we're building. Upgrading to the latest versions also changes nothing.

We then removed the Zone-snippet again from polyfills.ts. We then still get the above invalid_request error immediately followed by the original error described by @WouterVDW12 in the first post. So, it does indeed seem that the Zone-snippet at least suppresses the original error but now we are a bit in the dark as to where this invalid_request is coming from (this was absent when we first ran into the original issue). It is not like the actual call itself to start the flow is terribly complex as it is literally a method behind a button that contains a single line:

Auth.federatedSignIn({ customProvider: 'name-of-the-provider' });

We do notice some weird things (now looking with multiple devs). Some see 2 calls to the token endpoint:

afbeelding

While others only see one:

afbeelding

Notice also the difference with regards to the code_verifier which is not always present (not for those with a single call and only once for those with 2 calls). A snippet from the Cognito config (variables replaced in the CI/CD pipelines):

cognito: { region: '${COGNITO_REGION}', userPoolId: '${COGNITO_USERPOOLID}', userPoolWebClientId: '${COGNITO_USERPOOLWEBCLIENTID}', oauth: { domain: 'baseapp-xxxxxxxxxxxx.auth.eu-central-1.amazoncognito.com', scopes: ['email', 'openid' ], redirectSignIn: 'commissioning://app/building', redirectSignOut: 'commissioning://app/login', responseType: 'code', },

Have you had more success in the meantime in getting a basic Angular/Cordova app up and running?

tannerabread commented 1 year ago

So, it does indeed seem that the Zone-snippet at least suppresses the original error but now we are a bit in the dark as to where this invalid_request is coming from

I missed this part when I was re-reading your latest comment. I also saw that error for just a minute while still not being able to actually log in and used the snippet and it went away.

I didn't get invalid_request but I did get the invalid_grant error you had mentioned earlier. For me this showed up after I shut down the running local angular server, deleted my .angular folder and the node_modules folder, and then restarted the app. I was seeing the invalid_grant at that time and to get rid of that I did a "Empty cache and hard reload" in the browser and it started working for me.

I'm still looking into it further but that's where I'm currently at on reproduction. I will update when I know more and I have someone else looking into it as well with me

tannerabread commented 1 year ago

After hours of trying to figure this out myself, this is where I'm at: I have a cordova app running with the angular code and cordova-plugin-customurlscheme which lets me click the button and then sign in through google but returns me back to the redirect route not logged in

I have been trying to catch the event in main.ts with handleOpenURL with no luck

brainplug commented 1 year ago

It seems then that you are now where we got stuck as well (disregarding the invalid_request issue we're having for the moment). We also were able to sign in externally and then got redirected back but without actually getting logged in. Before using the Zone-snippet we then saw the replaceState exception. While this is now being ignored, I'm not sure it is an error that we can actually simply ignore if we want login to succeed.

We'll continue trying to get rid of the invalid_request issue first.

WouterVDW12 commented 1 year ago

Hi @tannerabread, so I'm glad that you got something up and running to try and help us out here. So since you are getting redirected back to the app we know that the custom url scheme is doing it's job at least. It is strange however that the handleOpenUrl event is not being triggered/called in main.ts . I don't know if it could be related but can you check or add <preference name="AndroidLaunchMode" value="singleTask" /> to your config.xml under <platform name="android">. Maybe it'll resolve the handleOpenUrl not get called.

tannerabread commented 1 year ago

@WouterVDW12 I did try that solution in the xml with no luck there either. I'm going to look into it some more tomorrow but I tried implementing handleOpenURL in a few different places and had no luck.

I was also looking at the actual plugin and it doesn't look to have any updates in quite some time so I thought maybe the CLI way of installing it was maybe wrong now and tried to install it with the manual steps just to see if there was something not added to the Cordova project that would help with that method. No luck here either.

I'm going to keep searching for a solution and I've asked the team for any ideas on how to accomplish the goals here. I'll keep posting updates to this issue as I go

tannerabread commented 1 year ago

@brainplug @WouterVDW12 Just a little follow up as I was able to reproduce and get to where you are.

I don't think this is an Amplify issue but rather an issue with the routing through Cordova. I tried all of the plugins you suggested and a few more and had no success with any of them.

It seems the response that contains the code/state gets lost somewhere between the browser and the redirect in Cordova and I wasn't able to figure out a way to capture that in the app. There's still some things I might try.

Have you tried any solutions other than Cordova? In past issues related to this the only success I saw was with Capacitor.

brainplug commented 1 year ago

Hi @tannerabread ,

No, we have not yet tried anything other than Cordova because this app is essentially one we got from HQ and that we use as-is for our regional market, with the single exception of replacing a standard Cognito setup (without federation) to one that can integrate with our third party IdP (so: with federation).

I could ask @WouterVDW12 to try out Capacitor but in that case it would help greatly if you could point us to any documentation on those past issues that explain how they got to success using Capacitor.

tannerabread commented 1 year ago

This is the user that was able to get it working with Capacitor. From what I was reading on this issue, it seemed to be related to Angular lifecycle issues. All of the workarounds seem to be pretty messy but I'm also pretty determined to make one of them work

This customer was able to use Ionic and ionic-plugin-deeplinks to intercept the custom url in the app. They then caught the exception we previously ignored and retrieved the user credentials there.

Actually now that I'm typing this out and reading that, your original app might be able to do the same thing with catching the exception and fetching the credentials using the code that is returned. I only got that error once and would need to restart my app to get back to it. I'll try to do that sometime this week and see if we can get the same results as this post, but @WouterVDW12 might be able to implement the exception handling that way.

Also can you clarify, were you ever able to get handleOpenURL working in main.ts? Did you get passed your invalid_request/grant error?

WouterVDW12 commented 1 year ago

Hi @tannerabread, To anwser your first question: yes, I was able to cacth the handleOpenUrlin main.ts when the redirect flow was still working for us. Here is some of the logging I did then where you can see the code and state parameters being passed from the redirect right through to the handleOpenUrlfunction (with some amplify events triggering along the way): image

To answer your second question, we didn't get passed the invalid_request error yet and are limited in testing different stuff out currently because our IdP is experiencing some issues which blocks our progress when logging into the app.

WouterVDW12 commented 1 year ago

Hi @tannerabread, Small update from my side, I've got the login up and running again to where it was when we started encountering the issue described here. So the one-liner that you suggested does indeed ingore the error being thrown about the "HistoryState". Could you elaborate on how we could catch the exception and retrieve the user credentials to allow login?

I'm also doing some logging of the events coming in from Hub.listen and see these events: image I don't know if these duplicate events are anything to worry about but I also never seem to get to any signIn event which I need for the login to succeed right? The only events I ever get are parsingCallBackUrl and codeFlow.

tannerabread commented 1 year ago

@WouterVDW12

Can I ask why you are recreating the URL to be https://app/building instead of what you originally had with commissioning://app/building?

const url = `https://app/building?code=${params.get('code')}&state=${params.get('state')}`;

I'm trying to understand what I'm doing wrong with my own app to not achieve the same results as you, is it possible for you to send everything you have related to Amplify/Auth in main.ts and also anything relevant in any of the app files?

WouterVDW12 commented 1 year ago

@tannerabread I've read on some issues related to this that Amplify expects this to be https/http and not the scheme defined for the redirect. The result with the scheme or the new url is the same however. To share everthing I have so far to get it working again:

main.ts

import { Auth } from 'aws-amplify';

....

const awsConfig = { Auth {....} }

(<any>window).handleOpenURL = function(redirectUrl) {
  console.log('redirected url', redirectUrl);

  const params = new URL(redirectUrl).searchParams;

  if (params.has('code') && params.has('state')) {
    console.log({ code: params.get('code'), state: params.get('state') });

    const url = `https://app/building?code=${params.get('code')}&state=${params.get('state')}`;

    (Auth as any)._handleAuthResponse(url);
  }
};

Auth.configure(awsConfig);

Triggering a refresh with the document.location.href seemed to cause the double token request which caused both to fail.

I've also switched the code for the hub.Listen to be included in the app.component.ts (seems to avoid the double events triggering) => app.component.ts

import { Auth, Hub } from 'aws-amplify';

...

constructor(.....) {
    ...
    this.hubListen();
  }

private setUser = user => {
    ...sets user to an object and confirms login to the app
  };

  private hubListen = () => {
    Hub.listen('auth', ({ payload: { event, data } }) => {
      console.log('Hub auth: ', { event, data });
      switch (event) {
        case 'signIn':
          console.log("signIn event");
          this.setUser(data);
          break;
        case 'signOut':
          console.log('logged out');
          break;
        case 'customOAuthState':
          this.setUser(data);
      }
    });

    Auth.currentAuthenticatedUser()
      .then(currentUser => this.setUser(currentUser))
      .catch(() => console.log('Not signed in'));
  };

To trigger authentication this gets called when login button is pressed (in seperate file related to login processing):

authInitiateAuth() {
    Auth.federatedSignIn({ customProvider: ... });
 }

And for the package.json:

"aws-amplify": "4.3.41",
"cordova": {
    "plugins": {
      ....,
      "cordova-plugin-customurlscheme": {
        "URL_SCHEME": "commissioning"
      }
    },
    "platforms": [
      "android",
      "ios"
    ]
  },

To use the plugin I just ran

cordova plugin add cordova-plugin-customurlscheme --variable URL_SCHEME=commissioning

If you need anything more let me know

WouterVDW12 commented 1 year ago

@tannerabread Update on this just now as well concerning shifting the Hub.Listen() logic to app.component.ts I see that when closing the app and restarting it, Auth.currentAuthenticatedUser() is getting the user credentials from logging in the first time the app opened. This is being passed to the setUser when re-opening the app: image Which means that Amplify is setting this correctly somewhere but I don't know when because Hub.Listen is not getting the signIn event?

tannerabread commented 1 year ago

I'm thinking I'm somehow improperly using the plugin, even though I have followed all of the steps you outlined. I can successfully login without federation so I know the app works and Amplify is connected properly, I'm just at a different part of the redirect not working than you. I'm still not sure why my handleOpenURL handler is not firing at all, I basically copied what you did.

Maybe you can check out this repo and see where I went wrong? With this repo, I build to the cordova app with

ng build --base-href . --output-path ../cordovaApp/www/

and then in the root of the cordova folder I run

cordova plugin add cordova-plugin-customurlscheme --variable URL_SCHEME=commissioning

One thing I have noticed about the plugin CLI command is that it doesn't seem to do any of the steps that are outlined in the manual configuration section of the plugin docs. I tried doing them manually as well and still had no luck I can upload a build from the cordova app and folder if we don't figure it out with the first repo

One last thing to note, I can see the redirect URI containing the code/state in logcat in android studio but none of the events seem to be capturing it, but I see commissioning://authenticated-page/?code=...&state=...

WouterVDW12 commented 1 year ago

Hi @tannerabread, I do see one thing that might be the culprit of the handleOpenUrl not firing, could you append this cordova.js script line to your index.html file:

<body>
  <app-root></app-root>
  <script type="text/javascript" src="cordova.js"></script>
</body>

The plugin also only adjusts the files during cordova prepare where you should see the plugin being installed: Installing "cordova-plugin-customurlscheme" for android My package.json also looks a bit different to yours with the plugin being defined under:

"cordova": {
    "plugins": {
      .... ,
      "cordova-plugin-customurlscheme": {
        "URL_SCHEME": "commissioning"
      }
    },
    "platforms": [
      "android"
    ]
  }

So I don't know if the plugin is being picked up when you build your app. We are using cordova prepare to build or app which looks at the plugins and platform defined in the package.json to build everything.

tannerabread commented 1 year ago

@WouterVDW12 That was definitely what I was missing. I really appreciate your patience through this process and all of your explanations with this issue.

I am exactly where you are at now, where the login technically works but doesn't show up until you restart the app. The hub event 'signIn' never fires from my findings either.

With my Check Status button I was able to see that the user is logged in before exiting the app, but the setUser function is not being applied in any of the hub events. I am trying to figure that part out and will get with the team about it.

Another note, I do think the polyfill is worth keeping in your code. I tried both with and without the polyfill and it gets the same results, minus the error in the console.

I also added Amplify.Logger.LOG_LEVEL = 'DEBUG'; in my main.ts that lets me see that the following error in the console:

"[DEBUG] 47:48.886 AuthClass - Error in cognito hosted auth response [object DOMException]"

I'm not sure why this shows up because the login is actually successful. I then I added the setUser function to my Check Status button and now I can display the username without exiting the app by clicking that button.

I've uploaded the changes to the same repo so that you can see where I'm at. Having to add a button to check the status is not the ideal solution but maybe there is something else we can listen for to set the user from currentAuthenticatedUser after that flow is complete.

I'll get back to you ASAP after discussing this with the rest of the team.

tannerabread commented 1 year ago

I almost forgot. In the handleOpenURL I switched it back to sending through the 'redirectURL' instead of the modified one to get it to work for me

tannerabread commented 1 year ago

@WouterVDW12 I still have someone on the team looking into it, but I'll tell you where I'm at now as I was able to unblock myself with a slightly odd workaround.

A slight variation of the DOMException from your original error shows up, even with the polyfill in place. For myself, it still correctly fills the local storage with everything needed from the Auth response, as you were seeing when you restarted the app. image

I made the hubListen function async and moved the call to Auth.currentAuthenticatedUser() to the 'codeFlow' event in the listener. I then get the error

AuthClass - OAuth signIn in progress timeout

It seems that the listener for the Hub event 'auth' is removed after this error which is followed by the call to currentAuthenticatedUser that sets the correct user to my user property in my app component. I added a detectChange function to reload my UI when the user is set. This removes the need for the Check Status button I had previously implemented and allows the app to update itself.

I have updated my repo with the current state of the app if you want to take a look, but it effectively works it's just not an ideal solution.

I'll keep updating this as I find out more but I wanted to send through my workaround for the time being and see if it worked for you. What I do know is that the redirect with the plugin is working as far as setting the local storage for Auth, but that error about replaceState seems to be killing the Auth flow with the Hub listeners and stopping Hub from getting to the "signIn" event.

WouterVDW12 commented 1 year ago

Hi @tannerabread, So your piece of code helped us to set the user without the need of manually reloading the app but we've encountered another issue.

We remain on our login page even though our user has been set and we should be redirected to the redirectSignIndefined in the oauthconfig for Cognito. Are you redirecting anywhere to a different page in your app? I see you have a "authenticated-page" to go to once logged in. Are you also getting to that page through the federated sign in? How does your redirectSignInlook like in that case? Does it need to change in order for the redirect to work inside the app? We should not normally do the redirect ourselves, that should be the responsibility of Amplify am I correct?

tannerabread commented 1 year ago

Hi @WouterVDW12 I'm not sure if my redirect was working either, I will look more into it before the weekend and get back to you.

WouterVDW12 commented 1 year ago

Hi @tannerabread, any update yet?

tannerabread commented 1 year ago

Hi @WouterVDW12 Sorry for not getting back to you sooner. I tried a few things but I am at the same spot as you where it doesn't redirect to the path I specify in redirectSignIn, it never reaches that "authenticated-page" and instead stays on the "login" page. You are correct that this should be handled by the redirectSignIn that you specify in the config. I suspect that missing the configuring step and the signIn step through the Hub have something to do with this portion not working.

I still need feedback from the engineering team on what would be needed for a fix with this issue, but I am staying on top of it and reminding the team frequently about this issue. I have provided them with full reproduction steps and all of the details to this issue. The team has been busy with the version 5 release that happened this week. I appreciate all of your patience with this issue so far and hope that we can find a solution for this soon.

brainplug commented 1 year ago

Hi @tannerabread, do you have any update? Given that you mentioned the Amplify 5 release we upgraded to that one in the meantime but, not unexpectedly, that didn't change anything and we are still stuck on the redirection.

tannerabread commented 1 year ago

Hi @brainplug, I'm really sorry about the delay again. I was finally able to look into this with one of the Auth experts and after a lot of debugging we came to the conclusion that it's not recommended to use the _handleAuthResponse method directly. The missing Hub events were indicative of the auth flow not working as designed and that also points to why the redirect URI was not working.

A better solution would be to try to find one of the cordova plugins and implement a urlOpener like React Native suggests and pass that to the config.

I will be trying to implement this tomorrow with the cordova-plugin-inappbrowser but wanted to let you know the progress. I know you already tried this route initially but it would be the preferred solution.

brainplug commented 1 year ago

Hi @tannerabread, let us know if you succeed because our developers also ran into issues using that approach.

tannerabread commented 1 year ago

I'm also running into issues currently. Let me just say where I'm at with it so you can say if it's where you were running into issues as well.

I am able to get the cordova-plugin-inappbrowser to work as a urlOpener, but after signing in through the social/IdP, no redirect happens and the inappbrowser remains open in the app

WouterVDW12 commented 1 year ago

Hi @tannerabread, I've tried the inappbrowser again to see where the issue was with that and I'm also stuck in the inappbrowser without being redirected. However I fear that if we would get the inappbrowser working, we would run into the same issue again. Unless using the urlOpener does all the routing for us (even when working with Cordova)?

tannerabread commented 1 year ago

That's correct, the urlOpener should handle the auth flow and redirects that are set up with Amplify even with Cordova.

I think any solution for this use case is going to end up being sort of hacky, unfortunately.

I still have not had any success with the task, currently I am trying to return a promise from the urlOpener that is fulfilled if the code and state are present in the url, and then close the inappbrowser with inAppBrowser.close(). This may also be a dead end but the interface for urlOpener is as follows:

urlOpener?: (url: string, redirectUrl: string) => Promise<any>;
tannerabread commented 1 year ago

Hi @WouterVDW12, @brainplug

I have made a detailed README in my repo outlining everything that we have discussed and discovered here in this thread/issue. Please leave a comment here if anything looks incorrect or if I missed something.

The task is still in the (more immediate) backlog for the team and should be continued by another engineer soon. I appreciate your patience throughout this process

brainplug commented 1 year ago

Hi @tannerabread,

I just went through your README and it seems correct and complete to me. I'd also like to thank you for diving in deep with us, providing the test project and writing everything up in the README. All of this provides a solid foundation for further investigation and will hopefully lead to a solution others can also benefit from in the (near) future.

ThibaultGobert commented 10 months ago

Hi @tannerabread

I'm one of the developers on the aforementioned project. I was wondering if this task is still in the backlog?

I'm asking because Apple requires us to have an in-app process and we are re-exploring the inAppBrowser approach for this. For this we still need to resolve the redirect problem.

Any update would be appreciated so that I can further explore the options.