MERN stack Ec2 deployed website does not log in - reactjs

Seeking some help, trying to deploy a mern stack application on aws using ec2 instances. Everythings going well I can register new users and I can log in, problem is it doesn't stay logged in, the user instantly becomes undefined. Has anyone ran into this problem before? Think it might have something to do with the cookies. As Postman works good, its able to log in and then retrieve user.
Featured is the middleware code using Passport.
const LocalStrategy = passportLocal.Strategy;
mongoose.connect(...);
// Middleware
const app = express();
app.use(express.json());
//before deployment
//app.use(cors({origin:"http://localhost:3000",credentials:true}))
app.use(cookieParser());
app.use(cors({origin:"http://ec2-18-191-200-43.us-east-2.compute.amazonaws.com:3000",credentials:true}))
app.use(
session({
secret:"secretcode",
resave:true,
saveUninitialized:true,
cookie:{
sameSite:false
}
})
);
app.use(passport.initialize());
app.use(passport.session());
//Passport
passport.use(new LocalStrategy((username:string, password:string, done) => {
User.findOne({ username: username }, (err:any, user: DatabaseUserInterface) => {
if (err) throw err;
if (!user) {
console.log("not user");
return done(null, false);
}
bcrypt.compare(password, user.password, (err, result:boolean) => {
if (err) throw err;
if (result === true) {
console.log("good should be logged in");
return done(null, user);
} else {
//unuathorised
console.log("unathorised");
return done(null, false);
}
});
});
})
);
passport.serializeUser((user: DatabaseUserInterface, cb) => {
cb(null, user._id);
});
passport.deserializeUser((id: string, cb) => {
User.findOne({ _id: id }, (err: any, user: DatabaseUserInterface) => {
const userInformation :UserInterface = {
username: user.username,
isAdmin: user.isAdmin,
id: user._id,
wins:user.wins,
losses:user.losses
};
cb(err, userInformation);
});
});
//log in
app.post("/login", passport.authenticate("local"), (req, res) => {
res.send("success");
});

Follow up: Im an aws noob, and thought you had to run 2 different clusters with 2 different instances to run 2 tasks. You in fact only need 1 cluster with 1 instance that can run as many tasks as you want. This way cookies can actually be transferred between the ports used by the different tasks i.e. front and backend.

Related

Passport JS does not allow to logout (ExpressJS, PassportJS, React)

