Closed iambryanwho closed 2 years ago
Hi @iambryanwho :wave:
Are you using Amplify here?
If so and you are implementing it in '../contexts/ReliefAuth'
or ./AuthStack
, that would be helpful to see.
Also if you could run the command to get the environment info in the root of your project or provide your package.json that would be helpful as well.
Sure no problem @tannerabread . Here is RelifAuth.tsx
as seen below (I removed some calls not being used in this example)
type AuthContextData = {
authData?: CognitoUser;
authSession?: CognitoUserSession;
loading: boolean;
signUp(registerState: RegisterUser): Promise<void>;
resendConfirmationCode(): Promise<void>;
confirmSignIn(code: string): Promise<void>;
signIn(phoneNumber: string): Promise<void>;
signOut(): void;
globalSignOut(): void;
};
Auth.configure({
authenticationFlowType: 'CUSTOM_AUTH'
});
//Create the Auth Context with the data type specified
//and a empty object
const AuthContext = createContext<AuthContextData>({} as AuthContextData);
const AuthProvider: React.FC<ReliefProps> = ({children}) => {
const [authData, setAuthData] = useState<CognitoUser>();
const [authSession, setAuthSession] = useState<CognitoUserSession>();
//the AuthContext start with loading equals true
//and stay like this, until the data be load from Async Storage
const [loading, setLoading] = useState(true);
useEffect(() => {
//Every time the App is opened, this provider is rendered
//and call de loadStorage function.
loadStorageData();
//attach Amplify listener for authentication events
Hub.listen('auth', listener);
console.log("hub attached");
return () => {
Hub.remove('auth', listener);
console.log("hub removed");
}
}, []);
async function loadStorageData(): Promise<void> {
setLoading(true);
console.log("LOADING STARTED");
try {
//Try get the data from Async Storage
const authDataSerialized = await AsyncStorage.getItem('@AuthData');
const authSessionSerialized = await AsyncStorage.getItem('@AuthSession');
if (authDataSerialized) {
//If there are data, it's converted to an Object and the state is updated.
const _authData: CognitoUser = await JSON.parse(authDataSerialized);
setAuthData(_authData);
}
if (authSessionSerialized) {
//If there are data, it's converted to an Object and the state is updated.
const _authSession: CognitoUserSession = await JSON.parse(authSessionSerialized);
setAuthSession(_authSession);
}
} catch (error) {
} finally {
//loading finished
console.log("LOADING FINISHED");
setLoading(false);
}
}
const confirmSignIn = async (code: string) => {
if(!authData) {
console.log("confirmSignIn: authData empty"); //TODO throw error
return;
}
try {
authData.sendCustomChallengeAnswer(code, {
async onSuccess(session) {
session.isValid()
setAuthSession(session);
await AsyncStorage.setItem('@AuthSession', JSON.stringify(session));
console.log("confirmed user signIn");
},
onFailure(err) {
console.log('error confirming sign up', err); //TODO Handle
},
})
} catch (error) {
console.log('error confirming sign up', error);
}
}
const signIn = async (phoneNumber: string) => {
try {
const user: CognitoUser = await Auth.signIn(phoneNumber);
setAuthData(user);
await AsyncStorage.setItem('@AuthData', JSON.stringify(user));
console.log("signed in user");
} catch (error) {
console.log('error signing in', error);
}
};
return (
//This component will be used to encapsulate the whole App,
//so all components will have access to the Context
<AuthContext.Provider value={{authData, authSession,loading, signUp, resendConfirmationCode, confirmSignIn ,signIn, signOut, globalSignOut}}>
{children}
</AuthContext.Provider>
);
};
//A simple hooks to facilitate the access to the AuthContext
// and permit components to subscribe to AuthContext updates
function useAuth(): AuthContextData {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}
export {AuthContext, AuthProvider, useAuth};
and here are the Environment Variables:
OS: macOS 12.6
CPU: (8) x64 Apple M2
Memory: 18.18 MB / 16.00 GB
Shell: 5.8.1 - /bin/zsh
Binaries:
Node: 17.9.1 - ~/.nvm/versions/node/v17.9.1/bin/node
npm: 8.11.0 - ~/.nvm/versions/node/v17.9.1/bin/npm
Watchman: 2022.10.24.00 - /opt/homebrew/bin/watchman
Browsers:
Brave Browser: 106.1.44.101
Chrome: 107.0.5304.87
Firefox: 105.0.3
Safari: 15.6.1
npmPackages:
@babel/core: ^7.12.9 => 7.19.6
@babel/runtime: ^7.12.5 => 7.20.0
@react-native-async-storage/async-storage: ^1.17.10 => 1.17.10
@react-native-community/art: ^1.2.0 => 1.2.0
@react-native-community/eslint-config: ^2.0.0 => 2.0.0
@react-native-community/netinfo: ^9.3.3 => 9.3.6
@react-native-picker/picker: ^2.4.6 => 2.4.8
@react-navigation/material-bottom-tabs: ^6.2.4 => 6.2.4
@react-navigation/native: ^6.0.13 => 6.0.13
@react-navigation/native-stack: ^6.9.0 => 6.9.1
@testing-library/react-native: ^11.2.0 => 11.3.0
@tsconfig/react-native: ^2.0.2 => 2.0.2
@types/jest: ^26.0.23 => 26.0.24
@types/react-native: ^0.70.0 => 0.70.6
@types/react-test-renderer: ^18.0.0 => 18.0.0
@typescript-eslint/eslint-plugin: ^5.37.0 => 5.41.0 (3.10.1)
@typescript-eslint/parser: ^5.37.0 => 5.41.0 (3.10.1)
HelloWorld: 0.0.1
amazon-cognito-identity-js: ^5.2.10 => 5.2.12
aws-amplify: ^4.3.37 => 4.3.42
aws-amplify-react-native: ^6.0.5 => 6.0.8
babel-jest: ^26.6.3 => 26.6.3 (29.2.2)
eslint: ^7.32.0 => 7.32.0
example: 0.0.1
hermes-inspector-msggen: 1.0.0
jest: ^29.2.0 => 29.2.2
metro-react-native-babel-preset: ^0.72.1 => 0.72.3 (0.72.1)
react: 18.1.0 => 18.1.0
react-native: 0.70.1 => 0.70.1
react-native-paper: ^4.12.5 => 4.12.5
react-native-pie-chart: ^2.0.2 => 2.0.2
react-native-safe-area-context: ^4.4.1 => 4.4.1
react-native-screens: ^3.17.0 => 3.18.2
react-native-vector-icons: ^9.2.0 => 9.2.0
react-test-renderer: 18.1.0 => 18.1.0
typescript: ^4.8.3 => 4.8.4
npmGlobalPackages:
corepack: 0.10.0
npm: 8.11.0
It may or may not make a difference but you shouldn't need aws-amplify-react-native
in your package.json to use amplify with React Native.
Outside of that, am I interpreting this correctly and you are rebuilding the entire Auth flow yourself?
From what I see:
Auth.configure
call only has authenticationFlowType: 'CUSTOM_AUTH'
in it, are you configuring the rest of your resources elsewhere?
Are you storing the user data manually in your AsyncStorage? I can't see your method to store the data I see how you are retrieving it
We're using a custom auth flow that creates accounts using a phoneNumber. The remaining configurations are in my App.tsx
which pulls configurations from my auto-generated aws-exports.js
file:
App.tsx
import {
Amplify,
Auth
} from 'aws-amplify'
import awsconfig from './src/aws-exports'
Amplify.configure({
Auth: {
region: awsconfig.aws_appsync_region,
userPoolId: awsconfig.aws_user_pools_id,
userPoolWebClientId: awsconfig.aws_user_pools_web_client_id,
}
});
User data is being stored in ReliefAuth.tsx
usning AsyncStorage:
ReliefAuth.tsx
import AsyncStorage from '@react-native-async-storage/async-storage';
...
const AuthProvider: React.FC<ReliefProps> = ({children}) => {
//storing in state
const [authData, setAuthData] = useState<CognitoUser>();
const [authSession, setAuthSession] = useState<CognitoUserSession>();
...
const signUp = async (registerState: RegisterUser) => {
const phone_number = parseInt(registerState.phoneNumber.substring(1)) as number;
const credentials: SignUpCredentials = {
username: registerState.phoneNumber,
password: "TODO use uuid4() to generate password",
autoSignIn: {
enabled: false,
},
}
try {
const result: ISignUpResult = await Auth.signUp(credentials);
const user = result.user;
//SAVING to state and to storage
setAuthData(user);
await AsyncStorage.setItem('@AuthData', JSON.stringify(user));
} catch (error) {
console.log('error signing up:', error);
}
}
...
You can see the use of AsynStorage
in the code snippets earlier in the signIn()
and confirmSignIn()
More than happy to do a walkthrough/screenshare as well @tannerabread
@iambryanwho sorry for the delayed response.
Before we go through a walkthrough/screenshare, I wanted to make a few observations and ask a few questions.
It is generally recommended to configure the entire app in the same location and at the root of the project. There's a chance of having multiple instances of your Auth object when configuring in multiple places. For a React Native built from the CLI (not-Expo), normally Amplify is configured in index.js
. When aws-exports
is present from using the Amplify CLI, the app can be configured as follows:
import { Amplify } from 'aws-amplify';
import awsconfig from './src/aws-exports';
Amplify.configure({
...awsconfig,
authenticationFlowType: 'CUSTOM_AUTH'
})
Another question about the config and the custom auth flow, is the custom auth flow only to create accounts with phone numbers? That is possible OOTB and the custom auth would only be needed to administer custom challenges. I see a call to sendCustomChallengeAnswer
in the confirmSignIn
method but it is through the CognitoUser object instead of through Amplify. If it is being used elsewhere, that's fine but I would recommend use the Auth.sendCustomChallengeAnswer(user challengeResponse)
flow instead of calling the Cognito method directly.
I see the use of AsyncStorage in the app, and am wondering why this is being done manually instead of letting Amplify handle the storage/retrieval of the user/session? The flow described in the docs would do that step automatically. I don't see anything very unusual with the signIn
method other than storing the session manually. It's possible when this is done, there are two versions of the session and user in AsyncStorage (one set by you and one set by Amplify), of which 1 might be undefined if something went wrong.
What is the reason for using the Context/Provider flow to encapsulate the whole app and passing methods from Amplify to it? Instead, it would normally be recommended to just sign in with Amplify's usual flow and then call methods from Auth
when needed. If this were done, Auth.currentAuthenticatedUser
could be used on the <Router>
component to check if the user is signed in and then the content could be rendered conditionally from there.
Thanks @tannerabread . Yes, Im currently using the custom auth flow to create accounts just with phone numbers. But the idea was to use custom to have more future flexibility for changing settings such as length of the custom code sent, etc. If the OOTB solution allows equal flexibility as custom then I can switch to OOTB. Is that the case?
Also does Amplify handle storage/retrieval even in a custom auth flow?
@iambryanwho
If the OOTB solution allows equal flexibility
Sorry, that is not the case. I was mistaken and it is not possible to do passwordless authentication OOTB. You would need to keep going the custom_auth route for that but I would still recommend using the methods from the Auth
package instead of Cognito directly.
The storage/retrieval part should be automatic though whether you have a normal or custom auth flow. From my understanding if you are calling Amplify Auth
methods, it will store/retrieve them without any extra setup.
That all being said, I'm still curious if either that or the configuration being in multiple places was the cause of your issue. Were you able to change the config to the root of the project?
@tannerabread using Auth
works. Thanks
Closing this as resolved. If you experience any other issues with Amplify please feel free to open a new issue so that we can assist you. Also feel free to join our discord server to speak with the community of Amplify developers.
Thank you!
Before opening, please confirm:
JavaScript Framework
React Native
Amplify APIs
Authentication
Amplify Categories
auth, api
Environment information
Describe the bug
isValid()
fromCognitoUserSession
produces inconsistent results as it is sometimes undefined.Error produced:
TypeError: undefined is not a function
Expected behavior
CognitoUserSession.isValid()
should producetrue
until user session is expiredReproduction steps
CognitoUserSession
CognitoUserSession.isValid()
This should workCognitoUserSession
is not undefined It shouldn't beCognitoUserSession.isValid()
This produces an errorCode Snippet
Log output
aws-exports.js
No response
Manual configuration
No response
Additional configuration
No response
Mobile Device
Android Emulator: Pixel 6
Mobile Operating System
API 31
Mobile Browser
No response
Mobile Browser Version
No response
Additional information and screenshots