I have an action that logs in the user via email and password:
export class LoginWithCredentials {
static readonly type = '[Auth] Login With Credentials';
}
I dispatch this action whenever the login form has been submitted:
onSubmitLogin() {
this.store.dispatch(LoginWithCredentials);
}
The action handler gets from the state the email and password and call firebase.auth().signInWithEmailAndPassword, like so:
#Action(LoginWithCredentials)
loginWithCredentials({getState}: StateContext<AuthStateModel>) {
const {email, password} = getState().forms.login.model;
this.afAuth.auth.signInWithEmailAndPassword(email, password)
.catch(err => console.log(err)); // Should console.log on error
console.log('Should print something');
}
For some reason, catch is being ignored, and the console.log that should print something, is ignored too.
I tried to run this method outside of the state, and it seemed to work. Although, I want to put this logic into my action.
PS:
if I use signInWithPopup(provider) instead of signInWithEmailAndPassword, then it will work (But it's not what I need).
I think this is because you call an asynchronous function and as you are not waiting for the result then NGXS "consider the action over" (I do not know how it works behind the scene..).
If signInWithEmailAndPassword returns a promise try this :
#Action(LoginWithCredentials)
async loginWithCredentials({getState}: StateContext<AuthStateModel>) {
const {email, password} = getState().forms.login.model;
await this.afAuth.auth.signInWithEmailAndPassword(email, password)
.catch(err => console.log(err)); // Should console.log on error
console.log('Should print something');
}
Anyway as it is asynchronous you need to wait the answer (success or failure) to update your state. Something like this:
#Action(LoginWithCredentials)
async loginWithCredentials({getState, patchState}: StateContext<AuthStateModel>) {
const {email, password} = getState().forms.login.model;
const loginAnswer = await yourLoginService.login(email, password);
if( loginAnswer === 'success' ) {
patchState( { loggedIn: true };
}
}
Related
I am trying to export a variable to other file, but what I get in other file is the pre-defined value. How can I get the changed value? Here is my code.
AuthUser.js
let phoneNumber = null;
let username = null;
export async function getLoggedinUser() {
await Auth.currentAuthenticatedUser() //function provided by AWS to get logged in user data
.then((data) => {
phoneNumber = data.attributes.phone_number;
username = data.attributes.preferred_username;
console.log('currentAuthenticatedUser() success' + phoneNumber);//output sucess with user phone number
})
.catch((err) => {
phoneNumber = false;
username = false;
console.log('currentAuthenticatedUser():' + err);
});
}
export default {
phoneNumber,
username,
};
LoginPage.js
import { getLoggedinUser } from '../../config/AuthUser';
import AuthUser from '../../config/AuthUser';
...
async function AuthUserSession() {
try {
await getLoggedinUser();
navigation.navigate('Home');
console.log('AuthUserSession:' + AuthUser.phoneNumber);//output null(undefined)
} catch (e) {}
}
...
What I am trying to achieve is to save logged in user data right after user log in. Every page need the data and I do not want to access to AWS Cognito everytime I open a new page. Actually I am not sure will that cost me. May someone familiar with AWS can give me some suggestion on this? Are there any other better solution to handle this problem?
Many thanks!
You can change getLoggedinUser return value to a function, let'say getUserInfo(). after await, call this function to get your information.
You should defind this function in AuthUser.js and return it when data is ready.
After a form submission using Redux, I am able to see the plain text password in the dev tools meta section. Is this safe? Am I doing something wrong when passing the password down to the reducer? How can I make this more secure?
So in my userSlice I am creating an Async Thunk that accepts user input then grabs the user from my server/database.
export const setUserAsync = createAsyncThunk(
'user/setUserAsync',
async (payload, { rejectWithValue }) => {
try {
const response = await axios.post('/auth/login', payload);
const { token, user } = response.data;
console.log(response);
localStorage.setItem('user', JSON.stringify(user));
localStorage.setItem('token', token);
return user;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
which works as intended. I am then calling the fulfilled reducer to set the user state.
[setUserAsync.fulfilled]: (state, action) => {
state.user = action.payload;
state.isLoggedIn = !!action.payload;
}
but in my dev tools I am seeing the following which is plain text of the password I input, in this case it is wrong, but when it's right it shows it just the same.
I don't think you need to be concerned. The production bundle of your app won't have the redux devtools enabled so the password can't linger there. And if you're using proper TLS (see https://security.stackexchange.com/questions/110415/is-it-ok-to-send-plain-text-password-over-https ), the password remains encrypted.
I'm using React Native/Firebase/Redux to build a simple login system. I am trying to work out how to capture errors that happen as a result of failed login attempts.
Here's my authscreen.js:
const [alertShowing, setAlertShowing] = useState(false);
const [alertMessage, setAlertMessage] = useState('');
...
function handleLogin() {
const response = dispatch(login(email, password));
console.log(response);
}
actions.js:
export const login = (email, password) => {
return async (dispatch) => {
try {
const response = await Firebase.auth().signInWithEmailAndPassword(email, password);
dispatch(getUser(response.user.uid));
} catch (e) {
return e;
}
};
};
My console.log(response) above correctly shows me the error message, but this obviously isn't very useful to users. And please note too that I can log in properly when using correct credentials.
What I really want to do in my handleLogin() is check if the response is an error, and if so, setlAlertShowing(true) and setAlertMessage to what I've gotten back from the useDispatch hook so that I may display it nicely to the user.
How should I go about this? TIA.
Firebase errors messages are designed for developers and not standard users friendly. The solution is to identify authentication error code and map with user-friendly messages.
list of error code https://firebase.google.com/docs/auth/admin/errors
You can use function like this to map authError to meaningful error messages.
function mapAuthCodeToMessage(authCode) {
switch (authCode) {
case "auth/invalid-password":
return "Password provided is not corrected";
case "auth/invalid-email":
return "Email provided is invalid";
// Many more authCode mapping here...
default:
return "";
}
}
and use it later
export const login = (email, password) => {
return async (dispatch) => {
try {
const response = await Firebase.auth().signInWithEmailAndPassword(email, password);
dispatch(getUser(response.user.uid));
} catch (e) {
dispatch({type:"authError",message:mapAuthCodeToMessage(e.code)}));
}
};
};
I am working on a full stack application and I am having a bit of trouble implementing a signin action. The signin process works, but the response I am getting includes too much information. In redux the state shows the data, but then it also updates the state to include the headers and the config information (which contains the username and password of the user). Since I am saving the same data in localStorage the local storage also contains this sensitive information. Obviously this is a problem and a huge security risk so I would like to update the response to only include the data from the API. My API schema returns the following information:
res.status(200).send({
id: userInfo.id,
name: userInfo.name,
email: userInfo.email,
role: userInfo.role,
message: `Welcome Back ${userInfo.name}`,
token
})
My signin action code is as follows:
export const signin = (email, password) => async (dispatch) => {
dispatch({type: user.USER_SIGNIN_REQUEST, payload: {email, password}})
try {
const data = await axios.post("/api/users/login", {email, password})
dispatch({type: user.USER_SIGNIN_SUCCESS, payload: data})
localStorage.setItem("userInfo", JSON.stringify(data))
} catch(err) {
dispatch({
type: user.USER_SIGNIN_ERROR,
payload: err.response ?? err.response.data.message
})
}
}
my reducer code is as follows:
export const userSigninReducer = (state = {}, action) => {
switch(action.type) {
case user.USER_SIGNIN_REQUEST:
return {loading: true}
case user.USER_SIGNIN_SUCCESS:
return {loading: false, userInfo: action.payload}
case user.USER_SIGNIN_ERROR:
return {loading: false, err: action.payload}
case user.USER_SIGNOUT:
return {}
default:
return state
}
}
from the looks of this code it would appear that the reducer would set userInfo to the object that is returned from the API. This does happen but it ALSO returns the headers and the config object. I have tried to update my signin action from const data = await axios.post("/api/users/login", {email, password}) to const {data} = await axios.post("/api/users/login", {email, password}) in an attempt to deconstruct the data object from the response, however this does nothing which leads me to believe that the headers and config are being applied to state by something else. I am not sure what else I can do to troubleshoot this issue.
PS: Here is a photo of what is shown in the userInfo response in redux and localstorage. You can see the full size image by right clicking and opening in a new tab.
Okay so I took a quick walk and when I got back I just restarted my server and tried to log in again. It appears that changing the response object from my signin action from const data = await axios.post("/api/users/login", {email, password}) to const {data} = await axios.post("/api/users/login", {email, password}) did in fact fix the issue. If someone is having a similar problem I would try to deconstruct the object you want from the response.
The Context:
I want to know how to get rid of this error:
Warning: Can't perform a React state update on an unmounted component.
I think I know exactly why this error shows up:
I have a Signin Route. I have a litte piece of code in the useEffect that does this:
if (!auth.isEmpty && auth.isLoaded) {
history.push("/");
}
So when someone goes to mypage/signin and is already signed in, he gets redirected to Homepage. This works fine BUT:
The Problem:
When he is not signed in I have a little Signin Function:
const signin = async (e: React.MouseEvent) => {
setIsLoading(true);
e.preventDefault();
try {
const user = await firebase.login({ email, password });
setIsLoading(false);
if (user) {
history.push("/");
}
} catch (error) {
setIsLoading(false);
setError(error.message);
}
};
So when the users hits enter, he gets redirected to home when there is no error. It works fine but I get this error in the console, because I set the state and the the snippet in useEffect routes me to /Home, but the promise is not yet completed from firebase. And when it's finished it tries to set state, but component already unmounted.
What have I tried
I added a isMounted hook and changed my signin function to look like this:
const signin = async (e: React.MouseEvent) => {
e.preventDefault();
if (isMounted) {
setIsLoading(true);
try {
const user = await firebase.login({ email, password });
setIsLoading(false);
if (user) {
history.push("/");
}
} catch (error) {
setIsLoading(false);
setError(error.message);
}
}
};
But still the same error on route change.
Additional Info
Don't get confused about these 2 loading states auth.isLoaded (from react-redux-firebase) and isLoading (my own state). Basically why I did it this way is, because when someone is already logged in and then goes to /signin he sees the login form for a tiny moment, because firebase doesn't know yet if user is authenticated, so I handled it like this, so the user definetily sees a spinner and then gets redirected if already logged in.
How to solve this little problem?
You can use React hooks for this. The useEffect return method is called when compoment is unmonuted from screen. This is like compomentdidunmount in class based react.
declare global variable _isMounted to false. When useEffect is called, it changes to true and components are on screen.
If component are unmounted, then return method from useEffect is called and _isMounted is set to false;
while updating the state, you can check using _isMounted variable that is component is mounted or not.
var _isMounted = false;
const fetchuser = () => {
if(_isMounted)
{
// code
}
}
useEffect(() => {
_isMounted = true;
// your code;
return()
{
_isMounted = false;
console.log("Component Unmounted");
}
},[])
if you redirected after login, you dont have to change loading state. Just remove setIsLoading(false)
const user = await firebase.login({ email, password });
if (user) {
history.push("/");
}