I have the following auth route in the backend of my API:
router.post('/', async (req, res) => {
const { error } = validate(req.body);
if (error) return res.status(400).send(error.details[0].message);
let user = await User.findOne({ username: req.body.username });
if (!user) return res.status(400).send('Invalid username or password.');
const validPassword = await bcrypt.compare(req.body.password, user.password);
if (!validPassword) return res.status(400).send('Invalid username or password.');
const token = user.generateAuthToken();
res.header('Access-Control-Expose-Headers', 'X-OBSERVATORY-AUTH');
res.header('X-OBSERVATORY-AUTH', token).send(_.pick(user, ['_id', 'email', 'username','isAdmin']));
});
And in the frontend i have the following handler for the login:
login( username: string, password: string) {
var user: User = { username: username, password: password };
this.http
.post<any>("http://localhost:3000/api/auth",user, {observe:'response'})
.subscribe((res) => {
const token = res.headers.get('X-OBSERVATORY-AUTH');
this.token = token;
console.log(res.headers.get('X-OBSERVATORY-AUTH'));
if (token!==null) {
this.isAuthenticated = true;
this.userId = res.body._id;
this.isAdmin=res.body.isAdmin;
this.authStatusListener.next(true);
this.saveAuthData(token, this.userId, this.isAdmin);
}
});
}
What I am trying to do is to pass the X-OBSERVATORY-AUTH in the response header but I cant figure how to expose the token from the backend. What should I do?
Related
Hello i am trying to use my token in my application after user is logged in but am getting an undefined response in my console. Below are my codes. How can i correct my code to be able to access token inside application and use to do other features of the application?
my controller
import User from "../models/user";
import Stripe from "stripe";
const stripe = Stripe(process.env.STRIPE_SECRET);
export const createConnectAccount = async (req, res) => {
console.log(req.user);
try {
const user = await User.findById(req.user._id).exec();
console.log("USER ==> ", user);
if (!user.stripe_account_id) {
const account = await stripe.accounts.create({
type: "express",
});
console.log("ACCOUNT ===>", account);
user.stripe_account_id = account.id;
user.save();
}
} catch (error) {
res.status(500).json();
}
};
my middleware
var { expressjwt: jwt } = require("express-jwt");
// req.user
export const requireSignin = jwt({
//secret, expiryDate
secret: process.env.JWT_SECRET,
algorithms: ["HS256"],
});
my routes
import express from "express";
const router = express.Router();
import { requireSignin } from "../middlewares";
import { createConnectAccount } from "../controllers/stripe";
router.post("/create-connect-account", requireSignin, createConnectAccount);
module.exports = router;
my auth controller
import User from "../models/user";
import jwt from "jsonwebtoken";
export const register = async (req, res) => {
console.log(req.body);
const { name, email, password } = req.body;
if (!name) return res.status(400).send("Name is required");
if (!password || password.length < 6)
return res
.status(400)
.send("Password is required and should be minimum 6 characters long");
let userExist = await User.findOne({ email }).exec();
if (userExist) return res.status(400).send("Email is taken");
const user = new User(req.body);
try {
await user.save();
console.log("User saved successfully", user);
return res.json({ ok: true });
} catch (err) {
console.log("CREATE USER FAILED", err);
return res.status(400).send("Error.Try again");
}
};
export const login = async (req, res) => {
// console.log(req.body);
const { email, password } = req.body;
try {
//check if user with credentials
let user = await User.findOne({ email }).exec();
// console.log("USER EXISTS", user);
if (!user) res.status(400).send("User with email not found");
//compare password
user.comparePassword(password, (err, match) => {
console.log("COMPARE PASSWORD IN LOGIN ERR", err);
if (!match || err) return res.status(400).send("Wrong password");
//("GENERATE A TOKEN THEN SEND AS RESPONSE TO CLIENT");
let token = jwt.sign({ _id: user._id }, process.env.JWT_SECRET, {
expiresIn: "7d",
});
res.json({
token,
user: {
_id: user._id,
name: user.name,
email: user.email,
createdAt: user.createdAt,
updatedAt: user.updatedAt,
},
});
});
} catch (err) {
console.log("LOGIN ERROR", err);
res.status(400).send("Signin failed");
}
};
my terminal output
POST /api/login 200 1142.309 ms - 349
undefined
POST /api/create-connect-account 500 9.092 ms - -
Headers
import axios from "axios";
export const createConnectAccount = async (token) => {
await axios.post(
`${process.env.REACT_APP_API}/create-connect-account`,
{},
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
};
I'm sorry to tell you your code has other errors in it.
My guess is that your res is not well written in auth controller, login function :
res.status(201).json({
token :token,
user: user
})
Also when reading your token trying to authenticate : it will be easier to use the same package than the one that sign it.
const jwt = require("jsonwebtoken");
exports. requireSignin = () => {
return async (req, res, next) => {
try {
const token = req?.headers?.authorization?.split(" ")[1];
const decodedToken = jwt.verify(token, process.env.JWT_SECRET);
const userId = decodedToken._id;
const user = await User.findOne({ _id: userId });
if (user) {
req.auth = {
user: user,
};
} else {
throw new Error("user not found");
}
next();
} catch (error) {
console.log(error.message);
res.status(401).json({ error: "failed to authenticate" });
}
};
};
But your code is pretty hard to read :
To make it easier to read and clearer for you, try and use joy or yup
Joi : https://www.npmjs.com/package/joi
Yup : https://www.npmjs.com/package/yup
With those you will be able to create middlewares to avoid wrong entries in your body : for example
if (!name) return res.status(400).send("Name is required");
is processed automatically with those packages
Also, you shouldn't use 'import' and 'require' in the same project, choose either one of them
I hope this will help
I am testing on postman with passport-jwt, I got my user's token, and try to test protected routes with passport-jwt, following my code, I am supposed to get "hello", but somehow still not allow me to authorize, and it keep gives me "401 unauthorized", can anyone help if my code is wrong? thanks!
on my postman, i have attached authorization, and Bearer token under header.
My middleware:
const jwt = require("jsonwebtoken");
const User = require("../models/user");
const { signupUser, loginUser } = require("../controller/auth");
const passport = require("passport");
const JwtStrategy = require("passport-jwt").Strategy,
ExtractJwt = require("passport-jwt").ExtractJwt;
const opts = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: "process.env.JWT_SECRET",
};
module.exports = (passport) => {
passport.use(
new JwtStrategy(opts, async (payload, done) => {
await User.findById(payload.id)
.then((user) => {
if (user) {
return done(null, user);
}
return done(null, false);
})
.catch((err) => {
console.log(err);
return done(null, false);
});
})
);
};
the route file:
const express = require("express");
const router = express.Router();
const { userAuth, signupUser, loginUser } = require("../controller/auth");
router.get("/category/getcategory", userAuth, async (req, res) => {
return res.json("hello");
});
module.exports = router;
My controller file:
const User = require("../models/user");
const jwt = require("jsonwebtoken");
const bcrypt = require("bcrypt");
const passport = require("passport");
exports.loginUser = async (req, role, res) => {
const { email, password } = req;
try {
const user = await User.findOne({ email });
if (!user)
return res.status(400).json({ message: "User does not exists." });
const isPasswordCorrect = await bcrypt.compare(password, user.password);
if (!isPasswordCorrect)
return res.status(400).json({ message: "Invalid credentials." });
if (!user.role == role) {
return res.status(403).json({ message: "please check the right portal" });
}
const payload = {
email: user.email,
id: user._id,
role: user.role,
};
const token = jwt.sign(payload, process.env.JWT_SECRET, {
expiresIn: "1h",
});
let result = {
mail: user.email,
id: user._id,
role: user.role,
token: `Bearer ${token}`,
expiresIn: 168,
};
res.status(200).send({ ...result, Message: "Now you are logged in!" });
} catch (error) {
console.log(error);
}
};
exports.signupUser = async (req, role, res) => {
const { firstName, lastName, email, password, confirmPassword } = req;
try {
const user = await User.findOne({ email });
if (user) return res.status(400).json({ message: "User already exists." });
if (!password == confirmPassword)
return res.status(400).json({ message: "Password don't match" });
const hashedPassword = await bcrypt.hash(password, 12);
const result = await User.create({
email,
password: hashedPassword,
firstName,
lastName,
role,
});
const token = jwt.sign(
{ email: result.email, id: result._id },
process.env.JWT_SECRET,
{ expiresIn: "1h" }
);
res.status(200).json({ result, token });
} catch (error) {
console.log(error);
}
};
exports.userAuth = passport.authenticate("jwt", { session: false });
node js
This is my register method to register a user. I am trying to pass token in headers when a user is registered which will be used in the front end to access the token and store it in the local storage.
module.exports.register = async function (req, res, next) {
try {
const { username, email, password } = req.body;
const profileImage = req.file.path;
const usernameCheck = await User.findOne({ username });
if (usernameCheck)
return res.json({ msg: "Username already used", status: false });
const emailCheck = await User.findOne({ email });
if (emailCheck)
return res.json({ msg: "Email already exists", status: false });
const hashedPassword = await bcrypt.hash(password, 10);
const user = await User.create({
_id: new mongoose.Types.ObjectId(),
username,
email,
profileImage,
password: hashedPassword,
});
delete user.password;
//create jwt token
const token = jwt.sign(
{
username: user.username,
email: user.email,
userId: user._id,
},
process.env.JWT_KEY,
{
expiresIn: "1h",
}
);
res.header("x-auth-token", token); //This is not setting the token in headers
return res.json({
message: "User Created Successfully",
status: true,
user,
});
} catch (ex) {
next(ex);
}
};
react js
This is my front-end react code to register a user. I want to login the user with the jwt token stored in localStorage once the user is registered.
const handleSubmit = async (values) => {
try {
const { username, email, profileImage, password } = values;
const formData = new FormData();
formData.append("username", username);
formData.append("email", email);
formData.append("profileImage", profileImage);
formData.append("password", password);
const response = await register(formData);
console.log(response);
if (response.status === false) return;
else {
loginWithJwt(response.headers["x-auth-token"]);// log the user in using jwt token
console.log(response.headers["x-auth-token"]);
navigate("/chatroom");
}
} catch (ex) {
console.log(ex.message);
}
};
So I create my route user and I use this tutorial https://www.bezkoder.com/node-js-jwt-authentication-mysql/ to add the token and Authentication Token + validation name. Everything work great. But then when I beginning to create my route Comment (acces for every user), I had normaly the auth but even with the token Postman send me an "No token provided!". I have to add this token and autorize the acces but I don't know how. If somebody know how to do it, it would be great :D thx ! This is how I add my token on Postman
enter image description here
Here is my code:
My route comment:
const express = require("express");
const router = express.Router();
const commentCtrl = require("../controllers/comment");
const { authJwt } = require("../middleware");
router.post("/upload", [authJwt.verifyToken], commentCtrl.createComment);
module.exports = router;
The middleware token :
const jwt = require("jsonwebtoken");
const config = require("../config/auth.config.js");
verifyToken = (req, res, next) => {
let token = req.headers["x-access-token"];
if (!token) {
return res.status(403).send({
message: "No token provided!"
});
}
jwt.verify(token, config.secret, (err, decoded) => {
if (err) {
return res.status(401).send({
message: "Unauthorized!"
});
}
req.id = decoded.id;
next();
});
};
const authJwt = {
verifyToken: verifyToken,
};
module.exports = authJwt;
const authJwt = require("./authJwt");
const verifySignUp = require("./verifySignUp");
module.exports = {
authJwt,
verifySignUp
};
My verification SignUp:
const { User } = require("../models");
checkDuplicateEmail = async (req, res, next) => {
const user = await User.findOne({
where: {
email: req.body.email
}
}).then(user => {
if (user) {
res.status(400).send({
message: "Failed! Email is already in use!"
});
return;
}
next();
});
};
const verifySignUp = {
checkDuplicateEmail: checkDuplicateEmail
};
module.exports = verifySignUp;
And here is my user.controllers when is send the token:
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const config = require("../config/auth.config");
const { User } = require("../models");
module.exports.signup = async (req, res) => {
if (!req.body.email || !req.body.password) {
res.status(400).send({
status: false,
message: 'une erreur dans create est survenu'
});
} else {
let { nom, prenom, email, password, status} = req.body;
const salt = await bcrypt.genSalt(10);
password = await bcrypt.hash(password, salt)
const user = await User.create({
nom, prenom, email, password, status
})
.then((user) => res.status(201).send(user)).catch((error) => {
console.log(error);
res.status(400).send(error);
});
}
};
module.exports.login = async (req, res) => {
const user = await User.findOne({
where: {
email: req.body.email
}
})
.then(user => {
if (!user) {
return res.status(404).send({ message: "User Not found." });
}
const passwordIsValid = bcrypt.compareSync(req.body.password, user.password);
if (!passwordIsValid) {
return res.status(401).send({
accessToken: null,
message: "Invalid Password!"
});
}
var token = jwt.sign({ id: user.id }, config.secret, {
expiresIn: 86400 // 24 hours
});
res.status(200).send({
id: user.id,
nom: user.nom,
email: user.email,
password: user.password,
accessToken: token
});
})
.catch(err => {
res.status(500).send({ message: err.message });
});
};
While it would have helped if you had provided a screenshot of where you are putting your token on postman, make sure you are using the appropriate header. According to your code, you are searching the 'x-access-token' header, so in postman, you should attach your jwt token to the same header property. It should be something like this:
postman x-access-token demo
I have an API in ExpressJS. Within that API I have a login endpoint, when posting to that endpoint however I keep getting the exception that headers cannot be set after they have been sent.
I understand this is normally a callback that is being called twice or not properly returning from something that has set headers, causing the app to attempt to set them again, however in my /login endpoint I am not doing this.
I cannot understand why this happening, I would love some input as to why as I am close to pulling my hair out reading the same replies and answers. I hope it is something obvious I am missing.
import User from '../../models/user';
import { Router } from 'express';
import jwt from 'jsonwebtoken';
export default () => {
const route = Router();
route.post('/create', async (req, res, next) => {
if (!req.body.email || !req.body.password) {
return res
.status(400)
.json({ message: 'username or password is missing' });
}
const { email, password } = req.body;
const count = await User.count({ email });
if (count > 0) {
return res.status(409).json({ message: 'email must be unique' });
}
const newUser = await new User({ email, password });
const doc = await newUser.save();
return res.status(201).json({ type: 'account', attributes: doc });
});
route.post('/login', async (req, res, next) => {
if (req.body.email && req.body.password) {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (user) {
user.comparePassword(password, isMatch => {
if (isMatch) {
const token = jwt.sign(
{ sub: user.id, roles: [], email: user.email },
process.env.SECRET_KEY,
{ expiresIn: '12h' },
);
return res
.status(200)
.json({ type: 'account', attributes: { token } });
}
});
}
}
res.sendStatus(401);
});
return route;
};
import User from '../../models/user';
import { Router } from 'express';
import jwt from 'jsonwebtoken';
export default () => {
const route = Router();
route.post('/create', async (req, res, next) => {
if (!req.body.email || !req.body.password) {
return res
.status(400)
.json({ message: 'username or password is missing' });
}
const { email, password } = req.body;
const count = await User.count({ email });
if (count > 0) {
return res.status(409).json({ message: 'email must be unique' });
}
const newUser = await new User({ email, password });
const doc = await newUser.save();
return res.status(201).json({ type: 'account', attributes: doc });
});
route.post('/login', async (req, res, next) => {
if (req.body.email && req.body.password) {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (user) {
return user.comparePassword(password, isMatch => {
if (isMatch) {
const token = jwt.sign(
{ sub: user.id, roles: [], email: user.email },
process.env.SECRET_KEY,
{ expiresIn: '12h' },
);
return res
.status(200)
.json({ type: 'account', attributes: { token } });
} else {
return res.status(400)
.json({ message: 'username or password is invalid' });
}
});
}
}
res.sendStatus(401);
});
return route;
};
Have a look at the updated code return was missing at
return user.comparePassword(password, isMatch => {
Hope it'll fix your issue.
The problem in here. Your callback in comparePassword return only inside that callback. So the code still run to res.sendStatus(401) and after the callback is done it will run res.status(200).json...
user.comparePassword(password, isMatch => {
if (isMatch) {
const token = jwt.sign(
{ sub: user.id, roles: [], email: user.email },
process.env.SECRET_KEY,
{ expiresIn: '12h' },
);
return res
.status(200)
.json({ type: 'account', attributes: { token } });
}
});
Try to promisify comparePassword method in the user model:
userSchema.methods.comparePassword = function (password) {
return new Promise( function(resolve, reject) {
resolve(password === this.password);
});
}
Now you can use await syntax to get the promise result:
route.post('/login', async (req, res, next) => {
if (req.body.email && req.body.password) {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (user) {
const isMatch = await user.comparePassword(password);
if (isMatch) {
const token = jwt.sign(
{ sub: user.id, roles: [], email: user.email },
process.env.SECRET_KEY,
{ expiresIn: '12h' },
);
return res
.status(200)
.json({ type: 'account', attributes: { token } });
}
}
}
res.sendStatus(401);
});