I'm implementing SSO authentication through passport google oidc strategy. Everything works fine for authentication, but I encounter some problems while logging out.
So, here's my Backend logout route
authRouter.post('/logout', async (req, res, next) => {
req.logout(req.user, (err)=> {
if (err) return next(err);
})
req.session.destroy((err) => {
res.clearCookie('connect.sid');
});
})
Here's Logout Handler on FrontEnd:
const handlerLogOut = async () => {
await axios.post('/api/auth/logout', {withCredentials: true})
.then(res => {
console.log('response ', res);
})
}
But while destroying the session, I encounter the next problem which prevent me from logging out:
<MyDisk>\<My Folder>\<Project Folder>\server\node_modules\passport\lib\sessionmanager.js:83
req.session.regenerate(function(err) {
^
TypeError: Cannot read properties of undefined (reading 'regenerate')
at Statement.<anonymous> (<MyDisk>\<My Folder>\<Project Folder>\server\node_modules\passport\lib\sessionmanager.js:83:17)
at Statement.<anonymous> <MyDisk>\<My Folder>\<Project Folder>\server\node_modules\connect-sqlite3\lib\connect-sqlite3.js:119:32)
It seems that session manager trying to regenerate the session which does not exist anymore and here's the error
I've implemented the solution showed in the next question req.session.destroy and passport logout aren't destroying cookie on client side but that doesn't work for me.
Pure req.logout doesn't work too.
Session does not destroy and req.user still has user after req.logout has been called
I figured it out.
Deleted req.destroy.session()
authRouter.post('/logout', async (req, res, next) => {
req.logout(req.user, (err)=> {
if (err) return next(err);
})
res.clearCookie('connect.sid');
res.send({isAuth: req.isAuthenticated(), user: req.user})})
And on the front-side:
await axios.post('/api/auth/logout',{}, {withCredentials: true})
.then(res => {
console.log('response ', res);
setUser(res.data.user)
})
.catch(e=> console.log(e))
Instead of:
await axios.post('/api/auth/logout',{withCredentials: true})
.then(res => {
console.log('response ', res);
setUser(res.data.user)
})
.catch(e=> console.log(e))

How can I save my token after closing the browser so that I stay logged in?

I have a React application that allows users to login using either google auth or email and password. In both cases, a JWT token containing the user ID is created. As long as the user has the respective browser open, he can reload the page without having to log in again. However, if the user closes the window, he must log in again because the token is no longer available. How can I change this ?
This is my auth controller class inside my server:
export const googleAuth = async (req, res, next) => {
try {
const user = await User.findOne({ email: req.body.email });
if (user) {
console.log(user);
const token = jwt.sign({ id: user._id }, process.env.JWT);
res
.cookie("access_token", token, {
httpOnly: true,
})
.status(200)
.json(user._doc);
} else {
console.log(req.body);
const newUser = new User({
...req.body,
fromGoogle: true,
});
const savedUser = await newUser.save();
const token = jwt.sign({ id: savedUser._id }, process.env.JWT);
res
.cookie("access_token", token, {
httpOnly: true,
})
.status(200)
.json(savedUser._doc);
}
} catch (err) {
next(err);
}
};
This is my verifyToken.js class, which checks whether the user is still logged in or whether the token is still available
export const verifyToken = (req, res, next) => {
const token = req.cookies.access_token;
if (!token) return next(createError(401, "You are not authenticated!"));
jwt.verify(token, process.env.JWT, (err, user) => {
if (err) return next(createError(403, "Token is not valid!"));
req.user = user;
next();
});
};
And that is calling a route, which first checks the verifiyToken and then, if it exists, executes the method
import { verifyToken } from "../verifyToken.js";
const router = express.Router();
router.get("/fav", verifyToken, fav);
const loadArticel = async () => {
setLoading(true);
await axiosInstance
.get(`/articel/fav`, { withCredentials: true })
.then((res) => {
setArticel(res.data);
setLoading(false);
});
};
However, this is the result:
If you give the cookie an expiry date, for example 24 hours from now, it will not be deleted when the browser is closed.
res.cookie("access_token", token, {
httpOnly: true,
expires: new Date(Date.now() + 24 * 60 * 60 * 1000)
});
But as a user, I would object to such a non-session cookie, unless I have checked an option "Keep me logged in for 24 hours" (or so) on the logon page.

React-facebook-login work only for one account

Hello I have implemented react facebook login package in my app. However it works only for one account. I can't get login info data with another account. Here is my code:
const registerFb = async (req, res) => {
console.log(req.body);
const newUser = new UsersSchema({
email: req.body.email,
});
try {
const existEmail = await UsersSchema.findOne({ email: req.body.email });
if (existEmail) {
const token = jwt.sign({ email: req.body.email }, "privKey");
res.send(token);
} else {
const savedUser = await newUser.save();
res.send(savedUser);
}
} catch (err) {
res.json({ message: err });
}
};
Is your app still in development mode? If so, you can only test with specified users -- and the person who set-up the app is one of the specified users by default.
You can add more testing users under Roles in the App Dashboard.

How to store my JWT token correctly and return data in MERN app

I'm creating a MERN application as I'm learning how to use React to create a blog/social media like website. I already have routes and jsx setup in order to display posts via axios requests and pulling the data from my MongoDB cluster.
This all works perfectly but I am now struggling on finding a proper way to handle authentication for users and display data back into my MERN application.
My current code that registers the user takes their name, email and password; using bcrpyt to hash/salt the password for security.
Register Code
exports.register = (req, res, next) => {
let {
name,
email,
password,
password_confirmation
} = req.body;
User.findOne({
email: email
})
.then(user => {
if (user) {
return res.status(422).json({
errors: [{
user: "email already exists"
}]
});
} else {
const user = new User({
name: name,
email: email,
password: password,
});
bcrypt.genSalt(10, function (err, salt) {
bcrypt.hash(password, salt, function (err, hash) {
if (err) throw err;
user.password = hash;
user.save()
.then(response => {
res.status(200).json({
success: true,
result: response
})
})
.catch(err => {
res.status(500).json({
errors: [{
error: err
}]
});
});
});
});
}
}).catch(err => {
res.status(500).json({
errors: [{
error: 'Something went wrong'
}]
});
})
}
my login function then uses bcrypt.compare in order to make sure the password types in matches with the hashed password stored in the database. If succesful, my code will call a function I created called createJWT which has a payload containing the email, userId and duration of the token before expiring.
const jwt = require("jsonwebtoken");
exports.createJWT = (email, userId, duration) => {
const payload = {
email,
userId,
duration
};
return jwt.sign(payload, process.env.TOKEN_SECRET, {
expiresIn: duration,
});
};
Login code
exports.login = (req, res) => {
let {
email,
password
} = req.body;
User.findOne({
email: email
}).then(user => {
if (!user) {
return res.status(404).json({
errors: [{
user: "not found"
}],
});
} else {
bcrypt.compare(password, user.password).then(isMatch => {
if (!isMatch) {
return res.status(400).json({
errors: [{
password: "incorrect"
}]
});
}
let access_token = createJWT(
user.email,
user._id,
3600
);
jwt.verify(access_token, process.env.TOKEN_SECRET, (err,
decoded) => {
if (err) {
res.status(500).json({
errors: err
});
}
if (decoded) {
return res.status(200).json({
success: true,
token: access_token,
message: user
});
}
});
}).catch(err => {
res.status(500).json({
errors: err
});
});
}
}).catch(err => {
res.status(500).json({
errors: err
});
});
}
If the login function is succesful, the code will decode the JWT code if the token matches my TOKEN_SECRET stored in my env file.
This all works perfectly fine but I am stuck at this point on where to go next. I have created a piece of middleware that I can succesfully implement into my routes in order to stop them loading if the user is not authorized.
//Auth middleware
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
try {
const token = req.header('x-auth-token');
jwt.verify(token, process.env.TOKEN_SECRET);
next();
} catch (error) {
res.status(401).json({
message: "Token not authorized!"
})
}
};
const express = require('express');
const router = express.Router();
const multer = require("multer");
const authorize = require('../middleware/auth');
const {
getPosts,
createPost,
findPostById,
updatePost,
deletePost
} = require('../controllers/posts');
const storage = multer.diskStorage({
destination: (req, file, callback) => {
callback(null, './client/public/uploads');
},
filename: (req, file, callback) => {
callback(null, file.originalname);
}
});
const upload = multer({
storage: storage
});
router.get('/', authorize, getPosts);
router.post('/add', upload.single("postImage"), createPost);
router.get('/:id', findPostById);
router.put('/update/:id', upload.single("postImage"), updatePost);
router.delete('/:id', deletePost);
module.exports = router;
From this point, what would be the best way to have the user login and gain access to the posts (containing my authorization middleware) if they have logged in succesfully and have the JWT token. I was also wondering how i would achieve something like pulling the users name from the decoded token and have it shown (for example) in the navbar for as long as they logged in / the session persists.
Any help here would be greatly appreciated, thanks everyone!
let access_token = createJWT(
user.email,
user._id,
3600
);
jwt.verify(access_token, process.env.TOKEN_SECRET, (err,
decoded) => {
if (err) {
res.status(500).json({
errors: err
});
}
if (decoded) {
return res.status(200).json({
success: true,
token: access_token,
message: user
});
}
});
There's no point to verify the JWT here as you just generated it on the server-side. This is the process:
Register: The user provides a username and a password. You save the user in the database and for the password, you save a hash.
When logging in, the user is providing the username and the password. If they are correct you create a JWT and return that to the client application. In the JWT you will encode the username/user_id.
The client needs to store that JWT somewhere and then send it in the following requests(usually as a header).
On the server you have a middleware that parses that header, decodes the JWT, and identify the user based on username/user_id or whatever you encoded there.
The client should not decode the JWT and pull information like the username from there. It should make an API request to the backend asking for the user information.
There are lots of things to take into consideration here like. Just to name a few:
how do you store the JWT on the client in a secure way?
on the client app, how do you prevent an unauthenticated user to access some private routes?
what do you do after the JWT expires?
how do you handle CORS?
are there any vulnerabilities?
I highly recommend this free course which will teach you a lot about how to implement these things.

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.

Resources