amplify-js: Social Authentication not working on Ionic

The docs tell us we can use federated sign in like so:

federatedSignIn('facebook')

To create a cognito user from facebook OAuth. However on a device with Ionic, the redirection process is broken.

In the browser, the process works great, however on IOS or Android it does not.

once the authentication is done, we cannot redirect back to localhost as it won’t go back to the app.

If you set the redirect URI to the apps url schema, the redirection does work, however the authentication system does not pick this up when the app is opened from the login redirect.

I have in place a function that can detect the URL being used to open the app and it is correctly opening the app with the appended token information in the redirect URL.

However Amplify does not pick this up. I then tried implementing the HUB to detect auth changes and again this had no effect. Once the redirect away to the Amplify federated login page occurs, it effectively breaks the process.

It is possible to fix this by using the Facebook Ionic wrapper or the Google auth wrapper, however this creates a federated identity which then means you cannot read and write using the API.

Any advice on this would be great as this is a real blocker. Is there a way to take the redirect tokens and pass them to amplify manually? This would solve the problem.

The goal is to be able to create a cognito user in the Amplify pool, from the social providers.

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 10
  • Comments: 48 (4 by maintainers)

Most upvoted comments

Hi @marcangelx

I currently use the latest version of amplify 5.3.10 and we are working without issues in production with web, android and ios. I had to do a few more tweaks to make it work, but it’s very similar to my last comment. We use Angular + Ionic + Capacitor.

Here’s how we make it work:

async federatedSignIn(provider: 'Facebook' | 'Google' | 'SignInWithApple') {
    // @ts-ignore
    await Auth.federatedSignIn({provider, customState: this.redirectUrl ? this.redirectUrl : this.router.url});
  }
  private initializeListenersForLogin() {
    const isWebPlatform = !Capacitor.isNativePlatform();
    if (!isWebPlatform) {
      App.addListener('appUrlOpen', ({url}) => {
        if (url.includes('/loginCallback')) {
          if (window && typeof window.history !== 'undefined') {
            this.saveWindowHistoryReplaceStateFunction = window.history.replaceState;
            window.history.replaceState = () => {};
          }

          // @ts-ignore
          // eslint-disable-next-line no-underscore-dangle
          (Auth as any)._handleAuthResponse(url);

          if (PLATFORM === 'ios') {
            Browser.close();
          }
        } else {
          const path = url.split('myapp.com').pop();
          if (path) {
            this.router.navigateByUrl(path);
          }
        }
      });
    }

    Hub.listen('auth', async ({payload: {event, data}}) => {
      if (isWebPlatform) {
        if (event === 'customOAuthState') {
          this.navController.navigateRoot(decodeURIComponent(data), {replaceUrl: true});
        }
      } else {
        if (event === 'signIn') {
          this.userService.changeUserStatusAuthentication(true);
        } else if (event === 'customOAuthState') {
          if (window && typeof window.history !== 'undefined') {
            window.history.replaceState = this.saveWindowHistoryReplaceStateFunction;
          }

          const loading = await this.loadingController.create({message: 'Loading...'});
          loading.present();

          this.router.navigateByUrl(decodeURIComponent(data), {replaceUrl: true})
            .finally(() => loading.dismiss());
        }
      }
    });
  }

amplify oauth config

const isWebPlatform = !Capacitor.isNativePlatform();
const redirectSignIn = isWebPlatform ? 'http://localhost:8100/loginCallback' : 'com.myapp.app://myapp.com/loginCallback';
const redirectSignOut = isWebPlatform ? 'http://localhost:8100' : 'com.myapp.app://myapp.com/logout';

const amplifyConfig = {
 amplify: {
    oauth: {
      domain: '=================.auth.us-east-1.amazoncognito.com',
      scope: ['phone', 'email', 'profile', 'openid', 'aws.cognito.signin.user.admin'],
      redirectSignIn,
      redirectSignOut,
      responseType: 'code',
      urlOpener: async (url) => {
        await Browser.open({url, windowName: isWebPlatform ? '_self' : '_blank'});
      }
    },
  }
}

I hope it helps you

I’m a bit late, but it might help others. I was being redirected back to my app after trying to authenticate and when checking the logs it stopped at “set credentials from session”. I’m using capacitor in the project and in my capacitor config file I had the CapacitorHTTP plugin enabled. After disabling the plugin it resolved the issue.

Correct me if I’m wrong, but I think it has to do with CapacitorHTTP sending CORS-free requests and this isn’t accepted by AWS.

In ionic with android until a while ago I used the following approach and it worked very well:

      urlOpener: async (url) => {
        await Browser.open({url});
      }
      App.addListener('appUrlOpen', ({url}) => {
        if (url.includes('/callbackLoginSocial')) {
          try {
            (Auth as any)._handleAuthResponse(url);
          } catch (e) {
          }
        }
      });
    Hub.listen('auth', ({payload: {event, data}}) => {
        if (event === 'signIn') {
         // code
        } else if (event === 'signOut' || event === 'oAuthSignOut') {
         // code
        } else if (event === 'customOAuthState') {
         // code
        }
    });

But after update aws-amplify version from 3.0.13 to 3.0.24 this no longer works. I was checking and the problem is in Auth.ts the next code is the problem https://github.com/aws-amplify/amplify-js/blob/85cb5ca42dc2bc7171f4d535ba2fa2ee270a955f/packages/auth/src/Auth.ts#L1940

