I have set up my react app on xxx.herokuapp.com, and my node app on yyy.herokuapp.com. Both are working, and I am the backend is able to send data to the front end. But when I want to send a cookie, no cookie is set. It works during local host, but not in production. I am also using cookieParser for cookies. Here is the code:
index.js:
app.use(
cors({
origin: [
"http://localhost:3000",
"https://xxx.herokuapp.com",
"https://yyy.herokuapp.com",
],
credentials: true,
})
);
auth.js:
export const signin = async (req, res, next) => {
try {
const user = await User.findOne({ username: req.body.username });
if (!user) return next(createError(404, "User does not exist!"));
const verifyPassword = bcrypt.compareSync(req.body.password, user.password);
if (!verifyPassword) return next(createError(400, "Invalid credentials!"));
const token = jwt.sign({ id: user._id }, process.env.JWT_KEY);
const { password, ...others } = user._doc;
res
.cookie("access_token", token, {
httpOnly: true,
})
.status(200)
.json(others);
} catch (error) {
next(error);
}
};
react:
const handleLogin = async (e) => {
e.preventDefault();
dispatch(loginStart);
try {
setOpen(true);
setAlert({ message: "Logged in.", severity: "success" });
const res = await axios.post(`${SERVER_URL}/auth/signin`, userForm, {
withCredentials: true,
});
dispatch(loginSuccess(res.data));
navigate("/");
// window.location.href = "/";
} catch (error) {
dispatch(loginFailure());
setOpen(true);
setAlert({
message: error.response.data.message,
severity: "error",
});
}
};
Had to put {sameSite: "none",
secure: true} in the res.cookie
Related
It was working when my client side used HTTP, but now that I've created a self-signed certificate and now my client side is running on HTTPS, the cookie I set in the backend isn't appearing in the browser. My express server is currently running on port 5000 HTTP and my client side is currently running on port 3000 with HTTPS.
backend
const app = express();
app.use(
cors({
credentials: true,
origin: "https://localhost:3000",
})
);
app.use(cookieParser({}));
app.use("/api/auth", authRoute);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log("Server is Running on Port " + PORT);
});
//from api/auth
const login = async (req, res) => {
try {
const user = await User.findOne({ email: req.body.email });
if (!user)
return res
.status(404)
.json({ success: false, message: "Email or Password Incorrect" });
const validatePassword = await bcrypt.compare(
req.body.password,
user.password
);
if (!validatePassword)
return res
.status(404)
.json({ success: false, message: "Email or Password Incorrect" });
const { password, ...others } = user._doc;
const token = generateJwt(user._id);
res.cookie("isLogged", true, {
sameSite: "lax",
});
res.cookie("authToken", token, {
httpOnly: true,
sameSite: "lax",
});
res.status(200).json({ success: true, user: others });
} catch (err) {
console.log(err);
res.status(500).json({ success: false, message: err });
}
};
onClient side
import axios from "axios";
const axiosInstance = axios.create({
withCredentials: true,
baseURL: "http://localhost:5000/api/",
});
export default axiosInstance;
const login = async () => {
try {
const data = {
email: email.current.value,
password: password.current.value,
rememberMe: rememberMe.current.checked,
};
const res = await axios.post("auth/login", data);
console.log(res);
} catch (err) {
console.error(err);
}
};
I have this block of code and when a user login, it should set the "access_token" cookie to a token, but when i check the browser application cookie, i see nothing and user is unauthenticated.
const login = async (req, res) => {
try {
const oneUser = await Users.findOne({ username: req.body.username });
if (!oneUser) {
return res.status(403).json("No such user in the database");
}
const isPassword = await bcryptjs.compare(req.body.password, oneUser.password);
if (!isPassword) {
return res.status(500).json("Password is incorrect");
}
const token = jwt.sign(
{
id: oneUser._id,
isAdmin: oneUser.isAdmin,
},
process.env.jwt,
);
const { password, ...others } = oneUser._doc;
res
.cookie("access_token", token)
.status(200)
.json({ ...others });
} catch (err) {
res.status(500).json(err);
}
};
in the routes auth file i call it like so
router.post("/login", login);
then in my index.html file, i linked the routes up.
app.use("/api/auth", authRoute);
I am trying to show the page only if the Jsonwebtoken is verified and the user is logged on to the website, else show him the sign-in page.
However, I can see the token is generated in MongoDB, and also when I console log I can see that it is all good. But the issue is when I try to verify it using an already generated jwt token i.e.
req.cookies.signinToken
it shows an error.
Please the detail code below:
On app.js
const dotenv = require("dotenv");
const mongoose = require("mongoose");
const express = require("express");
const app = express();
const jwt = require("jsonwebtoken");
const cookieParser = require("cookie-parser");
dotenv.config({ path: "./config.env" });
require("./db/connection");
app.use(express.json());
app.use(cookieParser());
app.use(require("./router/route"));
const PORT = process.env.PORT;
app.listen(5000, () => {
console.log(`server running on ${PORT}`);
});
On route.js
const express = require("express");
const bcrypt = require("bcrypt");
const router = express.Router();
const jwt = require("jsonwebtoken");
require("../db/connection");
const User = require("../model/newUserSchema");
const auth = require("../middleware/auth");
// router.get("/", (req, res) => {
// res.send("hello am backend sever");
// });
//Signup or Register Part
router.post("/signup", async (req, res) => {
const { username, email, cpassword, retypePassword } = req.body;
if (!username || !email || !cpassword || !retypePassword) {
return res.status(422).json({ error: "please enter valid details" });
}
try {
const UserExist = await User.findOne({ email: email });
if (UserExist) {
return res.status(422).json({ error: "email already exist" });
} else if (cpassword !== retypePassword) {
return res.status(422).json({ error: "password incorrect" });
} else {
const user = new User({
username,
email,
cpassword,
retypePassword,
});
const userResgister = await user.save();
if (userResgister) {
return res.status(201).json({ message: "signup successfully" });
}
}
} catch (error) {
console.log(error);
}
});
//Login Part
router.post("/signin", async (req, res) => {
try {
const { email, cpassword } = req.body;
if (!email || !cpassword) {
return res.status(400).json({ error: " please enter valid credentials" });
}
const userLogin = await User.findOne({ email: email });
const token = userLogin.generateAuthToken();
res.cookie("signinToken", token, {
expires: new Date(Date.now() + 25892000000),
httpOnly: true,
});
if (userLogin) {
const isMatch = await bcrypt.compare(cpassword, userLogin.cpassword);
if (isMatch) {
return res.status(200).json({ message: "sigin in scuccessfully" });
} else {
return res.status(400).json({ error: " Invalid credentials" });
}
} else {
return res.status(400).json({ error: " Invalid credentials " });
}
} catch (error) {
console.log(error);
}
});
//watchlistpage
router.get("/watchlist", auth, (req, res) => {
console.log(" this is jwt token test " + req.cookies.signinToken);
res.send(req.rootuser);
console.log(req.rootuser);
});
module.exports = router;
On newUserSchema.js:
const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const newUserSchema = new mongoose.Schema({
username: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
cpassword: {
type: String,
required: true,
},
retypePassword: {
type: String,
required: true,
},
tokens: [
{
token: {
type: String,
required: true,
},
},
],
});
newUserSchema.pre("save", async function (next) {
if (this.isModified("cpassword")) {
this.cpassword = await bcrypt.hash(this.cpassword, 12);
this.retypePassword = await bcrypt.hash(this.retypePassword, 12);
}
next();
});
newUserSchema.methods.generateAuthToken = async function () {
try {
let token = jwt.sign({ _id: this._id }, process.env.SECRETKEY);
this.tokens = this.tokens.concat({ token: token });
await this.save();
return token;
} catch (error) {
console.log(error);
}
};
const User = mongoose.model("newUser", newUserSchema);
module.exports = User;
On auth.js (this is also my middleware)
const jwt = require("jsonwebtoken");
const User = require("../model/newUserSchema");
const Auth = async (req, res, next) => {
try {
console.log(JSON.stringify(req.cookies.signinToken) + " this is jwt token test");
const token = req.cookies.signinToken;
const verifytoken = jwt.verify(token, process.env.SECRETKEY);
const rootuser = await User.findOne({ _id: verifytoken._id, "tokens.token": token });
if (!rootuser) {
throw new Error("user not found");
}
req.token = token;
req.rootuser = rootuser;
req.UserID = rootuser._id;
next();
} catch (error) {
res.status(401).send("Unauthorized access");
console.log(error);
}
};
module.exports = Auth;
The API call result in postman
The terminal error :
when I try to console log on route.js inside Signin page i see promise pending
const token = userLogin.generateAuthToken();
console.log(token);
res.cookie("signinToken", token, {
expires: new Date(Date.now() + 25892000000),
httpOnly: true,
});
Could you please help to correct the error also please let me know why is this error coming?
Thanks a million in advance for any tips or suggestions.
Hi Thanks for the help
I just saw that my token was returning a promise, I did not add the keyword await before the token other thing was I was trying to access it before the validation, hence it was showing me nodata and error.
Please see the correct code below:
//Login Part
router.post("/signin", async (req, res) => {
try {
const { email, cpassword } = req.body;
if (!email || !cpassword) {
return res.status(400).json({ error: " please enter valid credentials" });
}
const userLogin = await User.findOne({ email: email });
if (userLogin) {
const isMatch = await bcrypt.compare(cpassword, userLogin.cpassword);
const token = await userLogin.generateAuthToken();
console.log(token);
res.cookie("signinToken", token, {
expires: new Date(Date.now() + 25892000000),
httpOnly: true,
});
if (isMatch) {
return res.status(200).json({ message: "sigin in scuccessfully" });
} else {
return res.status(400).json({ error: " Invalid credentials" });
}
} else {
return res.status(400).json({ error: " Invalid credentials " });
}
} catch (error) {
console.log(error);
}
});
I hope this might help other learners too.
Thanks.
I was following a tutorial at Authentication in NodeJS With Express and Mongo - CodeLab #1
I got everything to work perfectly, but the tutorial does not address how to log out a user.
From what I can tell, the session is being saved on Mongoose Atlas, which is the database I am using. When I log a user in with Postman, I get a token back. But I am not sure how to configure the /logout route.
Here is my code:
//routes/user.js
const express = require("express");
const { check, validationResult } = require("express-validator");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const router = express.Router();
const auth = require("../middleware/auth");
const User = require("../models/User");
/**
* #method - POST
* #param - /signup
* #description - User SignUp
*/
//Signup
router.post(
"/signup",
[
check("username", "Please Enter a Valid Username")
.not()
.isEmpty(),
check("email", "Please enter a valid email").isEmail(),
check("password", "Please enter a valid password").isLength({
min: 6
})
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array()
});
}
const {
username,
email,
password
} = req.body;
try {
let user = await User.findOne({
email
});
if (user) {
return res.status(400).json({
msg: "User Already Exists"
});
}
user = new User({
username,
email,
password
});
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
await user.save();
const payload = {
user: {
id: user.id
}
};
jwt.sign(
payload,
"randomString", {
expiresIn: 10000
},
(err, token) => {
if (err) throw err;
res.status(200).json({
token
});
}
);
} catch (err) {
console.log(err.message);
res.status(500).send("Error in Saving");
}
}
);
// Login
router.post(
"/login",
[
check("email", "Please enter a valid email").isEmail(),
check("password", "Please enter a valid password").isLength({
min: 6
})
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array()
});
}
const { email, password } = req.body;
try {
let user = await User.findOne({
email
});
if (!user)
return res.status(400).json({
message: "User Not Exist"
});
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch)
return res.status(400).json({
message: "Incorrect Password !"
});
const payload = {
user: {
id: user.id
}
};
jwt.sign(
payload,
"randomString",
{
expiresIn: 3600
},
(err, token) => {
if (err) throw err;
res.status(200).json({
token
});
}
);
} catch (e) {
console.error(e);
res.status(500).json({
message: "Server Error"
});
}
}
);
// router.route("/logout").get(function (req, res, next) {
// if (expire(req.headers)) {
// delete req.user;
// return res.status(200).json({
// "message": "User has been successfully logged out"
// });
// } else {
// return next(new UnauthorizedAccessError("401"));
// }
// });
router.get("/me", auth, async (req, res) => {
try {
// request.user is getting fetched from Middleware after token authentication
const user = await User.findById(req.user.id);
res.json(user);
} catch (e) {
res.send({ message: "Error in Fetching user" });
}
});
router.get('/logout', isAuthenticated, function (req, res) {
console.log('User Id', req.user._id);
User.findByIdAndRemove(req.user._id, function (err) {
if (err) res.send(err);
res.json({ message: 'User Deleted!' });
})
});
module.exports = router;
function isAuthenticated(req, res, next) {
console.log("req: " + JSON.stringify(req.headers.authorization));
// if (!(req.headers && req.headers.authorization)) {
// return res.status(400).send({ message: 'You did not provide a JSON web token in the authorization header' });
//}
};
///middleware/auth.js
const jwt = require("jsonwebtoken");
module.exports = function (req, res, next) {
const token = req.header("token");
if (!token) return res.status(401).json({ message: "Auth Error" });
try {
const decoded = jwt.verify(token, "randomString");
req.user = decoded.user;
next();
} catch (e) {
console.error(e);
res.status(500).send({ message: "Invalid Token" });
}
};
///models/User.js
const mongoose = require("mongoose");
const UserSchema = mongoose.Schema({
username: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now()
}
});
// export model user with UserSchema
module.exports = mongoose.model("user", UserSchema);
So my question is, how can I implement a /logout route so that if the user clicks the logout button and invokes that route, their token is destroyed. I am only asking about the back-end part. I can handle using axios.
Thanks.
From what I see, you are not saving any session data or storing tokens anywhere - which is great. You are simply appending the token to your headers in requests to the API.
So the only thing you can do is possibly expire the token in the /logout route
and then ensure you delete the token on the client - could be localStorage, sessionStorage etc - your client code needs to kill the token so it cannot be included again.
Side note:
You are not extending the token lifetime anywhere, so even if the user keeps interacting on the website, the token expiration is not being updated. You will need to manually refresh the token/generate a new token to have a sliding expiration.
I would suggest you save the token in cookies rather. Set the cookie to HttpOnly, Secure, and specify the domain. This is far more secure and will allow you to also expire the cookie from the API. If any scripts you include get compromised, they can access all your users’ tokens easily.
Example:
import {serialize} from 'cookie';
import jsend from 'jsend';
...
const token = jwt.sign(
{
id: validationResult.value.id // whatever you want to add to the token, here it is the id of a user
},
privateKeyBuffer,
{
expiresIn: process.env.token_ttl,
algorithm: 'RS256'
});
const cookieOptions = {
httpOnly: true,
path: '/',
maxAge: process.env.token_ttl,
expires: new Date(Date.now() + process.env.token_ttl),
sameSite: process.env.cookie_samesite, // strict
domain: process.env.cookie_domain, // your domain
secure: process.env.cookie_secure // true
};
const tokenCookie = await serialize('token', token, cookieOptions);
res.setHeader('Set-Cookie', [tokenCookie]);
res.setHeader('Content-Type', 'application/json');
res.status(200).json(jsend.success(true));
Then in logout:
// grab from req.cookies.token and validate
const token = await extractToken(req);
// you can take action if it's invalid, but not really important
if(!token) {
...
}
// this is how we expire it - the options here must match the options you created with!
const cookieOptions = {
httpOnly: true,
path: '/',
maxAge: 0,
expires: 0,
sameSite: process.env.cookie_samesite, // strict
domain: process.env.cookie_domain, // your domain
secure: process.env.cookie_secure // true
};
// set to empty
const tokenCookie = await serialize('token', '', cookieOptions);
res.setHeader('Set-Cookie', [tokenCookie]);
res.setHeader('Content-Type', 'application/json');
res.status(200).json(jsend.success(true));
As you have used JWT the backend will always check 2 things
1. Proper token
2. If time is over for that particular (You should handle this one)
For 2nd point, if the user time is up than from frontend you may delete the token if you have stored the token in localstorage.
For logout when user clicks on logout just delete jwt from localstorage and redirect to login or other page
I'm new to React. I'm having trouble understanding what is happening to my req.session.passport object that is created when a user logs in to the application. When logging into my application I get the following in my console:
axios.post(/login) response: login.component.js:39
Object { cookie: {…}, passport: {…} }
The problem comes when I go to another page. This appears on the console:
axios.get(/logged_in) response: user.component.js:37
this.state.session: user.component.js:38
Object { cookie: {…} }
Where did the passport object go? I need it to tell react who is logged in. I'm calling req.session in both axios paths so I expect to see the exact same result, but I don't. None of the topics similar enough to my situation has helped me figure out this problem.
onSubmit(e) function from login component:
onSubmit(e) {
e.preventDefault();
axios.post("http://localhost:4000/users/login", {
username: this.state.username,
password: this.state.password
}).then(res => {
console.log("axios.post(/login) response: ");
console.log(res.data);
if (res.status === 200) {
this.props.updateSession({
session: res.data
});
}
}).catch(err => {
console.log("Login error: ");
console.log(err);
});
this.props.history.push("/");
}
Route that the above function uses to authenticate a user:
router.post("/login", passport.authenticate("local"), (req, res) => {
console.log("Calling router.post(/login)...");
console.log("req.session: ", req.session);
res.status(200).json(req.session);
});
componentDidMount() function from the main user component that checks if a session is active:
componentDidMount() {
axios.get("http://localhost:4000/users/logged_in").then(res => {
this.setState({
session: res.data
});
console.log("axios.get(/logged_in) response: ");
console.log("this.state.session: ");
console.log(this.state.session);
}).catch(err => {
console.log(err);
})
}
Route that the above function uses. Very similar to the login route minus the authentication:
router.get("/logged_in", (req, res) => {
console.log("Calling router.get(/logged_in)...");
res.status(200).json(req.session);
});
localStrategy.js (Passport):
const User = require("../database/models/user.model"),
LocalStrategy = require('passport-local').Strategy;
const strategy = new LocalStrategy(
{
usernameField: "username" // not necessary, DEFAULT
},
function(username, password, done) {
User.findOne({ username: username }, (err, user) => {
if (err) {
return done(err)
}
if (!user) {
return done(null, false, { message: "Incorrect username" })
}
if (!user.checkPassword(password)) {
return done(null, false, { message: "Incorrect password" })
}
return done(null, user)
})
}
)
module.exports = strategy;
index.js (Passport):
const passport = require("passport"),
LocalStrategy = require("./localStrategy"),
User = require("../database/models/user.model");
passport.serializeUser((user, done) => {
console.log("*** serializeUser called, user: ");
console.log(user);
console.log('---------');
done(null, { _id: user._id });
})
passport.deserializeUser((id, done) => {
console.log("DeserializeUser called");
User.findOne(
{ _id: id },
"username",
(err, user) => {
console.log("*** Deserialize user, user:");
console.log(user);
console.log("--------------");
done(null, user);
}
)
})
passport.use(LocalStrategy);
module.exports = passport;
console.log result from serializeUser() above:
*** serializeUser called, user:
{ isAdmin: false,
orders: [],
createdAt: 2020-02-11T02:09:18.992Z,
_id: 5e420cdb...,
username: 'user',
password:
'$2a$1...',
__v: 0 }
server.js:
const express = require("express"),
app = express(),
bodyParser = require("body-parser"),
session = require("express-session"),
MongoStore = require('connect-mongo')(session),
cors = require("cors"),
PORT = 4000,
// Require routes
productRoutes = require("./routes/products"),
orderRoutes = require("./routes/orders"),
userRoutes = require("./routes/users"),
passport = require('./passport'),
dbConnection = require('./database');
app.use(bodyParser.json());
app.use(cors());
// Sessions
app.use(
session({
secret: "Birdhouses are cool.", // Secret can be any string
store: new MongoStore({ mongooseConnection: dbConnection }),
resave: false,
saveUninitialized: false
})
);
app.use(passport.initialize());
app.use(passport.session()); // calls serializeUser and deserializeUser
// Routes config
app.use("/products", productRoutes);
app.use("/orders", orderRoutes);
app.use("/users", userRoutes);
// Start server
app.listen(PORT, function() {
console.log("Server is running on Port: " + PORT);
});
User model:
const mongoose = require("mongoose"),
bcrypt = require("bcryptjs");
// Define user schema
let userSchema = new mongoose.Schema({
username: {
type: String
},
password: {
type: String
},
isAdmin: {
type: Boolean,
default: false
},
orders: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Order"
}],
createdAt: {
type: Date,
default: Date.now()
}
});
userSchema.methods = {
checkPassword: function(inputPassword) {
return bcrypt.compareSync(inputPassword, this.password);
},
hashPassword: plainTextPassword => {
return bcrypt.hashSync(plainTextPassword, 10);
}
}
userSchema.pre("save", function(next) {
if (!this.password) {
console.log("models/user.js =======NO PASSWORD PROVIDED=======")
next();
} else {
console.log("models/user.js hashPassword in pre save");
this.password = this.hashPassword(this.password);
next();
}
})
module.exports = mongoose.model("User", userSchema);