capacitor-community / generic-oauth2

Generic Capacitor OAuth 2 client plugin. Stop the war in Ukraine!
MIT License
234 stars 115 forks source link

Feat: Logout Funtion & URL #97

Open loonix opened 4 years ago

loonix commented 4 years ago

Describe the Feature

The current logout functionally only cleans any cached tokens but does not log out of the provider. For that to work we would have to provide a logout URL, which is not supported.

Platform(s) Support Requested

Describe Preferred Solution

Be able to logout completely from the app, it should clear the app cache and log the user out.

Describe Alternatives

None

Related Code

n/a

Additional Context

Reference: https://github.com/moberwasserlechner/capacitor-oauth2/issues/96

loonix commented 4 years ago

Coded a workaround for azure b2c logout, while this feature is not completed:

//(...)
import { Plugins } from '@capacitor/core';
const { Browser } = Plugins;

//(...)
const urlLogout = 'https://XXXXXX.b2clogin.com/tfp/XXXXXX.onmicrosoft.com/b2c_1_sign_in/oauth2/v2.0/logout?client_id=XXXXX-XXXXX-XXXXX-XXXXX&response_type=token&redirect_uri=com.company.app://oauth/redirect&scope=openid%20offline_access%20https://XXXXX.onmicrosoft.com/api/demo.read';

// just opens and closes the browser with the previous URL
    const browser = await Browser.open({ url: urlLogout});
    Browser.close();

// Then run the logout from plugin
  Plugins.OAuth2Client.logout(environment.oauth2Options)
      .then(() => sessionStorage.clear())
      .catch(reason => { console.error('OAuth logout failed', reason); });
JCASTANO commented 4 years ago

hi, any official solution for this?

tobium commented 4 years ago

the workaround does not work because login and logout does not use the same browser instance...

loonix commented 4 years ago

It does work for me, its not a definitive fix though

tobium commented 4 years ago

