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

Authenticator component works in expo development builds but not in production #13587

Open Catazza opened 1 month ago

Catazza commented 1 month ago

Before creating a new issue, please confirm:

On which framework/platform are you having an issue?

React Native

Which UI component?

Authenticator

How is your app built?

Expo Application Services

What browsers are you seeing the problem on?

iOS (React Native), Android (React Native)

Which region are you seeing the problem in?

No response

Please describe your bug.

The authenticator component work fine in iOS and Android simulators. It also works fine on real devices with expo development builds. However, it does not work when moving to production builds. This happens for both signin of already existing users, and sign up of new users:

login signup

The weird thing is that Cognito in the backed does receive the API calls. The user for example gets created, with uncorfirmed passwords.

cognito

I looked at AWS Cloudtrail events, and the API calls between the development builds and the production builds look identical.

What's the expected behaviour?

The component works identically between development and production builds.

Help us reproduce the bug!

Here is how we use the authenticator component in out _layout.tsx file:

import { Stack } from "expo-router";
import { Amplify } from "aws-amplify";
import outputs from "@/amplify_outputs.json";
import { RecoilRoot } from "recoil";
import TrackPlayer from 'react-native-track-player';
import { useEffect } from "react";
import { PlayerProvider } from '@/hooks/usePlayerProvider';
import '@/i18n';
import { useFonts } from 'expo-font';
import { Authenticator, ThemeProvider } from "@aws-amplify/ui-react-native";
import 'react-native-polyfill-globals/auto';

TrackPlayer.registerPlaybackService(() => require('@/helpers/service'));
Amplify.configure(outputs);

export default function RootLayout() {

  const [loaded, error] = useFonts({
    'InstrumentSans': require('@/assets/fonts/InstrumentSans/InstrumentSans-Regular.ttf'),
  });

  return (
    <ThemeProvider
      theme={{
        tokens: {
          colors: {
            primary: {
              '10': '#bf5e40',
              '80': '#bf5e40',
              '90': '#bf5e40',
              '100': '#bf5e40',
            },
            background: {
              primary: '#f9f6f2'
            }
          },
        }
      }}
    >
      <Authenticator.Provider>
        <RecoilRoot>
          <PlayerProvider>
            <Stack screenOptions={{ headerShown: false }}>
              <Stack.Screen name="index" />
              <Stack.Screen name="(intro)" />
              <Stack.Screen name="(login)" />
              <Stack.Screen name="(talk)" />
              <Stack.Screen name="(camera)" />
              <Stack.Screen name="(profile)" />
            </Stack>
          </PlayerProvider>
        </RecoilRoot>
      </Authenticator.Provider>
    </ThemeProvider>
  );
}

Here is the package.json:

