How to properly logout with JWT using Passport Strategies? - authentication

I am new to JWT and Passport so I started following a MERN tutorial on Youtube by NoobCoder that deals with authentication and authorization using JWT. I reached the part where the route deals with '/logout' and I get Unauthorized as a reply from Postman. The code so far is exactly the same by the looks of it. Can someone help me understand what is wrong here?
I have attached the code in the bottom. Please let me know if more information is required.
Here is the code:
app.js
const express = require('express');
const app = express();
const cookieParser = require('cookie-parser');
const mongoose = require('mongoose');
app.use(cookieParser());
app.use(express.json());
mongoose.connect('mongodb://localhost:27017/mernauth', {useNewUrlParser: true, useUnifiedTopology: true}, () => {
console.log('Successfully connected to DB');
});
const userRouter = require('./routes/User');
app.use('/user', userRouter);
app.listen(5000, () => {
console.log('express server started');
});
passport.js
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const JwtStrategy = require('passport-jwt').Strategy;
const User = require('./models/User');
const cookieExtractor = req => {
let token = null;
if(req && req.cookies) {
token = req.cookies['access_token'];
}
return token;
}
// Authorization
passport.use(new JwtStrategy({
jwtFromRequest: cookieExtractor,
secretOrKey: 'NoobCoder'
}, (payload, done) => {
User.findById({_id: payload.sub}, (err, user) => {
if(err) {
return done(err, false);
}
if(user) {
return done(null, user);
}
else {
return done(null, false);
}
})
}));
// Authenticated local strategy using username and password
passport.use(new LocalStrategy((username, password, done) => {
User.findOne({username}, (err, user) => {
// Something went wrong with DB
if(err) {
return done(err);
}
// If no user exists; null = no error; false = user does not exist
if(!user) {
return done(null, false);
}
// Check if password is correct; callback cb = done
user.comparePassword(password, done);
});
}));
User.js (route)
const express = require('express');
const userRouter = express.Router();
const passport = require('passport');
const passportConfig = require('../passport');
const JWT = require('jsonwebtoken');
const User = require('../models/User');
const Todo = require('../models/Todo');
const signToken = userID => {
return JWT.sign({
iss: "NoobCoder",
sub: userID
}, "NoobCoder", {expiresIn: "1h"});
}
userRouter.post('/register', (req, res) => {
const {username, password, role} = req.body;
User.findOne({username}, (err, user) => {
if(err) {
res.status(500).json({message: {msgBody: "Error has occured", msgError: true}})
}
if(user) {
res.status(400).json({message: {msgBody: "Username is already taken", msgError: true}})
}
else {
const newUser = new User({username, password, role});
newUser.save(err => {
if(err) {
res.status(500).json({message: {msgBody: "Error has occured", msgError: true}})
}
else {
res.status(201).json({message: {msgBody: "Account Successfully Created", msgError: false}})
}
})
}
})
});
userRouter.post('/login', passport.authenticate('local', {session: false}), (req, res) => {
if(req.isAuthenticated()) {
const {_id, username, role} = req.user;
const token = signToken(_id);
res.cookie('access_token', token, {httpOnly: true, sameSite: true});
res.status(200).json({isAuthenticated: true, user: {username, role}})
}
});
userRouter.get('/logout', passport.authenticate('jwt', {session: false}), (req, res) => {
res.clearCookie('access_token');
res.json({user: {username: '', role: ''}, success: true});
});
module.exports = userRouter;
User.js (model)
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const UserSchema = new mongoose.Schema({
username: {
type: String,
required: true,
min: 6,
max: 15
},
password: {
type: String,
required: true,
},
role: {
type: String,
enum: ['user', 'admin'],
required: true
},
todos: [{type: mongoose.Schema.Types.ObjectId, ref: 'Todo'}]
});
UserSchema.pre('save', function(next) {
if(!this.isModified('password')) {
return next()
}
bcrypt.hash(this.password, 10, (err, passwordHash) => {
if(err) {
return next(err);
}
this.password = passwordHash;
next();
});
});
UserSchema.methods.comparePassword = function(password, cb) {
bcrypt.compare(password, this.password, (err, isMatch) => {
if(err) {
return cb(err);
}
else {
if(!isMatch) {
return cb(null, isMatch)
}
return cb(null, this);
}
})
};
module.exports = mongoose.model('User', UserSchema);

