I have created a login page and a about page the user will only access the about page if the user is logged in.
I am trying to authenticate the user by using the tokens generated while signing in, but the token is not getting authenticated even after signing in with the correct credentials. I don't know what is the problem?
This is code to my sign-in and token generating method
const express = require("express");
const { default: mongoose } = require("mongoose");
const router = express.Router();
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
require("../db/conn");
const User = require("../model/userSchema");
const cookieParser = require('cookie-parser');
const Authenticate = require("../middleware/authenticate");
router.use(cookieParser());
//LOgin route
router.post("/signin", (req, res)=>{
if(!req.body.email || !req.body.password){
return res.status(400).json({error: "Plz fill the required data"});
}else{
bcrypt.hash(req.body.password, 12, function (err, hash) {
User.findOne({email: req.body.email}, function (err, foundUser) {
if(err){
console.log(err);
}else{
if(foundUser){
bcrypt.compare(req.body.password, foundUser.password, function (err, result) {
if(result){
return res.json({message: "successfully log in"})
}else{
return res.json({message: "incorrect password"});
}
});
const email = req.body.email;
const token = jwt.sign(
{ user_id: foundUser._id, email },
process.env.TOKEN_KEY,
{
expiresIn: "720h",
}
);
foundUser.tokens = foundUser.tokens.concat({token: token});
foundUser.save();
// res.status(200).json(foundUser);
console.log(foundUser);
}else{
return res.status(400).json({message: "user not found"});
};
}
})
})
}
});
//about us page
router.get("/about", Authenticate, function (req, res) {
console.log("about running");
res.send(req.rootUser);
});
module.exports = router;
this is the code to authenticate the user
require("dotenv").config({path: "./config.env"});
const jwt = require("jsonwebtoken");
const User = require("../model/userSchema");
const Authenticate = async(req, res, next) =>{
try {
const token = req.cookies.jwtoken;
const verifyToken = jwt.verify(token, process.env.TOKEN_KEY);
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 (err) {
console.log(err);
return res.status(401).send("Unauthorized: No token provided");
}
}
module.exports = Authenticate;
This is react based code of: About-page to display it or not based on user's authenticity.
const navigate = useNavigate();
const callAboutPage = async() =>{
try {
const res = await fetch("/about",{
method: "GET",
headers: {
Accept: "application/json",
"Content-Type" : "application/json"
},
credentials: "include"
});
const data = await res.json();
console.log(data);
if(!res.status === 200){
const error = new Error(res.error);
throw error;
}
} catch (err) {
console.log(err);
navigate("/login");
}
}
As said in the comment looks like there is a issue on the process for setting up the jwtoken, and when you sign in, you just need to find the user and compare the password, there is no need to do the hash with Bcrypt, since you're not registing new user, for example, i will use Async/await instead of callback function, in order for you to read it much more easier:
//Login route
router.post("/signin", async (req, res)=> {
const { reqEmail, reqPassword } = req.body; //destructuring so less thing to write at the next step
if(!reqEmail || !reqPassword) {
return res.status(400).json({message: "Plz fill the required data"});
}
try {
const foundUser = await User.findOne({email: reqEmail})
if(!foundUser) {
return res.status(400).json({message: "Wrong username or password!"})
}
const result = await bcrypt.compare(reqPassword, foundUser.password);
if(!result){
return res.json({message: "Wrong username or password!"})
} else {
const accessToken = jwt.sign(
{ user_id: foundUser._id, email: foundUser.email},
process.env.TOKEN_KEY,
{ expiresIn: "720h",}
);
// I am confuse what are you trying to do here, in your place I would set up on the cookie since you do that on your authentification.
res.cookie("jwt", accessToken, {
maxAge: 60000, // 60 sec for testing
httpOnly: true,
sameSite: false, //false only for dev
secure: false, //false only for dev
})
res.status(200).json(foundUser);
};
} catch (error) {
return res.status(500).json({message: `${error}`})
}
Than the authentification middleware :
// ...
const Authenticate = (req, res, next) => {
const accessToken = req.cookies.jwt
if(!accessToken) {
return res.status(401).json({error: "Unauthorized: No token provided"});
}
try {
const user = jwt.verify(accessToken, process.env.TOKEN_KEY)
if(user) {
req.user = user
return next();
}
} catch (error) {
return res.status(403).json({error: "Forbidden token error"})
}
}
about page component it's simple for now since you don't manage any state
const navigate = useNavigate();
const callAboutPage = async() =>{
try {
const res = await fetch("/about",{
headers: {
"Content-Type": "application/json"
},
credentials: "include"
});
if(res.status === 200){
const data = await res.json();
// set up the state for rendering
console.log(data);
} else {
// you can also create a state to catch the error message from the backend, in this case the response json should be move to above the if statement.
throw new Error("You must log in to get access")
// than you can display this error message, or from the backend using state for this bloc, and the catch bloc
// navigate to /login
}
} catch (err) {
console.log(err);
navigate("/login");
}
}
router.use(cookieParser());
Try to use cookieParser with app.use instead. (app from express instense)
Expample:
const app = express();
app.use(cookieParser());
and try to put it before server listening in index.js or app.js file.
Hope it help.
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'm trying to create a platform, once I log in it, creates a token and store it in the cookie. I have successfully been able to create a route that stores the cookie using node js(I could see it saved in postman). But once I try to use the route in my flutter app, it seems the the token doesn't save anywhere.
How do I save the cookie and use it to validate if the user should be logged in or should log in again
Login.dart
Future<void> _signIn(email, password) async {
try {
setState(() {
LoadingWidget.show(context);
bool _absorbme = true;
});
var url = "http://192.168.8.23:3000/route/login"; // iOS
final http.Response response = await http.post(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'email': email.toLowerCase(),
'password': password,
}),
);
SharedPreferences prefs = await SharedPreferences.getInstance();
var parse = jsonDecode(response.body);
await prefs.setString('msg', parse["msg"]);
await prefs.setString('success', parse["success"]);
String? msg = prefs.getString("msg");
} finally {
setState(() {
LoadingWidget.hide(context);
bool _absorbme = false;
});
}
}
My Route.js file
const express = require('express');
const router = express.Router();
const Users = require('./model/model')
const passport = require('passport')
const bcrypt = require("bcrypt");
const { createTokens, validateToken, authenticateToken } = require("./JWT");
const Server = require('http-proxy');
router.get('/', (req, res)=>{
res.send('Hello world')
})
router.post("/signup", async (req, res) => {
const { username, email, password } = req.body;
let user = await Users.findOne({ email });
if (user){
return res.status(400).json({success: 'false', msg: "Email Already Exist"});
}
bcrypt.hash(password, 10).then((hash) => {
Users.create({
username: username,
password: hash,
email:email,
})
.then(() => {
res.json({success: 'true', msg: 'login Successfully'});
})
.catch((err) => {
if (err) {
res.status(400).json({success: 'false', msg: 'Failed to save'});
}
});
});
});
router.post("/login", async (req, res) => {
const { email, password } = req.body;
let user = await Users.findOne({ email });
if (!user) {
return res.status(400).json({success: 'false', msg: 'Authentication Failed, User not found'});
}
const dbPassword = user.password;
bcrypt.compare(password, dbPassword).then((match) => {
if (!match) {
res
.status(400)
.json({success: 'false', msg: 'Authentication failed, wrong password'});
} else {
const accessToken = createTokens(user);
var token = res.cookie("access-token", accessToken, {
maxAge: 60 * 60 * 24 * 30 * 1000,
httpOnly: true,
});
res
.status(200)
.json({success: 'true', msg: 'Successfully logged In'});
}
});
});
router.get("/profile", validateToken, async (req, res) => {
let user = await Users.findOne({email: req.decoded.email});
return res.json({
email: user.email,
balance: user.balance,
name: user.username
})
router.get("/logout", validateToken, (req, res) => {
return res
.clearCookie("access-token")
.status(200)
.json({success: "true" , msg: "Successfully logged out" });
});
router.get("/authenticate" ,authenticateToken, (req,res)=>{
})
module.exports = router;
enter image description here
Why not use JWT it can help you manage your users for the numbers of hours you specified
Create authorize endpoint on your api to refresh token when expired.
In dart side, create an authenticated client singleton.
class AuthenticatedClient extends http.BaseClient {
factory AuthenticatedClient() => _instance;
AuthenticatedClient._();
static final _instance = AuthenticatedClient._();
int? expiresAt;
String? token;
final _client = http.Client();
void setParams(http.Response res) {
final response = jsonDecode(res.body);
expiresAt = response['expiresAt'];
token = response['token'];
}
Future<void> authorize() async {
// Send a request to refresh token, update token and expiresAt
// accordingly. Note that, you can't use _client to send request, use
// htt.put, http.post.
// Ex:
//
// final response = await http.put(
// Uri.parse('https://myapi.com/authorize'),
// headers: {
// 'Content-Type': 'application/json',
// },
// body: jsonEncode({
// 'email': <email>,
// 'password': <password>,
// }),
// );
//
// setParams(response);
}
#override
Future<http.StreamedResponse> send(http.BaseRequest request) async {
// Ensure you set params for the first time.
assert(expiresAt != null);
if (expiresAt! < DateTime.now().millisecondsSinceEpoch) {
await authorize();
}
request.headers['Authorization'] = 'Bearer $token';
return _client.send(request);
}
}
On your api, generate a token on login or signup request and send token to client. For this example, response format is that:
{
'token': <generated token>,
'expiresAt': <timestamp>,
}
When you got response, call setParams method of AuthenticatedClient and set request params. I didn't add code for error handling to the example, don't forget to add.
Use AuthenticatedClient like this:
AuthenticatedClient().get(Uri.parse('https://myapi/profile'));
I'm doing one of my first exercises on authorization, but I can't understand what I'm doing wrong with this one. I'm trying to validate the JWT token, but it gives me back that it's invalid
I get the token with httpie:
http POST :4000/auth/login email=test#test.com password=test
And then I try validating with httpie:
http GET :4000/images authorization:"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTY0ODgyMDI3MCwiZXhwIjoxNjQ4ODI3NDcwfQ.AhArOvQTaJ7ohbPiyGiGTK5pFMWqjbZ5Kj9q2hXEhXU"
Which gives me: Invalid JWT token
This is my router/images.js
const { Router } = require("express");
const Image = require("../models").image;
const router = new Router();
const toData = require("../auth/jwt");
router.get("/images", async (req, res) => {
const auth =
req.headers.authorization && req.headers.authorization.split(" ");
if (auth && auth[0] === "Bearer" && auth[1]) {
try {
const data = toData(auth[1]);
const allImages = await Image.findAll();
res.send(allImages);
} catch (error) {
res.status(400).send("Invalid JWT token");
}
} else {
res.status(401).send({ message: "Please supply valid credentials" });
}
});
This is the router/auth.js
const { Router } = require("express");
const { toJWT, toData } = require("../auth/jwt");
const router = new Router();
router.post("/auth/login", async (req, res, next) => {
try {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).send("Email and password required");
} else {
res.send({ jwt: toJWT({ userId: 1 }) });
}
} catch (error) {
console.log(error.message);
next(error);
}
});
module.exports = router;
This is the auth/jwt.js
const jwt = require("jsonwebtoken");
const secret =
process.env.JWT_SECRET || "e9rp^&^*	sejg)DSUA)jpfds8394jdsfn,m";
const toJWT = (data) => {
return jwt.sign(data, secret, { expiresIn: "2h" });
};
const toData = (token) => {
return jwt.verify(token, secret);
};
module.exports = { toJWT, toData };
Hope somebody can help :)
Inside your router/images.js file, it looks like you are not requiring the toData method correctly. You are requiring the module with two exported functions, so if you use destructuruing, you would have to access your toData method like you did inside of auth.js.
Change line 4 of router/images.js to:
const { toData } = require("../auth/jwt");
When you require the auth/jwt file with a single const that is not destructured, you would access the exported methods of that file with dot notation:
const jwt = require("../auth/jwt");
jwt.toData(auth[0]);
I have a auth.js file And a middleware named as fetchuser code given beolow
Can anyone please tell me why am i getting this error.
I am using express js and mongoose but this error is occured during sending token to the user and verify the user whether is user logged in or not.
auth.js
const express = require('express');
const User = require('../models/User');
const router = express.Router();
const { body, validationResult } = require('express-validator');
const bcrypt = require('bcryptjs'); // it is used for password hashing
const jwt = require('jsonwebtoken');
const fetchuser=require('../middleware/fetchuser');
// Route:1 - Create a User using :POST. "/api/auth/createuser". NO Login Required.
router.post('/createuser', [
body('email', 'Enter valid email').isEmail(),
body('name', 'Enter valid email').isLength({ min: 3 }),
body('password').isLength({ min: 5 })
], async (req, res) => {
// Check fo vaidation whether is any rule(defined in User model) breaked or not
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Check Whether user with same email id exist or not
try {
let user = await User.findOne({ email: req.body.email });
if (user) {
return res.status(400).json({ error: "Sorry user with same email id already exist" });
}
// hashing of password
const salt = await bcrypt.genSalt(10);
const securePassword = await bcrypt.hash(req.body.password, salt);
// create A new User
user = await User.create({
name: req.body.name,
email: req.body.email,
password: securePassword
})
// returning user id in Token
const JWT_secret = "Rishiisa#boy";
const data = { user:{id: user.id} };
const auth_token = jwt.sign(data, JWT_secret);
res.json({ auth_token });
}
catch (error) {
console.error(error.message);
res.status(500).send("Internal server error");
}
})
// Route:2 - Login a User using credential. "/api/auth/login". NO Login Required.
router.post('/login', [
body('email', 'Enter valid email').isEmail(),
body('password', 'password can not be blank').exists(),
], async (req, res) => {
// Check for vaidation according to the rule defined at line no. 53, 54;
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// destructure the email and password from body request
const { email, password } = req.body;
try {
// Checking whether email is exist or not
let user = await User.findOne({ email });
if (!user) {
return res.status(400).json({ error: "Please try to login using correct credentials" });
}
// Now Comparing password with help of bcryptjs
const comparepassword = await bcrypt.compare(password, user.password);
if (!comparepassword) {
return res.status(400).json({ error: "Please try to login using correct credentials" });
}
// Now if user enter coorect password and login then user got logged in;
// And We will send authtoken to user;
// returning user id in Token
const JWT_secret = "Rishiisa#boy";
const data = { user:{id: user.id} };
const auth_token = jwt.sign(data, JWT_secret);
res.json({ auth_token });
}
catch (error) {
console.error(error.message);
res.status(500).send("Internal server error");
}
})
// Route:3 - Get Loggedin User details using:POST "/api/auth/getuser" Login required
router.post('/getuser', fetchuser, async (req, res) => {
try {
const userid = req.user.id;
const user = await User.findById(userid).select("-password");
res.send(user);
} catch (error) {
console.error(error.message);
res.status(500).send("Internal server error");
}
})
module.exports = router
middleware:
fetchuser.js
const jwt = require('jsonwebtoken');
const JWT_secret = "Rishiisa#boy";
const fetchuser = (req, res, next) => {
// Get the user from jwt token and add user id to req object
const token = req.header('auth_token');
if (!token) {
res.status(401).send({ error: "Please authenticate using a valid token" });
}
try {
const data = jwt.verify(token, JWT_secret);
req.user = data.user;
next();
} catch (error) {
res.status(401).send({ error: "Please authenticate using a valid token" });
}
}
module.exports = fetchuser;
In auth.js, where you wrote: "const data = { user:{id: user.id} };" Try changing user.id to user._id, since in MongoDB the user id is referred to as '_id'.
Let me know if that works.
I've had problems sending jwt token back and even verifying it, but all is good on my side now.
Also, below is my (inspired) method of going about this:
router.post('/register', (req, res)=>{
const { username, password } = req.body;
const user = new User({
username,
password
});
bcrypt.genSalt(10, (err, salt)=>{
bcrypt.hash(user.password, salt, (err, hash)=>{
if(err) throw err;
user.password = hash;
user.save()
.then(user=>{
jwt.sign(
{ id: user._id },
process.env.jwtSecret,
{ expiresIn: 3600 },
(err, token) =>{
if(err) throw err;
res.status(200)
}
)
})
})
})
})
I am trying to run a delete request but it is not working, I have used the exact same logic on another project exactly like it and it works there.
Here is the route file which includes the delete request as well as the post request that does indeed work
const express = require("express");
const router = express.Router();
const User = require("../models/users");
const cardSchema = require("../models/card");
//add card request
router.post("/:id/addcard", getUser, async (req, res) => {
try {
if (req.body != null) {
const newCard = new cardSchema({
name: req.body.name,
cardNumber: req.body.cardNumber,
ccv: req.body.ccv,
expiration: req.body.expiration,
});
res.user.cardInfo.push(newCard);
}
const updatedCardInfo = await res.user.save();
return res.status(200).json(updatedCardInfo);
} catch (error) {
return res.status(400).json({ message: error.message });
}
});
//delete card request
router.delete("/:id/deletecard", getUser, async (req, res) => {
if (req.body !== null) {
res.user.cardInfo.remove(req.body);
}
try {
const updatedUser = await res.user.save();
res.status(200).json(updatedUser);
} catch (error) {
res.status(400).json({ message: error.message });
}
});
//get user middleware
async function getUser(req, res, next) {
let user;
try {
user = await User.findById(req.params.id);
if (user == null) {
return res.status(404).json({ message: "Cannot find user" });
}
} catch (error) {
return res.status(500).json({ message: error.message });
}
res.user = user;
next();
}
module.exports = router;
I have triple checked that I am using the correct URL and passing in the correct information in the req.body. I recieved the users information after calling the delete request but just does not remove the card information. I have also checked in my database that it is 'cardInfo' so there is no spelling mistake there either.