{
  "name": "myapp",
  "main": "expo-router/entry",
  "version": "1.0.0",
  "scripts": {
    "start": "expo start",
    "reset-project": "node ./scripts/reset-project.js",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web",
    "test": "jest --watchAll",
    "lint": "expo lint"
  },
  "jest": {
    "preset": "jest-expo"
  },
  "dependencies": {
    "@aws-amplify/react-native": "^1.1.1",
    "@aws-amplify/storage": "^6.4.6",
    "@aws-amplify/ui-react": "^6.1.12",
    "@aws-amplify/ui-react-native": "^2.2.2",
    "@expo/vector-icons": "^14.0.0",
    "@react-native-async-storage/async-storage": "1.23.1",
    "@react-native-community/netinfo": "^11.3.2",
    "@react-navigation/native": "^6.0.2",
    "aws-amplify": "^6.3.6",
    "axios": "^1.7.2",
    "base-64": "^1.0.0",
    "expo": "~51.0.10",
    "expo-av": "^14.0.5",
    "expo-camera": "~15.0.11",
    "expo-constants": "~16.0.2",
    "expo-dev-client": "~4.0.18",
    "expo-font": "~12.0.7",
    "expo-linking": "~6.3.1",
    "expo-localization": "~15.0.3",
    "expo-media-library": "^16.0.3",
    "expo-router": "~3.5.15",
    "expo-splash-screen": "~0.27.4",
    "expo-status-bar": "~1.12.1",
    "expo-system-ui": "~3.0.5",
    "expo-web-browser": "~13.0.3",
    "i18next": "^23.11.5",
    "nativewind": "^2.0.11",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-i18next": "^14.1.2",
    "react-native": "0.74.1",
    "react-native-fetch-api": "^3.0.0",
    "react-native-gesture-handler": "~2.16.1",
    "react-native-get-random-values": "^1.11.0",
    "react-native-polyfill-globals": "^3.1.0",
    "react-native-reanimated": "~3.10.1",
    "react-native-safe-area-context": "^4.10.1",
    "react-native-screens": "3.31.1",
    "react-native-track-player": "^4.1.1",
    "react-native-url-polyfill": "^1.3.0",
    "react-native-uuid": "^2.0.2",
    "react-native-web": "~0.19.10",
    "recoil": "^0.7.7",
    "shaka-player": "^4.9.5",
    "text-encoding": "^0.7.0",
    "web-streams-polyfill": "^3.2.1",
    "expo-linear-gradient": "~13.0.2",
    "react-native-svg": "15.2.0"
  },
  "devDependencies": {
    "@aws-amplify/backend": "^1.0.3",
    "@aws-amplify/backend-cli": "^1.0.4",
    "@babel/core": "^7.20.0",
    "@types/jest": "^29.5.12",
    "@types/react": "~18.2.45",
    "@types/react-test-renderer": "^18.0.7",
    "aws-cdk": "^2.146.0",
    "aws-cdk-lib": "^2.146.0",
    "constructs": "^10.3.0",
    "esbuild": "^0.21.5",
    "jest": "^29.2.1",
    "jest-expo": "~51.0.1",
    "react-test-renderer": "18.2.0",
    "tailwindcss": "3.3.2",
    "tsx": "^4.15.5",
    "typescript": "~5.3.3"
  },
  "optionalDependencies": {
    "@parcel/watcher-linux-x64-glibc": "2.4.1"
  },
  "private": true
}

Here is the screen with the actual login:

import React, { useEffect, useState } from "react";
import { Button, View, StyleSheet, SafeAreaView } from "react-native";

import { Authenticator, useAuthenticator } from "@aws-amplify/ui-react-native";
import { Hub } from 'aws-amplify/utils';
import { I18n } from 'aws-amplify/utils';
import { translations } from '@aws-amplify/ui';
import { getLanguage } from "@/helpers/locales";
import { router } from "expo-router";

Hub.listen('auth', ({ payload }) => {
  switch (payload.event) {
    case 'signedIn':
      console.log('user have been signedIn successfully.');
      router.push('/mode')
      break;
    case 'signedOut':
      console.log('user have been signedOut successfully.');
      router.push('/intro')
      break;
    case 'tokenRefresh':
      console.log('auth tokens have been refreshed.');
      break;
    case 'tokenRefresh_failure':
      console.log('failure while refreshing auth tokens.');
      break;
    case 'signInWithRedirect':
      console.log('signInWithRedirect API has successfully been resolved.');
      break;
    case 'signInWithRedirect_failure':
      console.log('failure while trying to resolve signInWithRedirect API.');
      break;
    case 'customOAuthState':
      console.log('custom state returned from CognitoHosted UI');
      break;
  }
});

I18n.putVocabularies({
  it: {
    'Enter your Password': 'Inserisci la tua password',
    'Please confirm your Password': 'Conferma la password',
    'Given Name': "Nome",
    'Enter your Given Name': 'Inserisci il tuo nome',
    'Family Name': "Cognome",
    'Enter your Family Name': 'Inserisci il tuo cognome',
  }
});

const Login = () => {

  const [language, setLanguage] = useState('en')
  const initLang = () => getLanguage().then((lang) =>  {
    I18n.putVocabularies(translations)
    I18n.setLanguage(lang)
    setLanguage(lang)
  })
  useEffect(() => {
    initLang()
  }, [language])

  const SignOutButton = () => {
    const { signOut } = useAuthenticator();

    return (
      <View style={styles.signOutButton}>
        <Button title="Sign Out" onPress={signOut} />
      </View>
    );
  };

  return (
        <Authenticator loginMechanisms={['email']} signUpAttributes={[
          "given_name",
          "family_name",
          "email"
        ]}>
          <SafeAreaView>
            <SignOutButton />
          </SafeAreaView>
        </Authenticator>
  );
};

const styles = StyleSheet.create({
  signOutButton: {
    alignSelf: "flex-end",
  },

});