Perhaps the /logout route is unauthorised because the JWT token is not present?
JWT token presence can be verified by ensuring that the cookieExtractor function is returning a token

app.get('/logout', function(req, res){
req.logout();
res.redirect('/');
});
Source

Related

No 'Access-Control-Allow-Origin' header is present on the requested resource (MERN)

This error is never ending, I keep getting it and it's been days I've been trying to find a solution for this annoying error.
Here is what happens when I try to log in.
My app works perfectly fine in localhost but there are alot of issue when I uploaded it to heroku and it is really annoying.
Im using
Axios.defaults.withCredentials = true;
code on my every front end.
My backend
const express = require("express");
const cors = require("cors");
const mongoose = require("mongoose")
const app = express();
const bcrypt = require("bcryptjs")
const saltRounds = 10;
const bodyParser = require("body-parser")
const cookieParser = require("cookie-parser")
const session = require("express-session")
const voterModel = require('./modules/voters.js')
const presidentModel = require('./modules/president.js')
const viceModel = require('./modules/vice.js')
const treasurerModel = require('./modules/treasurer.js')
var MongoDBStore = require('connect-mongodb-session')(session);
app.use(express.json());
const corsOptions = {
origin: 'https://incomparable-speculoos-abdd5f.netlify.app',
//update: or "origin: true," if you don't wanna add a specific one
credentials: true,
};
app.use(cors(corsOptions));
app.options('*', cors());
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }))
mongoose.connect("hidden",
{
useNewUrlParser: true,
useUnifiedTopology: true
}
)
var store = new MongoDBStore({
uri: 'hidden',
collection: 'sessions'
});
// Catch errors
store.on('error', function(error) {
console.log(error);
});
app.use(session({
secret: "hidden",
resave: false,
store: store,
saveUninitialized: false,
cookie: {
maxAge: 1000 * 60 * 60 * 24
}
}))
app.post('/login', async (req, res) => {
const email = req.body.email;
const password = req.body.password;
voterModel.find({email: email}, {"email":1}, async (err, result) => {
if (err) {
console.log(err)
} else {
if(result.length > 0) {
const user = await voterModel.findOne({email: email})
const pass = await user.comparePassword(password)
if (pass) {
req.session.user = user
} else {
console.log("NOT LOGGED IN")
res.send({ message: 'Invalid email or password!'})
}
} else {
console.log("NOT LOGGED IN")
res.send({ message: 'Invalid email or password!'})
}
}
})
})
app.post('/register', async (req, res) => {
const username = req.body.username;
const email = req.body.email;
const password = req.body.password;
// HASING PASSWORD
bcrypt.hash(password, saltRounds, async (err, hash) => {
if (err) {
console.log(err)
}
// INSERTING VALUES
const voters = await voterModel({email: email, username: username, password: hash, status: false})
// CHECKS IF EMAIL IS IN USE
const isNewEmail = await voterModel.isThisEmailInUse(email)
if (!isNewEmail) return res.send({ message: 'This email is already taken!'})
// SAVES THE INSERT DATA FOR VOTERS
await voters.save()
res.send({success: true})
})
})
app.post('/voted', async (req, res) => {
// FOR UPDATING THE VOTING STATUS
const email = req.body.email
// VARIABLES FOR CHOSEN CANDIDATES OF USER
const president = req.body.president
const vice = req.body.vice
const treasurer = req.body.treasurer
// SETS THE STATUS OF VOTER TO TRUE SO HE/SHE CAN ONLY VOTE ONCE
voterModel.updateOne({email: email}, {$set : {status: true}}, (err, result) => {
if (err) {
console.log(err)
} else {
console.log(result)
}
})
// BELOW ARE THE COMMANDS FOR INCREMENTING THE VOTE COUNT OF SELECTED CANDIDATES OF THE VOTER
presidentModel.updateOne({nickname: president}, {$inc : {votes: 1}}, (err, result) => {
if (err) {
console.log(err)
} else {
console.log(result)
}
})
viceModel.updateOne({nickname: vice}, {$inc : {votes: 1}}, (err, result) => {
if (err) {
console.log(err)
} else {
console.log(result)
}
})
treasurerModel.updateOne({nickname: treasurer}, {$inc : {votes: 1}}, (err, result) => {
if (err) {
console.log(err)
} else {
console.log(result)
}
})
})
app.get('/login', (req, res) => {
if (req.session.user) {
res.send({loggedIn: true, user: req.session.user})
} else {
res.send({loggedIn: false})
}
})
app.post('/checkVote', (req, res) => {
const email = req.body.email
const num = true;
voterModel.find({ $and : [{email: email}, {status : num}]},(err, result) => {
if (err) {
console.log(err)
} else {
console.log(result)
if (result.length > 0) {
res.send( {voted: true } )
} else {
res.send( {voted: false } )
}
}
})
})
app.get("/logout", (req, res) => {
req.session.destroy(err => {
if (err) return next(err)
res.status(200).send('logged out')
})
res.status(200).send('User has been logged out');
});
const PORT = process.env.PORT || 3001
app.listen(PORT, () => {
console.log('running on port 3001')
})

