I have a basic MERN login/signup app with a dummy protected route to be visible only to logged in users. I have set up everything related to login and sign up.
I am sending the jwt token from server in a cookie. The aprt up to this works fine. I can sign up and I can login.
But when I make a GET call to /protected route, passport.jwt doesn't seem to work. Nothing happens in there.
Here is my passport.js config:
import {Strategy} from 'passport-jwt';
// import {ExtractJwt} from 'passport-jwt';
import passport from 'passport';
import mongoose from 'mongoose';
import {UserSchema} from '../models/userModel';
import {config} from './config';
const User = mongoose.model('User', UserSchema);
const cookieExtractor = (req) => {
let token = null;
if(req && req.cookies) {
token = req.cookies['jwt'];
}
return token;
}
export const passportConfig = (passport) => {
const opts = {};
opts.jwtFromRequest = cookieExtractor;
opts.secretOrKey = config.key;
passport.use(
new Strategy(opts, (jwt_payload, done) => {
User.findById(jwt_payload.id)
.then((user) => {
if(user) {
return done(null, user);
}
return done(null, false);
})
.catch((err) => {
console.log('Error in finding user by id in jwt.');
});
})
);
};
Here is the userRoutes.js:
import { addNewUser, loginUser, verifyLogin } from '../controllers/userController';
export const routes = (app) => {
app.route('/users/register')
.post(addNewUser);
app.route('/users/login')
.post(loginUser);
app.route('/users/protected')
.get(verifyLogin);
}
Finally, here are the verifyLogin and loginUser controllers in my userController.js:
export const loginUser = (req, res) => {
console.log(req.body);
if(mongoSanitize.has(req.body)) {
return res.send(400).json({error: "Characters $ and . are prohibited. Use different values."});
}
const {errors, isValid} = validateLoginInput(req.body);
if(!isValid) {
return res.status(400).json(errors);
}
const userName = req.body.userName;
const password = req.body.password;
User.findOne({userName: userName})
.then((user) => {
if(!user) {
return res.status(404).json({userNotFound: "Username not found."});
}
else {
bcrypt.compare(password, user.password)
.then((isMatch) => {
if(isMatch) {
const payload = {
id: user.id,
name: user.fullName
};
const token = jwt.sign(payload, config.key, {expiresIn: 60});
res.cookie('jwt', token, {httpOnly: true, secure: false});
res.status(200).json({success: true, token: token});
}
else {
return res.status(400).json({incorrectPassword: "Password incorrect."});
}
})
}
});
};
export const verifyLogin = () => {
console.log('protected'); //'protected' is printed
passport.authenticate('jwt', {session: false}),
(req, res) => {
console.log('here'); //'here' is not printed
const { user } = req;
res.send(200).send({user});
};
};
The controller loginUser works just fine, I can see the token in response and also I have a cookie with the jwt key:value pair.
But the 3rd controller verifyLogin doesn't do anything after passport.authenticate call.
Any ideas where I might be doing something wrong ?
Okay so I was able to fix. Anyone else having the same problem, the issue was that I wasn't using passport.authenticate as a middleware, instead I was calling it as a function with a callback on successful authentication. Here are the change that I made:
userRoutes.js:
export const routes = (app) => {
app.route('/register')
.post(addNewUser);
app.route('/login')
.post(loginUser);
app.route('/protected')
.get(passport.authenticate('jwt', {session:false}), verifyLogin);
}
And then verifyLogin just sends the logged in user information back:
export const verifyLogin = (req, res) => {
console.log('here');
const { user } = req;
res.status(200).json({message:'Secure route', user: user});
};
Everything else is pretty much the same.
I had the same problem until I changed the time to 1 hour. It seems like the value you wrote is in milliseconds, so instead of 60 you should write 60000.
Related
So im trying to make an admin login and I can get it to hash the password. But when I update the password or even the username it will un-hash the passwor for me when i need it to stay hashed. When I check my MongoDB itll be un-hashed for some reason. Any help will be appreciated. Thank you.
This is my user route
var express = require("express");
var router = express.Router();
const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const { isAuthenticated } = require("../middleware/auth");
const Admin = require("../models/Admin");
const saltrounds = 10;
/* GET users listing. */
router.get("/", function (req, res, next) {
res.send("respond with a resource");
});
router.post("/login", async (req, res) => {
if (!req.body.username || !req.body.password) {
return res.json({ message: "Please enter username and password" });
}
try {
const foundUser = await Admin.findOne({ username: req.body.username });
if (!foundUser) {
return res
.status(400)
.json({ message: "Username or password is incorrect" });
}
const isMatch = bcrypt.compareSync(req.body.password, foundUser.password);
if (!isMatch) {
return res
.status(400)
.json({ message: "Username or password incorrect" });
}
const payload = {
username: foundUser.username,
id: foundUser._id,
};
const token = jwt.sign(payload, process.env.SECRET, {
algorithm: "HS256",
expiresIn: "6h",
});
res.json({ token: token, id: foundUser.id });
} catch (err) {
res.status(400).json(err.message);
}
});
router.post("/update", isAuthenticated, async (req, res) => {
try {
const updateUser = await Admin.findByIdAndUpdate(
req.user.id,
{ ...req.body },
{ new: true }
);
res.json(updateUser);
} catch (err) {
res.json(err.message);
}
});
module.exports = router;
This is my authenticated folder
const jwt = require("jsonwebtoken");
const isAuthenticated = async (req, res, next) => {
// const token = req.headers.authorization;
// NOTE: if your token authentication is failing in Postman, uncomment the line below, and comment out the line above
const token = req.headers.authorization?.split(" ")[1];
if (!token || token === "null") {
console.log("NO TOKEN");
return res.status(400).json({ message: "Token not found" });
}
try {
const tokenInfo = jwt.verify(token, process.env.SECRET);
console.log(tokenInfo);
//If you have req.payload, change line 12 to:
// req.payload = tokenInfo;
req.user = tokenInfo;
next();
} catch (error) {
return res.status(440).json(error);
}
};
// Export the middleware so that we can use it to create a protected routes
module.exports = {
isAuthenticated,
};
I think the issue is located at the (function)
Admin.findByIdAndUpdate
May be the find unhash the password fisrt and the update do not hash it again.
Check at this function.
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 have a page written in nodejs and koa, and I'm a little confused about JWT, please check following code.
'use strict';
const Router = require("koa-router");
const router = new Router({prefix: '/login'});
const jwt = require('jsonwebtoken');
const jwtkey = "123456";
router
.get('/', async (ctx, next) => {
await ctx.render("login", {
});
})
.post('/', async (ctx, next) => {
try {
let email = ctx.request.body.email || "";
let password = ctx.request.body.password || "";
//check user
if (islogin) {
let payload = {
email: email,
password: sha1(password).toString()
}
let token = jwt.sign(payload, jwtkey, {expiresIn: '3h'});
ctx.body = {"error": false, "msg": token};
} else {
throw "Wrong";
}
} catch (e) {
ctx.body = {error:true, msg: e instanceof Error ? e.message : e};
}
})
module.exports = router;
I want to implement that when accessing the login page, if the token generated by jwt exists on the server side and is correct, then console.log("logined"),if not, show the login page.
.get('/', async (ctx, next) => {
//how to verify the token?
if(token) {
console.log("logined");
} else {
await ctx.render("login", {
});
}
})
Thank you.
After generating the token you should set that token in user's browser as cookie or some other way,
Then while requesting / page check for the token and verify the validity
var decoded = jwt.verify(token, key);
I moved from a express server handling my API's in Next to their built in API Routes!
Loving it!
Anyway, I am using Passport.js for authentication and authorization and have implemented that successfully.
But I noticed a few things which I want to bring first as I am pretty sure they're related to the problem with the SWR hook:
In my login route: /api/login:
import nextConnect from 'next-connect'
import auth from '../../middleware/auth'
import passport from '../../lib/passport'
import connectDB from '../../lib/mongodb';
const handler = nextConnect()
handler
.use(auth)
.post(
async (req, res, next) => {
await connectDB();
passport.authenticate('local', (err, user, info) => {
if (err) { return errorHandler(err, res) }
if (user === false) {
return res.status(404).send({
msg: `We were unable to find this user. Please confirm with the "Forgot password" link or the "Register" link below!`
})
}
if (user) {
if (user.isVerified) {
req.user = user;
return res.status(200).send({
user: req.user,
msg: `Your have successfully logged in; Welcome to Hillfinder!`
});
}
return res.status(403).send({
msg: 'Your username has not been verified! Check your email for a confirmation link.'
});
}
})(req, res, next);
})
export default handler
You can see I'm using the custom callback in Passport.js so when I get the user from a successful login I am just assigning the user to req.user = user
I thought this should allow the SWR hook to always return true that the user is logged in?
This is my hooks.js file i.e. SWR functionality:
import useSWR from 'swr'
import axios from 'axios';
export const fetcher = async (url) => {
try {
const res = await axios.get(url);
console.log("res ", res);
return res.data;
} catch (err) {
console.log("err ", err);
throw err.response.data;
}
};
export function useUser() {
const { data, mutate } = useSWR('/api/user', fetcher)
// if data is not defined, the query has not completed
console.log("data ", data);
const loading = !data
const user = data?.user
return [user, { mutate, loading }]
}
The fetcher is calling the /api/user:
import nextConnect from 'next-connect'
import auth from '../../middleware/auth'
const handler = nextConnect()
handler
.use(auth)
.get((req, res) => {
console.log("req.user ", req.user); // Shouldn't the req.user exist??
res.json({ user: req.user })
})
export default handler
Shouldn't that always return the user from a successful login?
Lastly here is my LoginSubmit:
import axios from 'axios';
export default function loginSubmit(
email,
password,
router,
dispatch,
mutate
) {
const data = {
email,
password,
};
axios
.post(`/api/login`,
data, // request body as string
{ // options
withCredentials: true,
headers: {
'Content-Type': 'application/json'
}
}
)
.then(response => {
const { userId, user } = response.data
if (response.status === 200) {
setTimeout(() => {
router.push('/profile');
}, 3000);
dispatch({ type: 'userAccountIsVerified' })
mutate(user)
}
})
}
Any help would be appreciated!
Update
Added auth middleware to question:
import nextConnect from 'next-connect'
import passport from '../lib/passport'
import session from '../lib/session'
const auth = nextConnect()
.use(
session({
name: 'sess',
secret: process.env.TOKEN_SECRET,
cookie: {
maxAge: 60 * 60 * 8, // 8 hours,
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
path: '/',
sameSite: 'lax',
},
})
)
.use((req, res, next) => {
req.session.users = req.session.users || []
next()
})
.use(passport.initialize())
.use(passport.session())
export default auth
Added: serializeUser && deserializeUser functions;
import passport from 'passport'
import LocalStrategy from 'passport-local'
import User from '../models/User'
passport.serializeUser((user, done) => {
done(null, user._id);
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
passport.use(
new LocalStrategy(
{ usernameField: 'email', passwordField: 'password', passReqToCallback: true },
async (req, email, password, done) => {
try {
const user = await User.findOne({ email }).exec();
if (!user) {
return done(null, false, { message: 'Invalid username!' });
}
const passwordOk = await user.comparePassword(password);
if (!passwordOk) {
return done(null, false, {
message: 'Invalid password!'
});
}
return done(null, user);
} catch (err) {
return done(err);
}
}
)
);
export default passport
And this is the session.js file:
import { parse, serialize } from 'cookie'
import { createLoginSession, getLoginSession } from './auth'
function parseCookies(req) {
// For API Routes we don't need to parse the cookies.
if (req.cookies) return req.cookies
// For pages we do need to parse the cookies.
const cookie = req.headers?.cookie
return parse(cookie || '')
}
export default function session({ name, secret, cookie: cookieOpts }) {
return async (req, res, next) => {
const cookies = parseCookies(req)
const token = cookies[name]
let unsealed = {}
if (token) {
try {
// the cookie needs to be unsealed using the password `secret`
unsealed = await getLoginSession(token, secret)
} catch (e) {
// The cookie is invalid
}
}
req.session = unsealed
// We are proxying res.end to commit the session cookie
const oldEnd = res.end
res.end = async function resEndProxy(...args) {
if (res.finished || res.writableEnded || res.headersSent) return
if (cookieOpts.maxAge) {
req.session.maxAge = cookieOpts.maxAge
}
const token = await createLoginSession(req.session, secret)
res.setHeader('Set-Cookie', serialize(name, token, cookieOpts))
oldEnd.apply(this, args)
}
next()
}
}
I want to get authorized user data. But instead I get the data of a completely different user. How to write a function getProfile to display the data of the current user?
controllers/auth.js:
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const db = require('../config/db.config.js')
const User = db.user
module.exports.login = async function(req, res) {
const candidate = await User.findOne({
where: {
username: req.body.username
}
})
if (candidate) {
const passwordResult = bcrypt.compareSync(req.body.password, candidate.password)
if (passwordResult) {
const token = jwt.sign({
username: candidate.username,
userId: candidate._id
}, process.env.SECRET_OR_KEY, {expiresIn: 60 * 60})
res.status(200).json({
token: `Bearer ${token}`
})
} else {
res.status(401).json({
message: 'Passwords do not match. Try again.'
})
}
} else {
res.status(404).json({
message: 'User with this login was not found.'
})
}
}
module.exports.getProfile = async function(req, res) {
try {
const user = await User.findOne({id: req.body.id})
res.status(200).json(user)
} catch(e) {
errorHandler(res, e)
}
}
routes/auth.js:
const express = require('express')
const router = express.Router()
const controller = require('../controllers/auth')
const passport = require('passport')
router.post('/login', controller.login)
router.get('/profile', passport.authenticate('jwt', {session: false}), controller.getProfile)
module.exports = router
You should attach a signed token in each HTTP req from client, either by custom HTTP header or set in cookie. This token is sent only after successful login which contains user's id and other info.
After you start receiving that token you can validate it (checking for expiry or some manual change) using a middleware and that token data will be the actual user data belongs to the user loggedin.
Now, you read that header/cookie to get requester user's info and you can then send their respective data only.
Let's say if client is sending you token info in header called tkn. Your token validation can be as follows:
var jwt = require('jsonwebtoken');
const SECRET = 'whatulike';
function verifyToken(req, res, next) {
var token = req.headers.tkn || "";
if (!token.length)
return unauthorised(res, 'Token absent');
jwt.verify(token, SECRET, function(err, decoded) {
if (err)
return unauthorised(res, 'Failed to authenticate token.');
req.tkn = decoded.id;
next();
});
}
function unauthorised(res, msg){
const sc = 401;
logger.warn(`${sc} - Unauthorised request ${res.req.originalUrl}`);
res.status(sc).send({msg});
}
module.exports.verifyToken = verifyToken;
And at handler side you can read tkn data like:
module.exports.getProfile = async function(req, res) {
try {
const user = await User.findOne({id: req.tkn.userId})
res.status(200).json(user)
} catch(e) {
errorHandler(res, e)
}
}