export default Login;

Code Snippet

// Put your code below this line.

Console log output

N/A it's a production build

Additional information and screenshots

No response

timngyn commented 1 month ago

👋 Hi @Catazza, sorry you're having this issue. Just to confirm, when you say "production build", is it the production build from EAS? What command are you using?

Catazza commented 1 month ago

👋 Hi @Catazza, sorry you're having this issue. Just to confirm, when you say "production build", is it the production build from EAS? What command are you using?

Hi @timngyn thanks for replying :) To be precise, it's in fact the preview the build with EAS that is without development client, which we push to App Store Connect or the Play store in order to do user testing. If I install directly the Android build (without passing from the Play Store) I still face the same issue.

I used the preview profile in this eas.json:

{
  "cli": {
    "version": ">= 10.0.0"
  },
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal"
    },
    "preview": {
      "distribution": "internal"
    },
    "submit": {
      "production": {}
    }
  }
}
reesscot commented 1 month ago

@Catazza We don't directly support builds using EAS cloud services. To better isolate the issue, are you able to do a production build using Expo in your local development environment (outside of EAS)?

Another thing you could try to isolate the issue would be to remove the Authenticator and use the Amplify JS signIn function directly: https://docs.amplify.aws/react-native/build-a-backend/auth/connect-your-frontend/sign-in/

Catazza commented 1 month ago

Hi @reesscot yes I also did try that, also local builds present the same issue. I can share some build logs if you wish? There's loads so I won't paste the whole block here!

And yes thanks for the suggestion, I am already onto trying the SignIn function - also because, I noticed in cloudtrail that the signIn event is responded with the challenge (for the SRP auth flow) and this is where it gets stuck. So I will also try with the "normal" password flow, which I can't seem to be able to configure in the authenticator component (although, signUp also presents the same issue, so in fact I doubt it's that).

timngyn commented 1 month ago

Hi @Catazza, just to clarify to make sure I'm understanding correctly, when you say local builds, you're using the preview build from EAS right?

Have you also tried using this command npx expo run:ios --configuration Release or npx expo run:android --variant release? Taken from here: https://docs.expo.dev/more/expo-cli/#compiling-android

Catazza commented 1 month ago

Hi @timngyn nice shout on trying the release variants from the CLI. I have done that (I used npx expo run:ios --configuration Release), I still see the same error, but now I can see the logs in the console:

[RemoteTextInput] -[RTIInputSystemClient remoteTextInputSessionWithID:performInputOperation:]  perform input operation
requires a valid sessionID
reesscot commented 1 month ago

@Catazza Can you also share your amplifyconfiguration.json file (with any sensitive pieces redacted)? It

Yes, any build logs you have would be helpful as well.

It also looks like you are overriding the loginMechanism and signUpAttributes. Do you get the same errors if you leave them off and let the configuration file determine your login type based on your Cognito settings?

Catazza commented 1 month ago

Hi @timngyn @reesscot so to provide further details: I have also tried the "bare" signIn function outside of the component, and we still get the same behaviour (i.e. it does work in the dev app version but not in the release one created with npx expo run:ios --configuration Release). I have also enabled the plain USER_PASSWORD_AUTH auth flow, but that also does not work.

I might open an issue in the Amplify-js repo, as it's not just a UI component issue, also the bare amplify method does not work. WDYT?

Here's the auth part of the amplify_outputs.json by the way:

{
  "auth": {
    "user_pool_id": "xxxx",
    "aws_region": "eu-central-1",
    "user_pool_client_id": "xxxx",
    "identity_pool_id": "xxxx",
    "mfa_methods": [],
    "standard_required_attributes": [
      "email"
    ],
    "username_attributes": [
      "email"
    ],
    "user_verification_types": [
      "email"
    ],
    "mfa_configuration": "OFF",
    "password_policy": {
      "min_length": 8,
      "require_numbers": true,
      "require_lowercase": true,
      "require_uppercase": true,
      "require_symbols": true
    },
    "unauthenticated_identities_enabled": true
  }
}
timngyn commented 1 month ago