NextAuth authentication not working on Deployment on Vercel (Working on localhost)

Used credentials for authentication with nextauth. My code is not working on vercel deployment but working on localhost.
I used the basic cresdentials for authentication username and password but not able to get the session object after signing up from SignUpPage from the pages folder
[...nextauth].js
export default NextAuth({
session: {
jwt: true,
},
providers: [
Credentials({
async authorize(credentials) {
const client = await MongoClient.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
const usersCollection = client.db().collection("users");
const user = await usersCollection.findOne({
username: credentials.username,
});
if (!user) {
client.close();
throw new Error("No user found!");
}
const isValid = await compare(credentials.password, user.password);
if (!isValid) {
client.close();
throw new Error("Could not log you in!");
}
client.close();
return { username: user.username };
},
}),
],
callbacks: {
jwt: async ({ token, user }) => {
if (user) {
token.user = user;
}
return token;
},
session: async ({ session, token }) => {
if (token) {
session.user = token.user;
}
return session;
},
},
secret: process.env.SECRET,
jwt: {
secret: process.env.SECRET,
encryption: true,
},
});
pages/api/auth/signup.js
async function handler(req, res) {
if (req.method !== "POST") {
return;
}
const data = req.body;
const { username, password } = data;
if (!username || !password || password.trim().length < 7) {
res.status(422).json({
message:
"Invalid input - password should also be at least 7 characters long.",
});
return;
}
const client = await MongoClient.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
const db = client.db();
const existingUser = await db
.collection("users")
.findOne({ username: username });
if (existingUser) {
res.status(422).json({ message: "User exists already!" });
client.close();
return;
}
const hashedPassword = await hash(password, 12);
const result = await db.collection("users").insertOne({
username: username,
password: hashedPassword,
});
res.status(201).json({ message: "Created user!" });
client.close();
}
export default handler;
pages/SignUpPage.js
useEffect(() => {
getSession().then((session) => {
if (session) {
router.push("/");
} else {
setIsLoading(false);
}
});
}, [router]);
if (isLoading) {
return <p>Loading...</p>;
}
const createUser = async (username, password) => {
const response = await fetch("/api/auth/signup", {
method: "POST",
body: JSON.stringify({ username, password }),
headers: {
"Content-Type": "application/json",
},
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || "Something went wrong!");
}
return data;
};
const submitHandle = async (username, password) => {
if (login) {
const result = await signIn("credentials", {
redirect: false,
username: username,
password: password,
});
if (!result.error) {
router.push("/");
}
} else {
try {
const result = await createUser(username, password);
console.log("result", result);
router.push("/");
} catch (error) {
console.log(error);
}
}
};
Looks like you have used environment variables. Make sure you have updated them with relevant values.
for example,
NEXTAUTH_URL=http://localhost:3000
to
NEXTAUTH_URL=https://youdomain.com

TypeError: User.comparePassword is not a function. Comes up with Postman

