React, connect-mongoose & Express session/user login - reactjs

I'm trying to get sessions to work with a React front-end and an express + connect-mongo using MongoStore back-end.
Handle Register Function
async function handleRegister(evt){
//Prevent default form redirect.
evt.preventDefault();
//Create a new user objec to pass into axios
const user = {
username: username,
password: password
}
//Send axios post request to nodeJS API.
await axios.post("http://localhost:5000/users/register",user)
.then((res) => {
history.push({
pathname: '/',
state: res.data
});
})
.catch((err) => {
console.log(err);
});
//Push react history back to index page.
}
Handle Login function
const handleLogin = async (evt) => {
//Prevent default form submission
evt.preventDefault();
const loginDetails = {
username: username,
password: password,
}
//send login request to api
await axios.post('http://localhost:5000/users/login', loginDetails)
.then((res) => {
})
.catch((err) => {
})
}
I'm stuck on trying to figure out how to make the data be sent back to react after either of the above functions. In the register function I've sent back the res.data which contains the session. See blow route for express
router.post('/register', async (req, res) => {
//Destructure req.body.
const {username,password} = req.body;
//hash password.
const hashedPassword = await hashPassword(password);
//Create new user to store in mongodb.
const newUser = {
username: username,
password: hashedPassword
}
//Create new user document
await User.create(newUser, (err, newlyAddedUser) => {
if(err){
console.log(err);
}else{
req.session.username = newlyAddedUser.username;
console.log(req.session);
res.send(req.session);
}
})
});
With the console.log(req.session) it outputs the cookie and the username I added in the session itself.
Should I make a user object on the react side and store the username and password inside?
Should I be passing back the session itself to the route with history.push({ pathname: '/',state: res.data});
How can I verify that the session is valid for the user using connect-mongo?

I spent 10 minutes trying to understand what is your goal. Didn't find.
But whatever you need to use a jsonwebtoken if you want to verify that the session is valid like you said
Enjoy https://jwt.io/
https://www.npmjs.com/package/jsonwebtoken
I wouldn't store the session in the History state API like you do.
history.push({
pathname: '/',
state: res.data
});
You better use a sessionStorage and/or localStorage. The name just talks by itself.
Give me one point please

Related

How to persist data using Apollo?

I have a Apollo client and server with a React app in which users can log in. This is the Apollo server mutation for the login:
loginUser: async (root, args) => {
const theUser = await prisma.user.findUnique({
where: {email: String(args.email)},
});
if (!theUser) throw new Error('Unable to Login');
const isMatch = bcrypt.compareSync(args.password, theUser.password);
if (!isMatch) throw new Error('Unable to Login');
return {token: jwt.sign(theUser, 'supersecret'), currentUser: theUser};
},
This returns a JWT and the user that's logging in.
In my React app I have a login component:
// Login.tsx
const [loginUserRes] = useMutation(resolvers.mutations.LoginUser);
const handleSubmit = async (e) => {
e.preventDefault();
const {data} = await loginUserRes({variables: {
email: formData.email,
password: formData.password,
}});
if (data) {
currentUserVar({
email: data.loginUser.currentUser.email,
id: data.loginUser.currentUser.id,
loggedIn: true,
});
window.localStorage.setItem('token', data.loginUser.token);
}
};
This function passes the form data to the LoginUser mutation which returns data if authentication is successful. Then I have a reactive variable called currentUserVar I store the email and id of the user in there so I can use it throughout the application. Finally I store the JWT in a LocalStorage so I can send it for authorization:
// index.tsx
const authLink = setContext((_, {headers}) => {
const token = localStorage.getItem('token');
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
},
};
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});
Everything is working, except for the fact that if a user refreshes the user data is gone and they have to log in again, which is of course quite annoying.
So I was hoping to get some advice on how to persist the data, perhaps using Apollo? I suppose I could add a checkbox with a remember me function that stores the email and id in the LocalStorage and when the app initiates check if there's user data in the LocalStorage and than use that, but I was wondering if there's a better/other way to do this.
When it comes to the login problem , you have set the headers on your every single request , but did you pass a fuction to the ApolloServer constructor that checks the headers from every single request ? Something like this:
const server=new ApolloServer({
typeDefs,
resolvers,
context:async({req})=>{
const me=getMe(req)
return {
models,
me,
process.env.SECRET
}
}
})
const getMe = async req => {
const token = req.headers['x-token'];
if (token) {
try {
return await jwt.verify(token, process.env.SECRET);
} catch (e) {
throw new AuthenticationError(
'Your session expired. Sign in again.',
);
}
}
};
As for the data persistence part of the question , you have to use setItem to persist the token in the locatStorage.

