Im learning express authentication using passport and react for frontend and i have a question. How do i access whole authenticated user object? I have db model that looks like that
const userSchema = new mongoose.Schema({
username: {type:String,required:true },
password: {type:String ,required:true},
note: {type:String}
})
My passportConfig.js
const userSchema = require("./user");
const bcrypt = require("bcryptjs");
const localStrategy = require("passport-local").Strategy;
module.exports = function (passport) {
passport.use(
new localStrategy((username, password, done) => {
userSchema.findOne({ username: username }, (err, user) => {
if (err) throw err;
if (!user) return done(null, false);
bcrypt.compare(password, user.password, (err, result) => {
if (err) throw err;
if (result === true) {
return done(null, user);
} else {
return done(null, false);
}
});
});
})
);
passport.serializeUser((user, cb) => {
cb(null, user.id);
});
passport.deserializeUser((id, cb) => {
userSchema.findOne({ _id: id }, (err, user) => {
const userInformation = {
username: user.username,
};
cb(err, userInformation);
});
});
};
My login request and user request
app.post('/login',(req,res,next) => {
passport.authenticate("local",(err,user,info) =>{
if (err) throw err
if(!user) res.send("No user with given login")
else {
req.logIn(user, (err) => {
if (err) throw err
res.send("Succesfully Authenticated")
})
}
})(req,res,next)
})
app.get('/user',(req,res) => {
res.send(req.user)
})
Now in react i want to access my logged user notes and i did this
const signIn = () => {
const user = {
username: login,
password: password
}
Axios({
method: "POST",
data: user,
withCredentials: true,
url: "http://localhost:4000/login",
}).then((res) => {
console.log(res)
getNotes()
});
}
const getNotes = () => {
Axios({
method: "GET",
withCredentials: true,
url: "http://localhost:4000/user",
}).then((res) => {
setNotes(res.data);
console.log(res);
});
}
In my getNotes response console.log i wanted to have all of my logged user object and i got only his username.How do i access his notes?
The req.user is set from the logic within passport.deserializeUser. You can update deserializeUser to have more data stored within the user object.
Example to pass notes to created req.user object:
passport.deserializeUser((id, cb) => {
userSchema.findOne({ _id: id }, (err, user) => {
const userInformation = {
username: user.username,
notes: user.notes
};
cb(err, userInformation);
});
});
Related
I am learning how to implement user authentication using passport.js. I have a basic passport "local" strategy set up on the server side and so far just a single POST route to log a user in. This all works exactly as intended when troubleshooting with insomnia but when I make the same request from the browser I get a message missing credentials. This message is coming from console.log(info) in controllers/auth.js.
I have tried including credentials in the fetch request as seen below but I must be missing something else or including them incorrectly. I have also changed the variable names from 'email' to 'username' since I read that was the default for passport.js.
From what I can tell in Chrome dev tools, the request body is formatted correctly and I am hitting the proper endpoint.
controllers/auth.js
const express = require("express");
const router = express.Router();
const passport = require("passport");
router.post("/register_login", (req, res, next) => {
passport.authenticate("local", function(err, user, info) {
console.log(info)
if (err) {
return res.status(400).json({ errors: err });
}
if (!user) {
return res.status(400).json({ errors: "No user found" });
}
req.logIn(user, function(err) {
if (err) {
return res.status(400).json({ errors: err });
}
return res.status(200).json({ success: `logged in ${user.id}` });
});
})(req, res, next);
});
module.exports = router;
passport/setup.js
const bcrypt = require('bcrypt');
const { User } = require('../models');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
// Local Strategy
passport.use(new LocalStrategy((username, password, done) => {
// Match User
User.findOne({ email: username })
.then(user => {
// Create new User
if (!user) {
return done(null, false, { message: 'No user found!!'})
// Return other user
} else {
// Match password
bcrypt.compare(password, user.password, (err, isMatch) => {
if (err) throw err;
if (isMatch) {
return done(null, user);
} else {
return done(null, false, { message: 'Wrong password' });
}
});
}
})
.catch(err => {
return done(null, false, { message: err });
});
})
);
module.exports = passport;
client side fetch
const handleLogin = async (evt) => {
evt.preventDefault();
const response = await fetch('/auth/register_login', {
method: 'POST',
credentials: 'include',
withCredentials: true,
body: JSON.stringify({
"username": "test#email.com",
"password": "password"
})
})
return response;
};
Oof, that was a simple one...
I was reading the "Content-Type" in Chrome dev tools however I was reading the "Response Headers" thinking they were the "Request Headers". The issue is that I was sending text instead of json.
Changing the client side fetch to the snippet below resolved the issue.
client side fetch
const handleLogin = async (evt) => {
evt.preventDefault();
const response = await fetch('/auth/register_login', {
method: 'POST',
credentials: 'include',
withCredentials: true,
headers: {
'Content-Type': 'application/json' // <--add this
body: JSON.stringify({
"username": "test#email.com",
"password": "password"
})
})
return response;
};
I have created a model and the name of the table is users. In the Model, i have a method generateToken which is used to generate the web token.
I have used sequelized ORM.
module.exports = (sequelize, Sequelize) => {
const Tutorial = sequelize.define("users", {
age: {
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
email: {
type: Sequelize.STRING
},
password: {
type: Sequelize.STRING
}
});
Tutorial.generateToken = async function () {
try {
const token = jwt.sign({ _id: this.id }, "ThisIsTaskApp")
console.log(token)
}
catch (error) {
response.send('there is an error' + error)
console.log('there is an error' + error)
}
}
return Tutorial;
};
I want to create a web token when my email id and password matches, so for that i have used the generateToken but i am getting an error
TypeError: user.generateToken is not a function
I believe i have error with javascript importing the generateToken function.
const jwt = require('jsonwebtoken')
const user = db.users;
const generateToken = require('./models/users')
app.post('/login', async (req, res) => {
try {
var email = req.body.email;
var password = req.body.password;
await user.findOne({ where: { email: email, password: password } })
.then(user => {
if (user === null) {
res.status(404).json({
message: 'Auth Failed'
})
}
else {
const token = user.generateToken()
res.status(200).json(user)
}
})
}
catch (error) {
console.log(error)
return res.json({ 'status': 400 })
}
})
Please help me fix this issue and generating web token.
Try using
generateToken.generateToken()
there instead of
user.generateToken()
Because you are basically exporting the model of users in generate token variable, so that function is accessible from that variable not from user variable.
There is some issue with your code related to async, please try this one
const user = db.users;
app.post("/login", async (req, res) => {
try {
var email = req.body.email;
var password = req.body.password;
const userdata = await user.findOne({ where: { email: email, password: password } });
if (userdata === null) {
return res.status(404).json({
message: "Auth Failed",
});
}
const token = await userdata.generateToken();
console.log("🚀 ~ token", token)
return res.status(200).json(userdata);
} catch (error) {
console.log(error);
return res.json({ status: 400 });
}
});
I think you need to require jsonwebtoken in /models/users as well as in the route handler file
I'm a bit new to using Passport, but I was wondering if it was possible to change where the login redirects you to a different place depending on the situation. Since I can't access my res/req variables in the strategy file I was wondering if anybody had any suggestions on how to do this.
For example
Normal login
User clicks authorize (redirects to) -> ./profile
New User Login
User clicks authorize (redirects to) -> ./new-user
Here is my current router:
let express = require('express');
let router = express.Router();
const passport = require('passport');
router.get('/', passport.authenticate('discord'));
//Log out
router.get('/logout', ((req, res) => {
req.logout();
res.redirect('/')
}));
router.get('/redirect', passport.authenticate('discord', {
failureRedirect: '/404',
}), ((req, res, next) => {
console.log(req.ip + ' has logged in.')
res.redirect('../profile')
}))
module.exports = router;
My strategy file:
const DiscordStrategy = require('passport-discord').Strategy;
const passport = require('passport');
const User = require('../schemas/User.js');
const axios = require("axios");
const MongoClient = require('mongodb').MongoClient;
const noblox = require('noblox.js');
const { getCollectionFromBotData } = require('../database');
passport.serializeUser((user, done) => {
done(null, user.userID)
});
passport.deserializeUser(async (userID, done) => {
let thisUser = await getCollectionFromBotData('userData').findOne({ userID })
if (thisUser) {
done(null, thisUser);
}
})
passport.use(new DiscordStrategy({
clientID: "Removed for privacy",
clientSecret: "Removed for privacy",
callbackURL: "/auth/redirect",
scope: ['identify', 'guilds.join', 'email']
}, async (accessToken, refreshToken, profile, done) => {
try {
let user = await getCollectionFromBotData('userData').findOne( { userID: profile.id } )
console.log(user);
if (user) {
console.log(`user exists`)
let updateObj = {
email: profile.email,
userID: profile.id,
discordAvatar: `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}`,
discordUsername: profile.username,
lastLoggedInAt: Date.now()
}
getCollectionFromBotData('userData').updateOne( { userID: profile.id }, {$set: updateObj })
done(null, user);
} else {
console.log(`user doesnt exists`)
User.email = profile.email;
User.userID = profile.id;
User.discordAvatar = `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}`
User.discordUsername = profile.username;
User.caffeinatedAccountCreatedAt = Date.now();
User.discordVerified = true;
User.lastLoggedInAt = Date.now();
axios.post('http://localhost:8000/verifyUser', {}, {
headers: {
userID: profile.id
}
})
.then((response) => {
if (response.data.code === 500) {
axios.put('https://discord.com/api/v8/guilds/737058844676587662/members/' + profile.id, { access_token: accessToken, roles: ['787536777312731136', '787536978320031775', '787536296523989012', '737609790905253918']}, {
headers: {
Authorization: 'Removed for privacy',
}
})
.catch(err => {
if (err.data.code === 40007) {
console.log('user is banned');
}
})
}
axios.get(`https://api.blox.link/v1/user/${profile.id}`)
.then(async r => {
if (r.data.status === 'ok') {
User.robloxData.robloxAvatar = `https://www.roblox.com/headshot-thumbnail/image?userId=${r.data.primaryAccount}&width=420&height=420&format=png`
User.robloxData.robloxID = r.data.primaryAccount;
User.robloxLinked = true;
User.robloxData.robloxUsername = await noblox.getUsernameFromId(Number(r.data.primaryAccount))
getCollectionFromBotData('userData').insertOne(User)
.then(res => {
done(null, res.ops[0]);
})
.catch((err) => {
console.log(err);
});
} else {
getCollectionFromBotData('userData').insertOne(User)
.then(res => {
done(null, res.ops[0]);
})
.catch((err) => {
console.log(err);
});
}
})
.catch(err => {
console.log(err);
})
})
.catch(err => {
console.log(err);
});
}
}
catch (e) {
console.log(e)
}
}));
i know its messy, but it works
If anybody could help me with how I could achieve this please let me know!
I have multiple routes which need the same userdata from the database. I have a function to check if the user is loggend in but that function dont return the user variables.
route:
app.get('/template', isAuthenticated, function (req, res) {
MongoClient.connect(url, { useNewUrlParser: true, useUnifiedTopology: true }, (err, client) => {
if (err) throw err;
const db = client.db(dbname);
let collection = db.collection('users');
// find data in db
collection.findOne({ _id: userid }).then(user => {
if (user != null) {
res.render('template', { layout: 'temaplte', csrfToken: req.csrfToken(), username: user.username, avatar: user.picture });
} else {
console.log("No user with this id!")
}
}).catch((err) => { console.log(err);
}).finally(() => { client.close(); });
});
});
Is there a way to get the variables users from the db from a function like isAuthenticated? Do I need to write the findOne-function on every route?
Best way to reuse logic in routes is to refactor that functionality into its own middleware.
function loadUserData(req, res, next) {
MongoClient.connect(url, {
useNewUrlParser: true,
useUnifiedTopology: true
}, (err, client) => {
if (err) {
return next(err)
};
const db = client.db(dbname);
let collection = db.collection('users');
// find data in db
collection.findOne({
_id: userid
}).then(user => {
if (user != null) {
req.user = user; // augment the request object with user data (check res.locals docs too)
return next(); // pass control to next middleware
} else {
res.end("No user with this id!");
}
}).catch((err) => {
return next(err);
})
.finally(() => {
client.close();
});
});
}
app.get('/template', isAuthenticated, loadUserData, function(req, res) {
const user = req.user; // loadUserData populated req.user;
res.render('template', {
layout: 'temaplte',
csrfToken: req.csrfToken(),
username: user.username,
avatar: user.picture
});
});
The following code works properly, registers & logins accounts.
I added a Data.create on the register POST requests which saves the data from register, saves properly aswell.
The issue is that after I turn off the server and on again, the data is not saved in users variable and I can't login with what I've registered earlier with.
server.js
const initializePassport = require('./passport-config')
initializePassport(
passport,
email => users.find(user => user.email === email),
id => users.find(user => user.id === id)
)
const users = []
app.post('/register', checkNotAuthenticated, async (req, res) => {
try {
const hashedPassword = await bcrypt.hash(req.body.password, 10)
users.push({
id: Date.now().toString(),
name: req.body.name,
email: req.body.email,
password: hashedPassword
}),
Data.create({
name: req.body.name,
email: req.body.email,
password: hashedPassword
})
res.redirect('/login')
} catch {
res.redirect('/register')
}
})
passport-config.js
const LocalStrategy = require('passport-local').Strategy
const bcrypt = require('bcrypt')
function initialize(passport, getUserByEmail, getUserById) {
const authenticateUser = async (email, password, done) => {
const user = getUserByEmail(email)
if (user == null) {
return done(null, false, { message: 'No user with that email' })
}
try {
if (await bcrypt.compare(password, user.password)) {
return done(null, user)
} else {
return done(null, false, { message: 'Password incorrect' })
}
} catch (e) {
return done(e)
}
}
passport.use(new LocalStrategy({ usernameField: 'email' }, authenticateUser))
passport.serializeUser((user, done) => done(null, user.id))
passport.deserializeUser((id, done) => {
return done(null, getUserById(id))
})
}
module.exports = initialize
data.js (Database)
var Data = sequelize.define('data', {
name: Sequelize.STRING,
email: Sequelize.STRING,
password: Sequelize.STRING
});
module.exports = Data;