I have a problem with setting and reading cookies. There is a login mask, a user logs in with an email and password, and if successful, his user ID is saved in a cookie (access_token). If I now call further POST or GETs, it is checked whether the token is still up to date. The problem here is that I get an undefined every time I check. What could be the reason? Thank you very much!
verifyToken.js
import jwt from "jsonwebtoken";
export const verifyToken = (req, res, next) => {
const token = req.cookies.access_token;
console.log("verifyToken: " + token); //undefined
if (!token) {
return res.status(200).send({
error: true,
msg: "Authentication Failed.",
});
}
jwt.verify(token, process.env.JWT, (err, user) => {
if (err) {
return res.status(200).send({
error: true,
msg: "Authentication Failed.",
});
}
req.user = user;
next();
});
};
route arts.js
import {
ChangeArt
} from "../controller/arts.js";
import express from "express";
import { verifyToken } from "../verifyToken.js";
const router = express.Router();
router.put("/ChangeArt/:id",verifyToken, ChangeArt);
Login.js Frontend
res
.cookie("access_token", token, {
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000,
SameSite: process.env.NODE_ENV === "development" ? false : true,
})
.status(200)
.json(others);
Server
//Middleware
const corsOptions = {
credentials: true,
origin: "http://localhost:3000", //or production URL
};
app.use(cookieParser());
app.use(cors(corsOptions));
Related
I am working with this NodeJS project using express-session to create session for my application. The problem is, when I make a post request to the http://localhost:5500/login, a session is created with additional property userid that I added intentionally. Then, when I use Postman to make a get request to http://localhost:5500/, the application actually receives the session with the property userid and redirect the user to his home page based on the userid is set or not. However, if I make get request to http://localhost:5500/ from a browser like Chrome, my server is not able to get the session with the additional property `userid' that I added when log in successfully and does not redirect my user to his home page. Can anyone explain why this happens please? Thank you
Here is the code of my index.js
`
const express = require("express")
const app = express()
const PORT = process.env.PORT || 5500
const session = require("express-session")
const { routers } = require("./routes/routes")
const mongoose = require("mongoose")
const cookieParser = require("cookie-parser")
const TIME = 1000 * 60 * 5
app.use(cookieParser())
app.use(
session({
secret: "iamnamdo1234567",
saveUninitialized: true,
cookie: { maxAge: TIME, sameSite: "strict" },
resave: false
})
)
const URI = process.env.DB_CONNECTION
app.use(express.urlencoded({ extended: true }))
app.use(express.json())
app.use("/api", routers)
app.get("/", (req, res) => {
let session = req.session.userid
session ? res.status(200).send("Hello my friend, you are logged in") : res.status(400).send("You need to log in")
})
mongoose.connect(URI, { useNewUrlParser: true.valueOf(), useUnifiedTopology: true }, err => {
if (err) {
console.log(err)
} else {
console.log("database connected")
}
})
app.listen(PORT, () => {
console.log(`Go to http://localhost:${PORT}`)
})
`
This is the code of my routes.js
`
const express = require("express")
const route = express.Router()
const { User } = require("../models/User")
const bcrypt = require("bcrypt")
const errorHandler = (type, error) => {
if (type === "register") {
if (error.code === 11000) {
return { message: "Username has been taken" }
} else if (error._message === "User validation failed") {
return { message: error.errors.username?.properties.message || error.errors.password?.properties.message }
}
} else if (type === "login") {
return { message: `${error}` }
}
}
route.post("/register", async (req, res) => {
try {
const { username, password } = req.body
const user = await User.create({ username, password })
res.status(200).send("User has been created successfully")
} catch (error) {
// console.log(error)
let message = errorHandler("register", error)
res.status(400).send(message)
}
})
route.post("/login", async (req, res) => {
const { username, password } = req.body
try {
const user = await User.findOne({ username })
if (!user) {
throw (new Error().message = "Username not found")
}
const checkPassword = await bcrypt.compare(password, user.password)
if (checkPassword === false) {
throw (new Error().message = "Password is incorrect")
} else {
req.session.userid = user.username
console.log(req.session.userid)
res.status(200).send("Logged in")
}
} catch (error) {
let message = errorHandler("login", error)
res.status(400).send(message)
}
})
route.post("/logout", (req, res) => {
req.session.destroy()
res.redirect("/")
})
module.exports.routers = route
`
I tried to access the session when making get request from the browser
If the session details are visible in Postman but not in the browser, it could be due to a number of reasons, one of them is Cookie policy.
By default, cookies are only sent with requests made to the same origin as the current page. To send cookies with cross-origin requests, you need to set the withCredentials option in Axios. Try this it worked for me
const axios = require('axios');
axios.defaults.withCredentials = true;
I am using Express and node for the session management with https. I want to create a session using express so that authentication and the session is made before accessing the data. So I want only admin who can access the books, but the session is undefined. here is my code :
AuthUser.js
export const adminOnly = async (req, res, next) => {
if (!req.session.userId) {
return res.status(401).json({ msg: "You must be logged in" });
}
const admin = await Admin.findOne({
where: {
uuid: req.session.userId,
},
});
if (!admin) return res.status(404).json({ msg: "Akses Terlarang" });
next();
};
Auth.js
export const LoginAdmin = async (req, res) => {
const admin = await Admin.findOne({
where: {
username: req.body.username,
},
});
if (!admin) return res.status(404).json({ msg: "Data not found" });
const match = await argon2.verify(admin.password, req.body.password);
if (!match) return res.status(400).json({ msg: "Wrong Password" });
req.session.userId = admin.uuid;
const uuid = admin.uuid;
const username = admin.username;
const role = admin.role;
res.status(200).json({ uuid, username, role });
};
index.js
import express from "express";
import cors from "cors";
import dotenv from "dotenv";
import db from "./config/Database.js";
import {
BookRoute,
BookTypeRoute,
CategoryRoute,
Denda,
PeminjamanRoute,
PengembalianRoute,
UserRoute,
AdminRoute,
} from "./routes/index.js";
import AuthRoute from "./routes/AuthRoute.js";
import SequelizeStore from "connect-session-sequelize";
import session from "express-session";
dotenv.config();
const app = express();
const sessionStore = SequelizeStore(session.Store);
const store = new sessionStore({
db: db,
});
app.use(
session({
secret: process.env.SESS_SECRET,
resave: false,
saveUninitialized: true,
store: store,
cookie: {
secure: "auto",
},
})
);
app.use(
cors({
credentials: true,
origin: "https://localhost:3000",
})
);
app.use(express.json());
app.use(UserRoute);
app.use(AdminRoute);
app.use(BookRoute);
app.use(AuthRoute);
app.use(CategoryRoute);
app.use(BookTypeRoute);
app.use(Denda);
app.use(PeminjamanRoute);
app.use(PengembalianRoute);
// store.sync();
app.listen(process.env.APP_PORT, () => {
console.log("Server is listening on port 5000");
});
I have tried logging console.log('req.session.userId') and it returns undefined value and when I delete if (!req.session.userId) return res.status(401).json({ msg: "You must be logged in" }); the error is Error: WHERE parameter "uuid" has invalid "undefined" value.
I send a cookie to client side but i can't get
In my browser there is no cookies
auth.js:
export const Login = async (req, res, next) => {
try {
const user = await User.findOne({
email: req.body.email,
});
if (!user) return next(createError(404, "User Not Found"));
const CorrectPassword = await bcrypt.compare(
req.body.password,
user.password
);
if (!CorrectPassword)
return next(createError(400, "Wrong password or email"));
const token = jwt.sign({
id: user._id,
isAdmin: user.isAdmin
},
process.env.JWT_SEC
)
const {password, isAdmin, ...others} = user._doc
res.cookie("access_token", token, {
maxAge:900000,
httpOnly:true,
}).status(200).json(others);
} catch (err) {
next(err);
}
};
Login.jsx React:
const handleClick = async (e) => {
e.preventDefault();
dispatch(loginStart());
try {
const res = await axios.post("http://localhost:8800/api/auth/login", {
email,
password,
});
dispatch(loginSuccess(res.data));
navigate("/");
} catch (err) {
dispatch(loginFailure());
console.log(err);
}
};
This is my code
How to fix this?
You have to edit secure option to be false.
secure: true is a recommended option. However, it requires an https-enabled website, i.e., HTTPS is necessary for secure cookies:
app.use(
session({
store: new MongoStore({
mongooseConnection: mongoose.connection,
url: 'mongodb://localhost:27017/test'
}),
secret: "mysecret-ssss",
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 1000 * 60 * 60 * 2,
sameSite: true,
secure: false // in development
}
})
);
I have been building this project from a tutorial. The signup functionality works fine but the login feature doesn't work. Whenever I try logging in a registered user using postman the error I get is
Error: Unknown authentication strategy "local"
In the other posts on stack overflow, I didn't find a solution to this error. Passport, passport-local and passport-jwt are all installed so that shouldn't be the issue. I would really appreciate any sort of help.
passport.js
require('dotenv').config();
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const JWTStrategy = require('passport-jwt').Strategy;
const User = require('./models/User');
// Environment variables
const STRATEGY_KEY = process.env.STRATEGY_KEY;
const cookieExtractor = req => {
let token = null;
// Retrieve the token from cookies
if (req && req.cookies) {
token = req.cookies['access_token'];
}
return token;
};
const jwtOptions = {
jwtFromRequest: cookieExtractor,
secretOrKey: STRATEGY_KEY,
};
// Authorization for protected routes
passport.use(
new JWTStrategy(jwtOptions, (payload, done) => {
User.findById({ _id: payload.sub }, (err, user) => {
// Check for error
if (err) return done(err, false);
// Check if user exists
if (user) return done(null, user);
return done(null, false);
});
})
);
// Local strategy using username and password
passport.use(
new LocalStrategy((username, password, done) => {
User.findOne({ username }, (err, user) => {
// Error while fetching the user from database
if (err) return done(err);
// No such user exists
if (!user) return done(null, false);
// Check if entered password matches
user.comparePassword(password, done);
});
})
);
routes.js
require('dotenv').config();
const express = require('express');
const passport = require('passport');
const router = express.Router();
const STRATEGY_KEY = process.env.STRATEGY_KEY;
const signToken = userID => {
return jwt.sign(
{
iss: STRATEGY_KEY,
sub: userID,
},
STRATEGY_KEY,
{
expiresIn: '1h',
}
);
};
router.post(
'/signin',
passport.authenticate('local', { session: false }),
(req, res) => {
if (req.isAuthenticated()) {
const { _id, username, email } = req.user;
const token = signToken(_id);
res.cookie('access_token', token, {
httpOnly: true,
sameSite: true,
});
res.status(200).json({
isAuthenticated: true,
user: {
username,
email,
},
});
}
}
);
module.exports = router;
So after many hours of debugging, the solution I found to this problem was that I didn't import passport.js file in routes.js file, which I was not expecting since that import stays there ideal not doing anything, not being part of any code(exceot the import) but I was wrong. The passport configuration we make in that file is imported under the hood even though it doesn't take part in any further lines of that file.
I'm using vue, vue-router for my client-side and express, morgan for my server side (MEVN app)
So, at the client i'm setting cookies by using vue-cookies
this.$cookies.set('Login', this.login, new Date(Date.now() + 86400 * 5 * 1000))
this.$cookies.set('Password', this.password, new Date(Date.now() + 86400 * 5 * 1000))
And at the server side i'm using cookieParser
So, at app.js i have such a code
const express = require('express');
const morgan = require('morgan');
const cors = require('cors');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const config = require('./config/config');
const db = require('./controllers/DB');
const mCLogs = require('./modelControllers/Logs');
const mCLogin = require('./modelControllers/Login');
const app = express();
app.use(morgan('combined'));
app.use(bodyParser.json());
app.use(cors());
app.use(cookieParser()); /*cookie parser*/
And, at the file ./modelControllers/Login i have such a code for a GET request
exports.checkLoginSession = async (req, res, next) => {
/*its not all of the code*/
var loginHash = req.cookies['Login'];
console.log(loginHash)
if(loginHash == undefined) {
res.send({
logged: false,
description: "err mes"
});
} else {
res.send({
logged: true,
description: "mes"
});
}
}
and the problem is that the var loginHash = req.cookies['Login']; always return undefined, even when i have "Login" cookie
Addition:
How i call this method:
Client-side and using axios
mounted () {
this.getLoginData()
},
methods: {
async getLoginData () {
const response = await LoginHandler.checkUserLoginSession()
if (response.data.logged === true) {
this.$router.push('/')
} else {
this.errorMessage = response.data.description
}
}
}
LoginHandler.js(client side)
import api from '#/services/api'
export default {
checkUserLoginSession () {
return api().get('/login')
}
}
Server-side /login link in app.js
app.get('/login', mCLogin.checkLoginSession);
app.post('/login', mCLogin.checkUserData);
ADDITION:
It doesnt work when i use such a code with axios API:
import api from '#/services/api'
export default {
checkUserLoginSession () {
return api().get('/login')
}
}
So, when i call checkUserLoginSession app.get('/login') return cookie value undefined, but, if i open link in browser (serverside) localhost:3000/login it's returning correct value
Addition: checkUserData
exports.checkUserData = async (req, res) => {
try {
let login = req.body.login;
let password = req.body.password;
const user = await db.users.findOne({
where: {
Login: login,
Password: password
}
});
if(user == null)
{
res.send({
logged: false,
description: "Пользователь не найден."
});
return;
}
if(user.dataValues.Login == login && user.dataValues.Password == password)
{
res.send({
logged: true,
description: "Авторизация произошла успешно. Сейчас Вас перенаправит!"
});
return;
}
}
catch(ex) {
res.send({
logged: false,
description: "Произошла ошибка на стороне сервера."
});
console.log(ex);
return;
}
}
If i add withCredentials: true to axios.create, server return cookie value, but i've this errors on console line
Access to XMLHttpRequest at 'http://localhost:3000/login' from origin 'http://localhost:8080' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
Ok guys, i solve my issue.
So, the answer is.
Change LoginHandler code:
From:
import api from '#/services/api'
export default {
checkUserLoginSession () {
return api().get('/login')
}
}
To:
import api from '#/services/api'
export default {
checkUserLoginSession () {
return api().get('/login', {withCredentials: true})
}
}
Change app.js
From:
app.use(cors());
To:
app.use(cors({ credentials: true, origin: "http://localhost:8080" }));
Change method checkLoginSession
To:
exports.checkLoginSession = (req, res, next) => {
const { Login, Password } = req.cookies;
//Where Login, Password ... is your cookie name
//console.log(Login)
if(Login == undefined) {
res.send({
logged: false,
description: "Нет сохранённых хешей для авторизации!"
});
} else {
res.send({
logged: true,
description: "Авторизован."
});
}
}
P.S Thanks to all, who tried to help me