Persist auth state in react/react native for Firebase

I am using react native for an ios app and firebase for authentication. Every time I leave the app and come back, it asks for a login. I want to persist the firebase login but don't really know where to put it.
I know I need to put this in:
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL)
I have the following signIn function that runs when the login button is pressed on the signInScreen:
const signIn = async () => {
setLoading(true);
try {
await firebase.signIn(email, password);
const uid = firebase.getCurrentUser().uid;
const userInfo = await firebase.getUserInfo(uid);
const emailArr = userInfo.email.split("#");
setUser({
username: emailArr[0],
email: userInfo.email,
uid,
isLoggedIn: true,
});
} catch (error) {
alert(error.message);
} finally {
isMounted.current && setLoading(false);
}
};
I have the following signIn stuff in my firebaseContext:
const Firebase = {
getCurrentUser: () => {
return firebase.auth().currentUser;
},
signIn: async (email, password) => {
return firebase.auth().signInWithEmailAndPassword(email, password);
},
getUserInfo: async (uid) => {
try {
const user = await db.collection("users").doc(uid).get();
if (user.exists) {
return user.data();
}
} catch (error) {
console.log("Error #getUserInfo", error);
}
},
logOut: async () => {
return firebase
.auth()
.signOut()
.then(() => {
return true;
})
.catch((error) => {
console.log("Error #logout", error);
});
},
};
Where do I put the persist code I listed above from the docs?
Thanks!
When do you check if someon is signed in or not?
From the code shown it looks like you check it manuelly by calling currentUser. You have to consider that the persistance of auth state is asynchronous. That means if you call currentUser on auth before the localy saved auth state is loaded you would get there null and thing that the user is not signed in.
To get the auth state Firebase recommend to use the onAuthStateChanges event listener. With that you can listen to auth state changes no matter if you logged in or the persistet auth state is loaded.
The usage is very simple:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
That is the reson I asked where you check if someon is signed in or not. If I could see that code I could help you adopt it to use that event listener.

How do I use a Firebase refresh token to persist authentication?

I have been trying to figure this out for weeks and either can't seem to understand the documentation, or something. I appreciate any help you can give.
I am using the Firebase SDK
I have my server-side route, in which I can access the token and could send it to the front:
const admin = require("firebase-admin")
admin.initializeApp()
exports.loginRoute = (req, res) => {
const user = {
email: req.body.email,
password: req.body.password
}
const { valid, errors } = validateLoginData(user)
if (!valid) {
return res.status(400).json(errors)
}
admin
.auth()
.signInWithEmailAndPassword(user.email, user.password)
.then((data) => {
console.log(data.user.refreshToken, "refresh token")
return data.user.getIdToken(true)
})
.then((token) => {
return res.json({ token })
})
.catch((err) => {
console.error(err)
if (err.code === "auth/user-not-found") {
return res.status(400).json({ general: "User not found" })
} else if (err.code === "auth/wrong-password") {
return res
.status(400)
.json({ password: "User credentials don't match" })
} else {
res.status(500).json({
error: "Something went wrong, please try again."
})
}
})
}
Here is where I could use the refresh token (on the front end) to fetch a new authentication token, but I can't figure out how to create a route to do this:
if (token) {
const decodedToken = jwtDecode(token)
if (decodedToken.exp * 1000 < Date.now()) {
localStorage.setItem("Authentication", false)
//axios request to persist authentication would go here
}
}
Does anyone have a route that would work, or advice on what to do?
EDIT
const login = async (credentials) => {
let token
await axios
.post("/api/login", credentials)
.then((res) => {
token = res.data.token
const FBIdToken = `Bearer ${token}`
localStorage.setItem("token", token)
localStorage.setItem("FBIdToken", FBIdToken)
localStorage.setItem("Authentication", true)
context.setAuthenticated((prev) => true)
})
.then(() => {
context.getUserData()
})
.then(() => {
context.setUserState((prevUserState) => ({
...prevUserState,
token
}))
})
.catch((err) => {
context.setUserErrors((prev) => ({
...prev,
errors: err.response.data
}))
})
history.push("/")
}
Observer (client-side):
firebase.auth().onAuthStateChanged((user) => {
if (user) {
firebase
.auth()
.currentUser.getIdToken(/* forceRefresh */ true)
.then((idToken) => {
const FBIdToken = `Bearer ${idToken}`
localStorage.setItem("FBIdToken", FBIdToken)
})
.catch((err) => {
console.log(err)
})
} else {
localStorage.removeItem("FBIdToken")
}
})
If you sign in with the Firebase Authentication JavaScript SDK in the client-side code, it already persists the user's sign-in state, and tries to restore it when you reload the page. You shouldn't have to do anything for that yourself.
It seems like you were using the same SDK in a server-side environment though, which is quite unusual. If you want to mint tokens yourself in a server-side environment, you should use the Firebase Admin SDK to do so. You can then send that token back to the client, and use it to sign in to Firebase Authentication there.
But for the vast majority of use-cases, I recommend using the Firebase Authentication SDK in your client-side code, so that the SDK managed refreshing of the token for you. If you then want to pass the token to the server, you can use getIdToken() as you do now. You can also monitor ID token generation, or more commonly monitor if a user's sign-in session is restored as shown in the first example of the documentation on detecting the current user.

