OAuth2 auth code flow w/ Capacitor Browser


#1

I want to use the Capacitor browser for an OAuth2 auth code flow. In the Cordova inappbrowser world, you could capture the URL and close the browser, once the redirect is complete. However, I’m running into several issues:

  • The listeners are returning empty data objects
  • Close is only supported in iOS

Is this even possible in the Capacitor world?

  await Browser.open({url: 'https://xxxxx'});

  Browser.addListener('browserPageLoaded', (data) => {
    console.log('Data - browserPageLoaded: '+JSON.stringify(data));  ---> returning {}
  });

  Browser.addListener('browserFinished', (data) => {
    console.log('Data - browserFinished: '+JSON.stringify(data)); ---> returning {}
  });

#2

I’m seeing the same thing. I have to upgrade an app from Ionic 3 to Ionic 4 and would like to use Capacitor. The inAppBrowser has worked well, and I’ve enjoyed using Capacitor (without plugins) so far.
However, this would be a blocker for using Capacitor for the upgraded production app.

I will be watching this thread closely and trying to find a solution to this myself also.


#3


Saw this on the Capacitor slack channel.


#4

@yulemata provided a solution on the slack channel which I haven’t tried out yet due to other work. Posting it here as it might be helpful to others.

I did a preliminary test with ios and I’ve got the redirection working with Browser and App plugins. The App plugin is the one that actually handles the redirection. For that you will need to:

  1. Open your auth url with the Browser plugin await Browser.open({url: <your url>});

  2. Set up a listener with the App plugin https://capacitor.ionicframework.com/docs/apis/app as follows

capacitor.ionicframework.com

App - Capacitor

The Native Bridge for Cross-Platform Web Apps. Invoke Native SDKs on iOS, Android, Electron, and the Web with one code base. Optimized for Ionic Framework apps, or use with any web app framework.

