express | node.js | oauth2 not authenticating - node.js

so, im using express, and passport-oauth2 / passport-discord to create a website for my discord bot, but the checkauth function always returns false, and during the auth process, i just get redirected to the main auth route and nothing happens. (i never get redirected to the dashboard)
heres the code: (i tried to include every file relative to the problem):
// strategy file
const DiscordStrategy = require('passport-discord').Strategy;
const passport = require('passport');
const DiscordUser = require('.././models/DiscordUser');
var session = require('express-session')
passport.serializeUser((user, done) =>{
done(null, user.id)
})
passport.deserializeUser(async (id, done) =>{
const user = await DiscordUser.findById(id);
if(user){
done(null, user);
}
})
passport.use(new DiscordStrategy({
clientID: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
callbackURL: process.env.CLIENT_REDIRECT,
scope: ['identify', 'guilds', 'email', 'bot']
}, async (accessToken, refreshToken, profile, done) => {
try{
const user = await DiscordUser.findOne({ discordId: profile.id});
if(user) {
console.log('a known user entered!')
done(null, user);
}else{
console.log('a new user entered!')
const newUser = await DiscordUser.create({
discordId: profile.id,
username: profile.username,
discriminator: profile.discriminator,
email: profile.email,
guilds: profile.guilds
});
const savedUser = await newUser.save();
done(null, savedUser);
}
}catch(err) {
console.log(err);
done(err, null);
}
}));
//checkauth file
module.exports = async (req, res, next) => {
if(req.isAuthenticated()){
return next();
} else {
return res.redirect("/auth");
}
};
//auth router
const router = require('express').Router();
const passport = require('passport');
router.get('/', passport.authenticate('discord'));
router.get('/redirect', passport.authenticate('discord', {
failureRedirect: '/forbidden',
successRedirect: '/dashboard'
}), (req, res) => {
res.sendStatus(200);
})
module.exports = router;
//dashboard router
const router = require('express').Router();
var session = require('express-session');
const CheckAuth = require('../CheckAuth');
router.get('/', CheckAuth, (req, res) => {
res.sendFile('../views/dashboard/index')
})
module.exports = router;
in the main file i just create a random cookie, define the routes, and use app.use(passport.initialize()) and app.use(passport.session()).
if you need anything else let me know, ty :)

In the documentation they use the callback method instead of successRedirect.
router.get('/redirect', passport.authenticate('discord', {
failureRedirect: '/'
}), function(req, res) {
res.redirect('/secretstuff') // Successful auth
});

Related

passport js to authenticate user login not working Local Strategy not being called