How can I pass data to a separate component which is loaded by a redirect?

I want to let users of my website see their basic information on a Dashboard component after they log in.
So far I've figured out that I have to assign the result of the axios log in request to a variable and then pass it. The question is: How can I pass that? From what I know, I can only pass data down from parent to child and the Dashboard is a completely separate component which gets loaded by calling props.history.push.
So far my request looks like:
const handleSubmitLogin = async (event: any) => {
event.preventDefault();
const isValid = validateLogin();
if (isValid) {
axios
.post(baseUrl + "User/SignIn", {
username: formDetails.loginUsername,
password: formDetails.loginPassword,
})
.then((res) => {
localStorage.setItem("cool-jwt", res.data.result.tokenString);
res = res.data.result;
props.history.push("/dashboard");
console.log(res);
return res;
})
.catch((e) => {
console.log(e);
});
setFormDetails(INIT_STATE);
}
};
After a successful login, the user gets redirected to his dashboard where I want to access the res data and let him see his customerID, username and email.
props.history.push accepts a second argument state. You can use this argument to pass the data which you want to be able to access from the Dashboard component.
.then((res) => {
localStorage.setItem("cool-jwt", res.data.result.tokenString);
const result = res.data.result;
props.history.push("/dashboard", {
customerId: result.id,
username: result.username,
email: result.email,
});
Inside the Dashboard component, access the state through the location via props.location.state or the useLocation hook.
const {state} = useLocation();

Property in state becomes undefined within action creators

I'm making an app where different users can add their own plants/flowers.
The flowerlist contains the users flowers and loads these items upon mounting.
class flowerList extends Component {
componentDidMount() {
this.props.getFlowers();
}
To send the correct GET request to the backend I need to have the currently logged in user's ID.
This is what the called action creator looks like:
export const getFlowers = () => (dispatch, getState) => {
dispatch(setFlowersLoading());
axios
.get(`/api/users/${getState().auth.user.id}/flowers`)
.then((res) =>
dispatch({
type : GET_FLOWERS,
payload : res.data
})
)
.catch((err) => dispatch(returnErrors(err.response.data, err.response.status)));
};
However, this doesn't work very well. It only works when coming directly from signing in. If I refresh the page, the app crashes with the error message "TypeError: Cannot read property 'id' of null". When writing the POST requests in a similar fashion it doesn't work well either, so I guess there must be a better way to access the state. I'd really appreciate any help in getting this to work.
When you login, you should set local storage to keep the users info something like these:
const setAuthorizationHeader = token => {
const Token = `Bearer ${token}`;
localStorage.setItem("Token", Token);
axios.defaults.headers.common["Authorization"] = Token;
};
you can add it to your user login action, after then(), when the login is successful, here is an example, I assume you handle the token in the backend, so after successful login, it sends the token with a respond(res.data):
export const loginUser = (userData) => dispatch => {
axios
.post("http://localhost:5000/api/users/login", userData)
.then(res => {
setAuthorizationHeader(res.data.token);
})
.catch(err => {
dispatch({
type: SET_ERRORS,
payload: err.response
});
});
};
Afterwards, put these to your app.js:
const token = localStorage.Token;
if (token) {
const decodedToken = jwtDecode(token);
if (decodedToken.exp * 1000 < Date.now()) {
store.dispatch(logoutUser());
window.location.href = "/login";
} else {
store.dispatch({ type: SET_AUTHENTICATED_USER });
axios.defaults.headers.common["Authorization"] = token;
store.dispatch(getUserData(decodedToken));
}
}
Here I used jwtDecode because I am using JWT to crypt my users' info and store it to the localStorage, these codes provide to look for Token in localStorage after refreshing the page. If the user logged in, there is the token and so the app will not crash

Resources