if (window && typeof window.history !== 'undefined') {
  window.history.replaceState(
    {},
    null,
    (this._config.oauth as AwsCognitoOAuthOpts).redirectSignIn
  );
}

Because in the new updates, this piece of code was moved to before

dispatchAuthEvent(
  'signIn',
  currentUser,
  `A user ${currentUser.getUsername()} has been signed in`
);
etc..

So I had to go back to the previous version 😕

I think this pull request broke this functionality https://github.com/aws-amplify/amplify-js/pull/5937

Any update on this?

With AWS Amplify v6 and capacitor, Amplify Auth works for me. I’ll share my implementation in case it is helpful to some.

  • Set up universal links as documented here: https://capacitorjs.com/docs/guides/deep-links
  • On the js file where you have Amplify Auth, import @capacitor/app and add a listener for appUrlOpen.
  • In your listener, if your are on Android call: window.location.replace(data.url)
  • In your listener, if you are on iOS, call: window.location.replace(data.url.replace('https', 'capacitor'))
  • In my situation, I code on windows, and pull latest via github onto my mac. So I need to run npx cap sync ios anytime dependencies in package.json change, otherwise imports won’t work. Also, the domain for applinks on iOS seems to get cached, so set ?mode=developer if you are making updates to the site association file so it reads the latest.

I got this working with ionic angular on iOS. 4 basic tenets of this implementation:

  1. Setup cognito with Callback URL(s) matching your deeplinks and entries in aws-exports.js.
  2. Define (or generate via amplify-cli) aws-exports.js. Key here is redirectSignIn url should launch app via deep linking (tenet# 3)
  3. Implement deeplinking to handle the incoming queryString. Key here is to reload the app using window.location.href passing the same queryString for amplify to do its magic.
  4. Update config.xml to set iosScheme (Scheme for android) to match your deeplink scheme and allow-navigation to the new scheme. This point is not explicitly mentioned in any documentation I referred to POC this.

Links I referred with slight changes to implementation as needed: https://medium.com/@tobinc/create-a-multi-platform-app-using-ionic-4-aws-amplify-in-app-oauth-with-social-idp-deep-linking-6b8de9bc6878 https://medium.com/@zippicoder/setup-aws-cognito-user-pool-with-an-azure-ad-identity-provider-to-perform-single-sign-on-sso-7ff5aa36fc2a https://arjunsk.medium.com/cognito-hosted-ui-with-amplify-in-angular-7-26c9285675c4 And bunch of open issues on this topic on github. Thanks for all contributors.

Key code snippets:

    // Used for listening to login events
    Hub.listen("auth", ({ payload: { event, data } }) => {
      console.log("Hub.listen event", event, data, JSON.stringify(data));
      if (event === "cognitoHostedUI" || event === "signedIn") {
        this.zone.run(() => this.router.navigate(['/home']));
      }
    });

    //currentAuthenticatedUser: when user comes to login page again
    Auth.currentAuthenticatedUser()
      .then(() => {
        console.log("Auth.currentAuthenticatedUser");
        this.router.navigate(['/home'], { replaceUrl: true });
      }).catch((err) => {
        console.log(err);
      })

  private setupDeepLinks(){
    if (this.platform.is('cordova')) {
      this.deeplinks.route({
        '/login':'/login'
      }).subscribe(match => {
        console.log('Success: ', match.$route, JSON.stringify(match.$link), JSON.stringify(match.$args));
                
        const internalPath = `${match.$route}?${match.$link.queryString}`;
        console.log("internalPath", internalPath);
        window.location.href = internalPath;
      },noMatch => {
        console.error('Error: ', noMatch);
      });
    }
  }

config.xml

    <allow-navigation href="iacdapp://*" />
    <preference name="iosScheme" value="iacdapp" />

aws-exports.js

        "redirectSignIn": "iacdapp://localhost/login",
        "redirectSignOut": "iacdapp://localhost/login",

@@giacomocarrozzo I finally got it working!!

So the reason the authentication is not registered is because the Ionic webview is not loaded with the state and code from the redirect. These need to be passed to Ionic in order for Amplify to run its magic.

In order to do so you need to add redirect URL’s top the app and then intercept these deep links. In my case im using capacitor, which handles the deep links so I can grab the data:

   App.addListener('appUrlOpen', (data: any) => {
      console.log('App opened with URL: ' + data.url);
      this.deeplinks.consumeDynamicLink(data.url);
    });

From this point I now have the redirect query params passed back by Facebook and google. Now I need to load the application into the webview again but this time with the query params appended to it:

document.location.href = capacitor://localhost/authevent/?code=${queryParams.code}&state=${queryParams.state};

The next step is to ensure you have the amplify hub set up, as for some reason the auth state observable doesn’t notice the event even after this.

   Hub.listen("auth", ({ payload: { event, data } }) => {
      if (event === 'signIn') {
        this.auth.setAuthstate(true);
        this.userService.listenToSocialAuth(data.username);
      }
    });

At this point I have the facebook auth up and running.

@giacomocarrozzo so I also had that. If you run it incognito mode, you wont get that issue.

It seems cognito saves session data about the federated user, so that if you are removing and adding users in development it will throw this error.

Try running in incognito the error should disappear.

Please post your solution if you figure it out @oliverandersencox . This is brick-walling my deployment, as well as everyone else’ I’m sure.