Can someone explain to me the reason why LocalStrategy is not being called when I'm trying to login?
Basically nothing happens when I login it just refreshes the page.
require("dotenv").config()
const ejs = require("ejs");
const express = require("express")
const session = require('express-session');
const mongoose = require('mongoose')
const passport = require("passport")
const LocalStrategy = require('passport-local').Strategy
const bcrypt = require("bcrypt")
const flash = require("express-flash")
//const User = require("./users")
//creating application
const app = express()
//const initializePassport = require("./passportconfig")
//connection to MongoDB with mongoose
main().catch(err => console.log(err));
async function main() { await mongoose.connect('mongodb://localhost:27017/passportDB') }
const userSchema = new mongoose.Schema({
name: String,
password: String,
userEmail: String
})
const User = mongoose.model("User", userSchema);
//middleware
//connecting to ejs
app.set("view engine", "ejs")
//connecting error text giver
app.use(flash())
//setup session
app.use(session({
secret: String(process.env.SECRET),
resave: false,
saveUninitialized: false,
cookie: { maxAge: 1000 * 60 * 60 * 24 * 7 }
}))
//connecting to body-parser
app.use(express.urlencoded({ extended: false }))
//passport.js
app.use(passport.initialize())
app.use(passport.session())
//serialize and deserialize session
passport.serializeUser(function (user, done) {
console.log('Deserialize user called.');
// #ts-ignore
done(null, user.id);
});
passport.deserializeUser(function (id, done) {
console.log('Deserialize user called.');
User.findById(id, function (err, user) {
done(err, user);
});
});
//initializePassport(passport)
passport.use(new LocalStrategy((email, password, done) => {
User.findOne({ userEmail: email }, async function (err, user) {
if (err) {return done(err)}
if (!user) { return done(null, false, { message: 'No user with that email' }) }
if (await bcrypt.compare(password, user.password) == false) { return done(null, false, { message: 'No user with that email' }) }
return done(null, user);
});
}))
function isLoggedIn(req, res, next) {
if (req.isAuthenticated())
return next();
res.redirect("/login");
}
app.get("/", isLoggedIn, (req, res) => {
res.render('index.ejs', { name: "Kyle" })
})
app.get("/login", (req, res) => {
res.render('login.ejs')
})
app.get("/register", (req, res) => {
res.render('register.ejs')
})
app.post("/register", async (req, res) => {
//10 is the value to generate the salt higher = more salted
try {
const hashedPassword = await bcrypt.hash(req.body.password, 10)
const user = new User({
name: req.body.name,
password: hashedPassword,
email: req.body.email
})
await user.save()
console.log(user)
res.redirect('login')
} catch {
res.redirect('register')
}
})
app.post("/login", passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/login',
failureFlash: true
}))
app.listen(3000, ()=> {
console.log("listening on port 3000")
})
Is there something I am missing?
The app.post for register is working all the data is entering the database it's simply for the login that's not working and it's because passport.authenticate is not working for the login post

how to use passport-google-oauth20 twice in the same app

I'm trying to use the passport-google-oauth20 twice in my node app but it's not working. Still learning passport authentication.
So here's the problem:
i made a button for the user to login using google(/auth.google) and a button for a user to register using google(/users/google). I want it so that if the user is already registered on my app using google and he goes on a presses "register using google" button, he should be redirected back to the register page(auth fails). In my case when i add the strategy once and add a login button only(without register), auth works and users is redirected to dashboard. However, when i add the strategy again in passport.js and the 'already registered user" tries to login using google, he gets redirected to register page(where the register using google button is found) instead of dashboard. I know there's no need to do such thing and no need to use the same strat again for registeration as it works the same for logging in. just wondering if it's possible to work.
Here's my code:
Passport.js
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const myClientId = require("./keys").clientID;
const myClientSecret = require("./keys").clientSecret;
// get the user schema
const User = require("../models/User");
// passport google authentication
module.exports = function (passport) {
passport.use(
new GoogleStrategy(
{
clientID: myClientId,
clientSecret: myClientSecret,
callbackURL: "/auth/google/callback",
},
(accessToken, refreshToken, profile, done) => {
const newUser = new User({
googleId: profile.id,
displayName: profile.displayName,
firstName: profile.name.givenName,
lastName: profile.name.familyName,
image: profile.photos[0].value,
});
User.findOne({ googleId: profile.id })
.then((user) => {
if (user) {
done(null, user);
} else {
newUser.save().then((userd) => {
done(null, userd);
});
}
})
.catch((err) => console.log(err));
}
)
);
// using same strategy again but different callback URL
passport.use(
new GoogleStrategy(
{
clientID: myClientId,
clientSecret: myClientSecret,
callbackURL: "/users/google/callback",
},
(accessToken, refreshToken, profile, done) => {
const newUser = new User({
googleId: profile.id,
displayName: profile.displayName,
firstName: profile.name.givenName,
lastName: profile.name.familyName,
image: profile.photos[0].value,
});
User.findOne({ googleId: profile.id })
.then((user) => {
if (user) {
done(null, false);
} else {
newUser.save().then((userd) => {
done(null, userd);
});
}
})
.catch((err) => console.log(err));
}
)
);
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
};
app.js
/* initalized passport, session and other stuff here but just showing you the routes and the passport
function in app.js */
require("./config/passport")(passport);
app.use("/auth", require("./routes/auth"));
app.use("/users", require("./routes/users"));
auth.js
const express = require("express");
const passport = require("passport");
const router = express.Router();
//? register and login handle
router.get(
"/google",
passport.authenticate("google", {
scope: ["profile"],
})
);
//? google auth callback
router.get(
"/google/callback",
passport.authenticate("google", {
successRedirect: "/dashboard",
failureRedirect: "/",
})
);
module.exports = router;
users.js
const express = require("express");
const router = express.Router();
const passport = require("passport");
const { ensureAuthenticated, forwardAuth } = require("../config/checkAuth");
//? register page
router.get("/register", forwardAuth, (req, res) => {
res.render("register");
});
//? login page
router.get("/login", forwardAuth, (req, res) => {
res.render("login");
});
//? logout handle
router.get("/logout", (req, res) => {
req.logOut();
res.redirect("/");
});
router.get(
"/google",
passport.authenticate("google", {
scope: ["profile"],
})
);
router.get(
"/google/callback",
passport.authenticate("google", {
successRedirect: "/dashboard",
failureRedirect: "/users/register",
})
);
module.exports = router;
So if the users logs in on /auth/google ...he gets redirected to the register page(/users/register)! Can't know why
I solved my problem.
the solution was actually simple. Just using the same passport.js file.
in passport.js
const GoogleStrategy = require("passport-google-oauth20").Strategy;
module.exports = function (passport) {
passport.use("firstUse", new GoogleStrategy(....))
passport.use("secondUse", new GoogleStrategy(...))
}
so the thing is that you can register the strategy with any name you want and then when you want to handle the authorization middleware in the routes you just do this:
in users and auth.js
app.get("/auth/google/callback", passport.authenticate("firstUse"));
app.get("/users/google/callback", passport.authenticate("secondUse"));
just add the name that you registered with. I thought that it is necessary to only use passport.authenticate("google") and not any name else than "google".