how do you do the login? via the plugin (with it's native browser usage?)

Are youz forced to type in your username and password after logging out and logging in again?

loonix commented 4 years ago

The workaround works for azure b2c, not sure about other types of logins. I have made a repo that is working for logins, I have not impletemented there this workaround, but you can have a picture of how I do it: https://github.com/loonix/capacitor-oauth2-azure-example/blob/master/src/app/home/home.page.ts

Give it a try and tell us how it goes.

loonix commented 4 years ago

@tobium you tried in IOS Right? Seems to work on the IOS emulator but not on the device :( . Android works fine on my phone. Workaround only for android then :(.

~Would be great if this plugin could have the logout, as its kind of a deal-breaker for using it

moberwasserlechner commented 4 years ago

~Would be great if this plugin could have the logout, as its kind of a deal-breaker for using it

PRs are welcome ;)

https://github.com/moberwasserlechner/capacitor-oauth2/blob/master/.github/CONTRIBUTING.md#pull-request-guidelines

loonix commented 4 years ago

After some digging I found this, not sure if it will help to develop this feature. If I knew how to implement it I would. https://github.com/Azure-Samples/active-directory-b2c-ios-native-appauth/issues/6

moberwasserlechner commented 4 years ago

@loonix Thanks for the example. Such hints are very helpful as I need less research when starting to work on this task

loonix commented 4 years ago

@tobium I was able to logout with IOS but had to increase the timeout specifically for IOS.

const urlLogout = https://${environment.tenantName}.b2clogin.com/tfp/${environment.tenantName}.onmicrosoft.com/${environment.signInPolicy}/oauth2/v2.0/logout?client_id=${environment.clientID}&response_type=token&redirect_uri=${environment.redirectUrl}&scope=openid%20offline_access%20https://XXXX.onmicrosoft.com/api/demo.read;

 // Workaround to get IOS logout
    if(Capacitor.platform === 'ios'){
      await Browser.open({ url: urlLogout }).finally(() => setTimeout(() => Browser.close(), 4000));
      this.onLogoutClick();
      return;
    }
tjelz commented 3 years ago

Not able to get it working with any of these solutions, did anyone work out a different way to actually log the user out?

loonix commented 3 years ago

https://github.com/loonix/capacitor-oauth2-azure-example

Check my repo, and the readme file and see if it helps.

PieterT2000 commented 3 years ago

+1 for this feature

PragyaSingla commented 3 years ago

The workaround works for azure b2c, not sure about other types of logins. I have made a repo that is working for logins, I have not impletemented there this workaround, but you can have a picture of how I do it: https://github.com/loonix/capacitor-oauth2-azure-example/blob/master/src/app/home/home.page.ts

Give it a try and tell us how it goes.

I tried same but logout is not working for me. Any solution for logout in android?

moberwasserlechner commented 3 years ago

@PragyaSingla not yet but I try to include this into the upcoming 3.x release, that needs Capacitor 3 as its minimum version.

This is a breaking change so I will not back port it to 2.x.

moberwasserlechner commented 3 years ago

I'm not able to implement this in 3.0.0 and therefore postpone it.

SantoshPisini commented 2 years ago

With this setup I'm able to logout

For iOS await Browser.open({ url }).finally(() => setTimeout(() => { Browser.close(); }, 1500) );

For Android await Browser.open({ url }).finally();

Where url = https://{TENANT_NAME}.b2clogin.com/tfp/{TENANT_NAME}.onmicrosoft.com/{USER_FLOW_NAME}/oauth2/v2.0/logout

DwieDima commented 2 years ago

i can confirm that the workaround from @loonix works. For IOS it always works, but for Android it only works if the system browser is Chrome. If the system browser on Android devices is not Chrome, the logout will not work. To fix this issue i'm using the Cordova In App Browser for android, where the system browser can be forced. Here is a working snippet for it with angular:


  import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx';
  import { Browser } from '@capacitor/browser';
  import { OAuth2Client } from '@byteowls/capacitor-oauth2';
  import { isPlatform } from '@ionic/angular';

  constructor(private iab: InAppBrowser) {}

  public signOut() {
    const url = 'YOUR_LOGOUT_URL';

    return of(isPlatform('android')).pipe(
      switchMap((isAndroid) => (isAndroid ? this.handleAndroidSignOut(url) : this.handleIosSignOut(url))),
      switchMap(() => from(OAuth2Client.logout(OAUTH_OPTIONS)))
    );
  }

  private handleIosSignOut(url: string) {
    return from(Browser.open({ url })).pipe(
      switchMap(() => from(Browser.addListener('browserPageLoaded', () => {}))), // await page load to make sure logout was called
      switchMap(() => Browser.close())
    );
  }

  private handleAndroidSignOut(url: string) {
    const ref = this.iab.create(url, '_system', 'hidden=yes'); 
    return ref.on('loadstop').pipe(tap(() => ref.close())); // // await page load to make sure logout was called
  }

As a sideeffekt you'll see the Browser popup and close quickly. It would be nice, if this could happen in background for better UX. This should be fixed in the plugin itself.

DenysAshikhin commented 1 year ago

@DwieDima That looks exactly what would work for me. I have the app-browser working but I'm not sure where is the .pipe coming from? I also can't close the window:

let ref = await InAppBrowser.create(
    `https://login.microsoftonline.com/<tenant_id>/oauth2/v2.0/logout`,
    "_system",
    "hidden=yes"
  );
ref.close()

The hidden flag isn't making the window hidden, and the .close() isn't closing it. Other than that it does work as a solution....

I'm trying to get this to work on reactJS PWA ionic + capacitor.

DwieDima commented 1 year ago

@DenysAshikhin try this promise-based snipped:

  public async signOut(): Promise<void> {
    const url = 'your-auth-url';

    if(isPlatform('android')) {
      await this.handleAndroidSignOut(url);
    } else {
      await this.handleIosSignOut(url);
    }

    await OAuth2Client.logout(OAUTH_OPTIONS);
  }

  private async handleIosSignOut(url: string): Promise<void> {
    await Browser.open({ url });
    await Browser.addListener('browserPageLoaded', () => {});
    await Browser.close();
  }

  private async handleAndroidSignOut(url: string): Promise<void> {
    const ref = this.iab.create(url, '_system');
    ref.show();
    await new Promise(resolve => setTimeout(() => resolve, 300));
    ref.close();
  }
mozzi commented 1 year ago

@DwieDima Did you only install "@awesome-cordova-plugins/in-app-browser": "^6.3.0",, or "cordova-plugin-inappbrowser": "^5.0.0" too? It seems npx cap sync doesnt see @awesome-cordova-plugins and only adds inAppbrowser when i also install "cordova-plugin-inappbrowser": "^5.0.0" Making let ref = await InAppBrowser.create(url,"_system",'hidden=yes'); doesnt open chrome browser by default, but shows browser select dialog https://user-images.githubusercontent.com/906026/213331694-87da460c-888b-485c-abd7-b63ced064228.png

noahkriesi commented 4 months ago

I just call 'authorize' again with my logout url. This works for me.

  getAzureB2cOAuth2Options(logout: boolean = false): OAuth2AuthenticateOptions {
    return {
      ...
      authorizationBaseUrl: `https://....onmicrosoft.com/.../oauth2/v2.0/` + (!logout ? 'authorize' : 'logout'),
     ...
    };
  }

...

  logout() {
    GenericOAuth2.authenticate(this.getAzureB2cOAuth2Options(true))
    .then(async response => {
      console.log('OAuth response', response);
    })
    .catch(reason => {
      console.error('OAuth rejected', reason);
    });
  }
singatias commented 3 months ago

It would be nice if the plugin would handle it automatically if we provided the logoutUrl in the options.

zolakt commented 1 month ago

Sorry if this is a stupid question, but I'm getting lost in this. Is anything supposed to work regarding logout?

I'm trying it on the web and Android, but pretty much nothing happens. It doesn't clear the tokens from storage, and it doesn't redirect to the logout url. The promise returns true, there is no error... but nothing happens.

I'm doing it pretty much the same as the example in the docs here:

GenericOAuth2.logout(oauth2LogoutOptions, this.accessToken).then(() => {
    // do something
}).catch(reason => {
    console.error('OAuth logout failed', reason);
});

If it is not expected to work, then I don't really get:

Again, sorry if it's a stupid question, but all of this is quite confusing and misleading.

Btw. we have our own IdentityServer4, and the login is working just fine. Just not the logout

UPDATE: After trying different workarounds, the approach suggested by @noahkriesi works best for my use case: https://github.com/capacitor-community/generic-oauth2/issues/97#issuecomment-2178188975. Thanks!