Hey @Catazza, thanks so much for trying that out. That makes sense to open the issue in Amplify JS. I can transfer the issue over to their repo (so you don't have to open a new ticket)

cwomack commented 1 month ago

@Catazza, thanks for opening this issue and we'll investigate this/attempt to reproduce on our side. In the mean time, are you seeing any errors or logs (either screenshots or text) that can be shared on the release build?

Catazza commented 1 month ago

@Catazza, thanks for opening this issue and we'll investigate this/attempt to reproduce on our side. In the mean time, are you seeing any errors or logs (either screenshots or text) that can be shared on the release build?

Hi @cwomack I can't see anything beyond the screenshot I shared in the issue description. If I use the bare signIn method, I can't see anything at all (no logs no errors). I'll try setting up Sentry to see if I have any better luck there

Catazza commented 1 month ago

@cwomack build logs when I run npx expo run:ios --configuration Release don't say much:

› Planning build
› Executing react-native Pods/hermes-engine » [CP-User] [Hermes] Replace Hermes for the right configuration, if needed
› Executing react-native Pods/React-Fabric » [CP-User] [RN]Check rncore
› Executing expo-constants Pods/EXConstants » [CP-User] Generate app.config for prebuilt Constants.manifest
› Executing myapp » [Expo] Configure project
› Executing myapp » Bundle React Native code and images
    Bundler cache is empty, rebuilding (this may take a minute)
› Executing myapp » [CP] Copy Pods Resources
› Signing   myapp » myapp.app
› Build Succeeded

› 0 error(s), and 1 warning(s)

Starting Metro Bundler
Waiting on http://localhost:8081
› Installing on iPhone 15 Pro Max
› Opening on iPhone 15 Pro Max (com.myappai.myapp)
› Opening exp+myapp://expo-development-client/?url=http%3A%2F%2F192.168.1.42%3A8081 on iPhone 15 Pro Max

› Logs for your project will appear below.

[CoreFoundation] AddInstanceForFactory: No factory registered for id <CFUUID
xxxx> xxxxx

Cloudtrail event:

{
    "eventVersion": "1.08",
    "userIdentity": {
        "type": "Unknown",
        "principalId": "Anonymous"
    },
    "eventTime": "2024-07-12T19:21:42Z",
    "eventSource": "cognito-idp.amazonaws.com",
    "eventName": "InitiateAuth",
    "awsRegion": "eu-central-1",
    "sourceIPAddress": "1234",
    "userAgent": "myapp/1 CFNetwork/1474 Darwin/22.6.0",
    "requestParameters": {
        "authFlow": "USER_PASSWORD_AUTH",
        "authParameters": "HIDDEN_DUE_TO_SECURITY_REASONS",
        "clientId": "xxxx"
    },
    "responseElements": {
        "challengeParameters": "HIDDEN_DUE_TO_SECURITY_REASONS",
        "authenticationResult": {
            "accessToken": "HIDDEN_DUE_TO_SECURITY_REASONS",
            "expiresIn": 3600,
            "tokenType": "Bearer",
            "refreshToken": "HIDDEN_DUE_TO_SECURITY_REASONS",
            "idToken": "HIDDEN_DUE_TO_SECURITY_REASONS"
        }
    },
    "additionalEventData": {
        "sub": "xxxx"
    },
    "requestID": "e84e5394-62f0-47ab-88ee-ef2e4c548e0b",
    "eventID": "7360e3c0-ec1e-4365-9589-b63994091ce2",
    "readOnly": false,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "12345678",
    "eventCategory": "Management",
    "tlsDetails": {
        "tlsVersion": "TLSv1.3",
        "cipherSuite": "TLS_AES_128_GCM_SHA256",
        "clientProvidedHostHeader": "cognito-idp.eu-central-1.amazonaws.com"
    }
}

I am now going to investigate perhaps permission issues

HuiSF commented 1 month ago

Hi @Catazza Thank you for provide detailed information helping with the triaging process.

I created a minimum Expo app that invokes the Amplify signIn() API without the Authenticator to isolate the issue. I created a production build use the Expo EAS build CLI and installed the app to an iPhone device. From testing I don't see any issues calling signIn(), where I can sign in an end user, and subsequently get the user session via calling fetchAuthSession.

From your screenshots in the original post, I saw "Network error" so I suspect the network request generated by signIn or signUp call didn't go through. Could you double check whether your native app has any network related configuration that's build scheme specific?

In your last comment though, the Cloudtrail log indicates that there was a successful sign in and tokens were returned to the client. Can you confirm whether this event was triggered by clicking the sign in button in your app?