Unable to get Passport working with React & Node, without Passport authentication works fine

This is my first try with PassportJS, and I am following their documentation, which is very hard to understand for me.
My goal is to have a user register through Registration Page, a react component and then use the Login page, a react component as well, to login and redirect to a Dashboard, which is again a react component.
All the above works, but without Passport. I am not sure what I am doing wrong, as this is my first time working with it, but it seems to not working. And by not working I mean that I don't see any error message and the login page is not redirecting to dashboard.
Although I have managed to almost put things together, somehow either I am missing something or doing something wrong.
This is my React Login Component, which is sending Axios post request to API:
const onSubmit = (e) => {
e.preventDefault();
const newLogin = {
username,
password,
};
const config = {
headers: {
'Content-Type': 'application/JSON',
},
};
axios.post('/api/v1/login', newLogin, config).then((res) => {
return res.data === 'user'
? (window.location = '/dashboard')
: (window.location = '/login');
});
setUsername('');
setPassword('');
};
On Node server.js, the above login post request is sent to controller through router.
server.js:
app.use(passport.initialize());
app.use(passport.session());
app.use('/api/v1/register', register);
app.use('/api/v1/login', login);
Router.js (Login):
const { loginUser } = require('../controllers/loginController');
router.route('/').post(loginUser);
loginController.js:
require('../config/passport')(passport);
exports.loginUser = (req, res, next) => {
passport.authenticate('local', {
successRedirect: '/dashboard',
failureRedirect: '/register',
failureFlash: true,
});
};
This is my passport.js file:
const User = require('../models/UserModel');
const passport = require('passport');
module.exports = function (passport) {
passport.use(
new LocalStrategy((username, password, done) => {
// Match User
User.findOne({ username: username })
.then((err, user) => {
if (!user) {
return done(null, false, console.log('no user'));
}
//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: 'Password incorrect' });
}
});
})
.catch((err) => console.log(err));
})
);
};
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findOne(id, (err, user) => {
done(err, user);
});
});
I'd suggest removing the redirects from the passport login controller and handling the routing from the react app internally (via react router or otherwise) based on whether or not the app received a user in the response. Here's how I've got it set up in my recent project:
//router.js
const express = require("express");
const fs = require("fs");
const { userModel } = require("../models");
const passport = require("passport");
const bcrypt = require("bcrypt");
const bodyParser = express.urlencoded({ extended: true, limit: "50mb" });
const jsonParser = express.json({ limit: "50mb" });
const router = express.Router();
const saltRounds = 10;
router.get("/login", (req, res) => {
res.render("login");
});
router.get("/logout", (req, res, next) => {
req.logout();
req.session.destroy();
next();
});
router.post("/login", bodyParser, jsonParser, **passport.authenticate("local"),** (req, res, next) => {
if (!req.user) return;
const date = new Date();
console.log(`User ID:${req.user._id} logged in at ${date}`);
res.json(req.user);
next();
});
module.exports = router;
Note how I'm just using passport as middleware, then move on to the next function, which checks for the user it should have received from the previous function