private async setupRedirectListener(){

 App.addListener('appUrlOpen', async (data) =&gt; {

   // could be explicit with the redirectUri since we know we're native

   if (data.url.indexOf(AUTH_CONFIG.redirectUri) !== -1) {

       await Browser.close();

       await this.handleRedirectUrl(data.url);

   }

});

so, in the listener you first close the Browser (since the redirection callback has been provided) and then you handle the redirection according to your application requirements (the handleRedirectUrl() method)

I kept the capacitor default URLScheme which is capacitor://. In that case my callback url was capacitor://callback

but you will need to change that accordingly with your app bundle id in the Info.plist


#5

The docs suggest that Browser.close() only works on iOS. Additionally, I could not get the appUrlOpen listener to trigger correctly on Android. It appears the latter is being tracked as an issue here.


#6

Below code works fine on iOS, but the listener does not fire on Android and Browser.close() does not work. The logs clearly show the listener getting registered, but the event is never fired.

async linkAccount() {

   App.addListener('appUrlOpen', (data) => {
   console.log('Data: '+JSON.stringify(data));
  })

  this.addRedirectListener();

  await Browser.open({url: 'https://domain/oauth/authenticate/?client_id=xxxx&response_type=token&redirect_url=capacitor://localhost/callback'});
}

private async addRedirectListener() {
  App.addListener('appUrlOpen', async (data: any) => {
    console.log('appUrlOpen: '+data.url);
    console.debug('AppComponent - constructor - appUrlOpen');
    if(data.url.indexOf('callback#')!=-1) {
      let regEx = /(callback#access_token=)(.*)/g;
      let code = regEx.exec(data.url)[2];
      console.log(code);
      this.router.navigate(['/tabs/profile/link/'+code]);
    }
    Browser.close();
  });
}

Logs:

W/zygote: Attempt to remove non-JNI local reference, dumping thread
V/Capacitor/Plugin: To native (Capacitor plugin): callbackId: 24424146, pluginId: App, methodName: addListener
V/Capacitor: callback: 24424146, pluginId: App, methodName: addListener, methodData: {“eventName”:“appUrlOpen”}
V/Capacitor/Plugin: To native (Capacitor plugin): callbackId: 24424147, pluginId: Browser, methodName: open
V/Capacitor: callback: 24424147, pluginId: Browser, methodName: open, methodData: {“url”:“xxxxxxx”}
W/cr_Ime: updateState: type [0->0], flags [0], show [false],
D/Capacitor: App paused
W/zygote: Attempt to remove non-JNI local reference, dumping thread
W/zygote: Attempt to remove non-JNI local reference, dumping thread
W/zygote: Attempt to remove non-JNI local reference, dumping thread
I/zygote: NativeAllocBackground concurrent copying GC freed 10(48KB) AllocSpace objects, 0(0B) LOS objects, 57% free, 1122KB/2MB, paused 659us total 188.309ms
W/zygote: Attempt to remove non-JNI local reference, dumping thread
I/chatty: uid=10083(u0_a83) events.beerfest.app identical 1 line
W/zygote: Attempt to remove non-JNI local reference, dumping thread
I/zygote: NativeAllocBackground concurrent copying GC freed 10(47KB) AllocSpace objects, 0(0B) LOS objects, 57% free, 1123KB/2MB, paused 5.489ms total 34.557ms
W/zygote: Attempt to remove non-JNI local reference, dumping thread
W/zygote: Attempt to remove non-JNI local reference, dumping thread
I/zygote: NativeAllocBackground concurrent copying GC freed 15(47KB) AllocSpace objects, 0(0B) LOS objects, 57% free, 1124KB/2MB, paused 29.306ms total 85.913ms
D/EGL_emulation: eglMakeCurrent: 0x9fd061a0: ver 3 0 (tinfo 0x8df3f2e0)
D/Capacitor: Saving instance state!
D/Capacitor/Plugin/App: Firing change: false
V/Capacitor/Plugin/App: Notifying listeners for event appStateChange
D/Capacitor: App stopped
V/Capacitor: callback: -1, pluginId: Console, methodName: log, methodData: {“level”:“log”,“message”:“App state changed {“isActive”:false}”}
I/Capacitor/Plugin/Console: App state changed {“isActive”:false}

------ Browser close ------

D/Capacitor: App restarted
D/Capacitor: App started
D/Capacitor/Plugin/App: Firing change: true
V/Capacitor/Plugin/App: Notifying listeners for event appStateChange
D/Capacitor: App resumed
V/Capacitor: callback: -1, pluginId: Console, methodName: log, methodData: {“level”:“log”,“message”:“App state changed {“isActive”:true}”}
I/Capacitor/Plugin/Console: App state changed {“isActive”:true}
V/Capacitor/Plugin/Network: Notifying listeners for event networkStatusChange
D/Capacitor/Plugin/Network: No listeners found for event networkStatusChange
W/zygote: Attempt to remove non-JNI local reference, dumping thread
D/EGL_emulation: eglMakeCurrent: 0x9fd061a0: ver 3 0 (tinfo 0x8df3f2e0)
W/zygote: Attempt to remove non-JNI local reference, dumping thread
W/zygote: Attempt to remove non-JNI local reference, dumping thread
I/zygote: NativeAllocBackground concurrent copying GC freed 10(32KB) AllocSpace objects, 0(0B) LOS objects, 57% free, 1114KB/2MB, paused 8.106ms total 58.317ms
W/zygote: Attempt to remove non-JNI local reference, dumping thread
W/zygote: Attempt to remove non-JNI local reference, dumping thread
I/zygote: NativeAllocBackground concurrent copying GC freed 11(31KB) AllocSpace objects, 0(0B) LOS objects, 57% free, 1130KB/2MB, paused 953us total 112.234ms
W/zygote: Attempt to remove non-JNI local reference, dumping thread
I/zygote: NativeAllocBackground concurrent copying GC freed 9(31KB) AllocSpace objects, 0(0B) LOS objects, 57% free, 1114KB/2MB, paused 7.752ms total 29.100ms
W/zygote: Attempt to remove non-JNI local reference, dumping thread