I'm trying to get my login component on the server side to work, but it keeps giving me this one error over and over. "TypeError: User.comparePassword is not a function" I had it working in postman before, but I can't figure out why it broke now. I'm making an ecommerce site, so I'm excluding code that has stuff to do with the rest of the site.
[user.js - my routing and where the error is being presented.]
const { User, validateUser } = require("../models/user.js");
const bcrypt = require("bcrypt");
const config = require("config");
const jwt = require("jsonwebtoken");
const auth = require("../middleware/auth");
const express = require("express");
const router = express.Router();
//get users
router.get("/", async (req, res) => {
try {
const users = await User.find();
return res.send(users);
} catch (ex) {
return res.status(500).send(`Internal server Error: ${ex}`);
}
});
//get a user
router.get("/:userId", async (req, res) => {
try {
const user = await User.findById(req.params.userId);
return res.send(user);
} catch (ex) {
return res.status(500).send(`Internal server Error: ${ex}`);
}
});
//new user
router.post("/register", async (req, res) => {
try {
const { error } = validateUser(req.body);
if (error) return res.status(500).send(error.details[0].message);
let user = await User.findOne({ email: req.body.email });
if (user) return res.status(400).send("User already registered.");
const salt = await bcrypt.genSalt(10);
user = new User({
name: req.body.name,
email: req.body.email,
password: await bcrypt.hash(req.body.password, salt),
});
await user.save();
const token = jwt.sign(
{ _id: user._id, name: user.name },
config.get("jwtSecret")
);
return res
.header("x-auth-token", token)
.header("access-control-expose-headers", "x-auth-token")
.send({ _id: user._id, name: user.name, email: user.email });
} catch (ex) {
return res.status(500).send(`InternalServerError:${ex}`);
}
});
router.get("/auth", auth, (req, res) => {
res.status(200).json({
_id: req.user._id,
isAdmin: req.user.role === 0 ? false : true,
isAuth: true,
email: req.user.email,
name: req.user.name,
role: req.user.role,
image: req.user.image,
cart: req.user.cart,
history: req.user.history,
});
});
router.post("/login", (req, res) => {
User.findOne({ email: req.body.email }, (err, user) => {
if (!User)
return res.json({
loginSuccess: false,
message: "Auth failed, email not found",
});
});
User.comparePassword(req.body.password, (err, isMatch) => {
if (!isMatch)
return res.json({ loginSuccess: false, message: "Wrong password" });
user.generateToken((err, user) => {
if (err) return res.status(400).send(err);
res.cookie("w_authExp", User.tokenExp);
res.cookie("w_auth", User.token).status(200).json({
loginSuccess: true,
userId: user._id,
});
});
});
router.get("/logout", auth, (req, res) => {
User.findOneAndUpdate(
{ _id: req.user._id },
{ token: "", tokenExp: "" },
(err, doc) => {
if (err) return res.json({ success: false, err });
return res.status(200).send({
success: true,
});
}
);
});
});
[User.js - Userschema]
const mongoose = require('mongoose');
const Joi = require('joi');
const cors = require('cors');
const config = require('config');
const jwt = require('jsonwebtoken');
const { productSchema } = require('./Product');
const { reviewSchema } = require('./review');
const userSchema = new mongoose.Schema({
name: { type: String, required: true, minlength: 5, maxlength: 50},
email: {type: String, unique: true, required: true, minlength: 5, maxlength: 255},
password: {type: String, required: true, maxlength: 1024, minlength: 5},
timestamp: { type: Date, default: Date.now() },
cart: {type: [productSchema], default: []},
newSalePost: {type: [productSchema], default: []},
review: {type: [reviewSchema], default: []},
image: {type: String, required: true}
});
const User = mongoose.model('User', userSchema);
userSchema.methods.generateAuthToken = function () {
return jwt.sign({_id: this._id, name: this.name, isAdmin: this.isAdmin}, config.get('jwtSecret'));
};
function validateUser(user){
const schema = Joi.object({
name: Joi.string().min(5).max(50).required(),
email: Joi.string().min(5).max(255).required().email(),
password: Joi.string().min(5).max(1024).required(),
});
return schema.validate(user);
}
userSchema.statics.findByToken = function (token, cb) {
var user = this;
jwt.verify(token, 'secret', function (err, decode) {
user.findOne({ "_id": decode, "token": token }, function (err, user) {
if (err) return cb(err);
cb(null, user);
})
})
}
exports.User = User;
exports.validateUser = validateUser;
exports.userSchema = userSchema;

404 Error after successful authentication with Passport.js unless redirected to '/'