How to call multiple passport-jwt strategies in the same app

I'm able to differentiate the two routes and
authenticate the first route(student). When i call the second route(teacher) to be authenticated, a empty JSON file is returned.
In users.js 'passportStudent.authenticate' works and returns a JSON file with user information as intended, But i'm not sure why 'passportTeacher.authenticate' returns a empty JSON file.
Here's My Code:
users.js:
const passportStudent = require('passport');
const passportTeacher = require('passport');
/*Some code here*/
router.get('/profile/student', passportStudent.authenticate('student', {session: false}), (req, res, next) =>{
res.json({user : req.user});
});
router.get('/profile/teacher', passportTeacher.authenticate('teacher', {session: false}), (req, res, next) =>{
res.json({teacher : req.teacher});
});
passport.js:
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const User = require('../models/user');
const Teacher = require('../models/teacher');
const config = require('../config/database');
module.exports = function(passport){
let opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderWithScheme('jwt');
opts.secretOrKey = config.secret;
passport.use('student', new JwtStrategy(opts, (jwt_payload, done) => {
console.log(jwt_payload);
User.getUserById(jwt_payload.data._id, (err, user) => {
if(err){
return done(err, false);
}
if(user){
return done(null, user);
}
else{
return done(null, false);
}
});
}));
passport.use('teacher', new JwtStrategy(opts, (jwt_payload2, done) => {
console.log(jwt_payload2);
Teacher.getTeacherById(jwt_payload2.data._id, (err, teacher) => {
if(err){
return done(err, false);
}
if(teacher){
return done(null, teacher);
}
else{
return done(null, false);
}
});
}));
}
app.js code for Passport:
const passportStudent = require('passport');
const passportTeacher = require('passport');
/*Some code here*/
app.use(passportStudent.initialize());
app.use(passportStudent.session());
app.use(passportTeacher.initialize());
app.use(passportTeacher.session());
require('./config/passport')(passportStudent);
require('./config/passport')(passportTeacher);
How can I authenticate either on desired routes? Or should I go for local strategy for student and jwt for teacher?
Thanks for your help.
In both cases, the passport store the signed users information in req.user.
So you need to update the teacher router to
router.get('/profile/teacher', passportTeacher.authenticate('teacher', {session: false}), (req, res, next) =>{
res.json({teacher : req.user});
});
Here I change
res.json({teacher : req.teacher});
To
res.json({teacher : req.user});

Local and Google strategy in passport.js: issue when serializing user

I've been trying to understand why I couldn't keep an user logged in once authenticated even though authentication itself was working. I even posted a question here: Passport.js - Local strategy doesn't authenticate
By trying to fix the issue, I finally worked out what's wrong.
The issue is the following: I have two different passport strategy, so I am serializing and deserializing the user twice. If I serialize the user with the local strategy first, local strategy will work, but Google's won't. And vice versa.
I put a comment to highlight the problem in app.js.
Here's the files:
app.js
const express = require("express"),
mongoose = require("mongoose"),
bodyParser = require("body-parser"),
cookieSession = require("cookie-session"),
localStrategy = require("passport-local"),
passport = require("passport");
const LocalUser = require("./models/localuser");
const keys = require("./config/keys"); // requiring keys
const authRoutes = require("./routes/auth"); // requiring auth routes
const mainRoutes = require("./routes/main");
//Initialize express app
const app = express();
mongoose.connect("mongodb://localhost/thoughtApp"); // connectiong database
app.use(express.static(__dirname + "/public"));
app.set("view engine", "ejs");
app.use(bodyParser.urlencoded({extended: true}));
app.use(cookieSession({
maxAge: 24 * 60 * 60 * 1000,
keys: [keys.session.cookieKey]
}));
//initialize passport
app.use(passport.initialize());
app.use(passport.session());
passport.use(new localStrategy(LocalUser.authenticate()));
passport.serializeUser(LocalUser.serializeUser());
passport.deserializeUser(LocalUser.deserializeUser());
app.use(function(req, res, next){
res.locals.user = req.user;
next();
});
app.use("/", mainRoutes); //main routes
app.use("/auth", authRoutes); // setup auth routes
const passportSetup = require("./config/passport-setup"); /// THIS IS THE ISSUE
// IF BeFORE LINE 33 ( passport.use(new localStrategy(LocalUser.authenticate()));, GOOGLE LOGIN WORKS BUT LOCAL DOESNT; IF AFTER, LOCAL WORKS BUT GOOGE DOESN'T; PROBABLY DUE TO SERIALIZE AND DESARIALIZE BEING USED ALREADY
app.listen(process.env.PORT || 3000, () => {
console.log("Server started.")
});
auth.js (auth routes)
const router = require("express").Router();
const passport = require("passport");
const LocalUser = require("../models/localuser");
const authCheck = function (req, res, next) {
if (!req.user) {
next();
} else {
res.redirect("/");
}
};
//login
router.get("/login", authCheck, (req, res) => {
res.render("login", {user: req.user});
});
router.post("/login", passport.authenticate("local", {
successRedirect: "/",
failureRedirect: "/login"
}), (req, res) => {
})
// logout
router.get("/logout", (req, res) => {
//handle with passport
req.logout();
res.redirect("/");
});
//register
router.get("/signup", authCheck, (req, res) => {
res.render("signup", {user: req.user});
});
router.post("/signup", (req, res) => {
LocalUser.register(new LocalUser({username: req.body.username}), req.body.password, (err, user) => {
if (err) {
console.log(err);
res.redirect("/auth/signup")
}
passport.authenticate("local")(req, res, () => {
console.log(user)
res.redirect("/");
})
})
})
// google auth
router.get("/google", authCheck, passport.authenticate("google", {
scope: ["profile"]
}))
//goes to google consent screen
// callback for google to redirect to
router.get("/google/redirect", passport.authenticate("google"), (req, res) => {
res.redirect("/profile");
});
module.exports = router;
passport-setup.js (google strategy setup)
const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth20");
const keys = require("./keys");
const User = require("../models/user")
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id).then((user) => {
done(null, user);
});
});
passport.use(new GoogleStrategy({
//options for the google strategy
callbackURL: "/auth/google/redirect",
clientID : keys.google.clientID,
clientSecret : keys.google.clientSecret
}, (accessToken, refreshToken, profile, done) => {
//passport callback function
// check if user exists already
User.findOne({googleID: profile.id}).then((currentUser) => {
if (currentUser) {
console.log("user is: " + currentUser);
done(null, currentUser);
} else {
new User({
username: profile.displayName,
googleID: profile.id
}).save().then((newUser) => {
console.log("new user created: " + newUser);
done(null, newUser);
})
}
})
})
)
localuser.js
const mongoose = require("mongoose");
const passportLocalMongoose = require("passport-local-mongoose");
const localUserSchema = new mongoose.Schema({
username: String,
password: String
});
localUserSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("localUser", localUserSchema);
How can I solve this?
So I have been struggling with the same and I don't know if you found a solution but I stumbled upon this link,
Linking all accounts together
So basically, you first need to check is in the request is req.user exists, if so add the fields you want to serialize and call done(null,newUser)
This should do the trick,
I hope i was clear enough

Resources