I have an Express server on the backend that uses Passport.js for authentication. Even when successfully authenticated, a 404 error is shown in the webbrowser, when redirecting to '/about', '/login', or any other path (all those paths exists on the frontend), or even when not redirecting. The only time a 404 is not shown is if a redirect to '/' is made. I will include the relevant code snippets below.
users.js
router.post('/api/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err); }
if (!user) { return res.redirect('/login'); }
req.logIn(user, function(err) {
if (err) { return next(err); }
// res.statusCode is still 200 here!!!!!!!!
return res.redirect('/about');
});
})(req, res, next);
})
Alternative users.js (has same issue)
router.post('/api/login',
passport.authenticate('local', { successRedirect: '/about',
failureRedirect: '/login' }))
passportLocalStrategy.js
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const init = require('./passportSessionConfig');
const knex = require('../db/connection');
const authUtils = require('./utils')
init();
passport.use(new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
},
function (username, password, done) {
// check to see if the username exists
knex('users')
.where({ 'email': username })
.orWhere({ username })
.first()
.then((results) => {
if (!results) return done(null, false);
if (!authUtils.comparePass(password, results.password)) {
return done(null, false);
} else return done(null, results);
})
.catch((err) => { return done(err); });
}));
module.exports = passport;
This is the frontend javascript code that made the request (Vue.js)
methods: {
login: function(e) {
e.preventDefault();
let data = {
email: this.email,
password: this.password,
returnTo: window.location.pathname
};
this.axios
.post("/api/login", data)
.then(response => {
console.log("Logged in");
this.$router.push("/about");
})
.catch(errors => {
console.log("Cannot log in");
console.log(errors);
});
}
}
This is the 404 error from the webbrowser

passport.js + express + apollo-server, req.user is undefined

I tried to record sessions in the database through sequelize, to make it serverless, but req.user is always undefined, I tried every manual which I found on the internet, I do not understand why it not working.
I tried passport.js manual, express manuals, github gists.
Records in the database are successfully created, on successful authentication, but when I try hit /graphql endpoint, it does not fill req.user with user.
req.user should be restored based on session hash which is stored in database.
#!/usr/bin/env node
import express from 'express';
import session from 'express-session';
import { ApolloServer } from 'apollo-server-express';
import { typeDefs, resolvers } from './graphql';
import orm from './orm';
import compose from './dataloader/status.dataloader';
import passport from 'passport';
import { Strategy as GitHubStrategy } from 'passport-github';
import cors from 'cors';
const app = express();
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
const user = req.user;
console.log({ user });
return {
user,
orm,
dataloader: compose(orm),
};
},
});
passport.use(
new GitHubStrategy(
{
clientID: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
callbackURL: `/auth/github/callback`,
},
async (accessToken, refreshToken, profile, done) => {
const { provider, id: externalId, profileUrl, username, displayName, photos } = profile;
const photo = photos && photos[0] && photos[0].value;
const user = await orm.User.findOne({
include: [
{
attributes: [],
model: orm.UserProvider,
where: {
provider,
externalId,
},
},
],
raw: true,
}).then(async (v) => {
if (null !== v) {
return v;
}
v = await orm.User.create({
displayName,
photo,
});
await orm.UserProvider.create({
provider,
internalId: v.id,
externalId,
username,
profileUrl,
});
return v;
})
const session = await orm.UserSession.create({
internalId: user.id,
hash: accessToken,
});
return done(null, session);
}
)
);
passport.serializeUser(({ hash }, done) => {
console.log({ hash });
done(null, hash);
});
passport.deserializeUser(async (hash, done) => {
console.log({ hash });
const user = await orm.User.findOne({
include: [
{
attributes: [],
model: orm.UserSession,
where: {
hash,
},
},
],
raw: true,
});
done(null, user);
});
app.use(
cors({
origin: "*",
methods: "GET,POST",
preflightContinue: false,
optionsSuccessStatus: 204,
credentials: true,
})
);
app.use(session({ secret: 'test' }));
app.use(passport.initialize());
app.use(passport.session());
app.get(
'/auth/github',
passport.authenticate('github', { session: true })
);
app.get(
'/auth/github/callback',
passport.authenticate('github', { session: true }),
(req, res) => res.redirect('/')
);
app.use('/graphql', passport.authenticate('session', { session: true }));
// (req, res, next) => {
// debugger;
// // passport.
// console.log({
// req,
// session: JSON.stringify(req.session, ',', 4),
// cookie: JSON.stringify(req.cookie),
// user: req.user,
// });
// return next();
// });
server.applyMiddleware({ app, path: '/graphql' });
app
.listen(process.env.PORT, () => {
console.log(`GraphQL ready on: http://localhost:${process.env.PORT}/graphql`);
});