I'm New to node js. I'm using passport jwt for authentication. When i tried to authenticate, its always showing "unauthorized".
my passport.js file
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const User = require('../models/user');
const config = require('../config/database');
module.exports = function(passport){
let opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = config.secret;
passport.use(new JwtStrategy(opts, (jwt_payload, done) => {
User.getUserById(jwt_payload._doc._id, (err, user) => {
if(err){
return done(err, false);
}
if(user){
return done(null, user);
} else {
return done(null, false);
}
});
}));
}
user model user.js
module.exports.getUserById = function(id, callback){
User.findById(id, callback);
}
routes
router.get('/profile', passport.authenticate('jwt', {session:false}), (req, res, next) => {
res.json({user: req.user});
});
When I google it many suggested to change this line in passport.js
User.getUserById(jwt_payload._doc._id, (err, user) => {
I tried with
User.getUserById(jwt_payload._id, (err, user) => {
User.findById(jwt_payload._id, (err, user) => {
still now i'm getting this same error.
I found out the issue,
In new passport-jwt updates, we have to use
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderWithScheme('jwt');
if you are using opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken(); as your jwtFromRequest then your Authorization header is like
bearer xxxxx.yyyyy.zzzzz
you can check the BEARER_AUTH_SCHEME specified in the extract_jwt.js located in the passport-jwt/lib folder
if you are using opts.jwtFromRequest = ExtractJwt.fromAuthHeaderWithScheme('jwt') as your jwtFromRequest then your Authorization header is like
JWT xxxxx.yyyyy.zzzzz
you can check the LEGACY_AUTH_SCHEME specified in the extract_jwt.js located in the passport-jwt/lib folder
Related
Hello I am quite new to nodejs and this is my first question. I want to add a user login to my express server. So I tried the passport.js example (express-4.x-local-example). Now I tried to store users in my sqlite database. I oriented on other question (node.js passport autentification with sqlite). But that didn't solved my problem. I don't want to create a live application with it, I just want to understand how it works. When I add a user to my database and try to login, I always get directed to /bad.
Here is the code I wrote:
The database.js
const sqlite3 = require("sqlite3");
const sqlite = require("sqlite");
const fs = require("fs").promises;
async function provideDatabase() {
const databaseExists = await fs
.access("./.data/database.db")
.catch(() => false);
const db = await sqlite.open({
filename: "./.data/database.db",
driver: sqlite3.Database
});
if (databaseExists === false) {
await db.exec(
"CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, password TEXT, salt TEXT)"
);
}
return db;
}
module.exports = provideDatabase;
And here is my server.js
var express = require('express');
var passport = require('passport');
var Strategy = require('passport-local').Strategy;
const provideDatabase = require("./database");
const database = provideDatabase();
const LocalStrategy = require('passport-local').Strategy
const bodyParser = require("body-parser");
var app = express();
app.use(bodyParser.json());
var crypto = require('crypto');
function hashPassword(password, salt) {
var hash = crypto.createHash('sha256');
hash.update(password);
hash.update(salt);
return hash.digest('hex');
}
passport.use(new LocalStrategy(async function(username, password, done) {
const db = await database;
db.get('SELECT salt FROM users WHERE username = ?', username, function(err, row) {
if (!row) return done(null, false);
var hash = hashPassword(password, row.salt);
db.get('SELECT username, id FROM users WHERE username = ? AND password = ?', username, hash, function(err, row) {
if (!row) return done(null, false);
return done(null, row);
});
});
}));
passport.serializeUser(function(user, done) {
return done(null, user.id);
});
passport.deserializeUser(async function(id, done) {
const db = await database;
db.get('SELECT id, username FROM users WHERE id = ?', id, function(err, row) {
if (!row) return done(null, false);
return done(null, row);
});
});
app.post('/login', passport.authenticate('local', { successRedirect: '/good',
failureRedirect: '/bad' }));
// Configure view engine to render EJS templates.
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
// Define routes.
app.get('/',
function(req, res) {
res.render('home', { user: req.user });
});
app.get('/login',
function(req, res){
res.render('login');
});
app.get('/logout',
function(req, res){
req.logout();
res.redirect('/');
});
app.get('/profile',
require('connect-ensure-login').ensureLoggedIn(),
function(req, res){
res.render('profile', { user: req.user });
});
app.get("/user", async (request, response) => {
const db = await database;
const results = await db.all("SELECT * FROM users");
response.send(results);
});
app.post("/user", async (request, response) => {
const db = await database;
hashedPassword = hashPassword(request.body.password, request.body.salt)
const created = await db.run(
"INSERT INTO users (username, password, salt) VALUES(?,?,?)",
request.body.username,
hashedPassword,
request.body.salt
);
const user = await db.get("SELECT * FROM users WHERE Id = ?", [
created.lastID
]);
response.status(201).send(user);
});
app.listen(3000);
I am grabbing my code from a project in production. Although it is not using SQLite, the concept should be the same.
I use bcrypt instead of implementing my own crypto. I suggest you do the same to avoid error and security issues in case of misapplication.
I also put the passport logic in a separate file to make the code looks cleaner and avoid confusion.
// Dependencies
const passport = require('passport');
const { Strategy: LocalStrategy, } = require('passport-local');
const bcrypt = require('bcrypt');
// Load model for User_DB
const { User_DB, } = require('../dataBase/dbConnection');
// Winston Logger
const passLog = require('../system/log').get('passportLog');
// Session
// Take in user id => keep the session data small
passport.serializeUser((id, done) => {
done(null, id);
});
// Deserialize when needed by querying the DB for full user details
passport.deserializeUser(async (id, done) => {
try {
const user = await User_DB.findById(id);
done(null, user);
} catch (err) {
passLog.error(`Error Deserializing User: ${id}: ${err}`);
}
});
// Export the passport module
module.exports = (passport) => {
passport.use(new LocalStrategy({ usernameField: 'email', }, async (email, password, done) => {
try {
// Lookup the user
const userData = await User_DB.findOne({ email: email, }, {
password: 1, }); // Return the password hash only instead of the whole user object
// If the user does not exist
if (!userData) {
return done(null, false);
}
// Hash the password and compare it to the hash in the database
const passMatch = await bcrypt.compare(password, userData.password);
// If the password hash does not match
if (!passMatch) {
return done(null, false);
}
// Otherwise return the user id
return done(null, userData.id);
} catch (err) {
passLog.error(`Login Error: ${err}`);
}
}));
};
These options for passport seems to malfunction a lot or exhibit weird behaviors, so I suggest you handle the redirection logic like in my controller.
{ successRedirect: '/good',
failureRedirect: '/bad' }
Login controller logic:
(I am omitting the code here for session storage and made some modifications, but this code should work for what you need)
const login = (req, res, next) => {
//Using passport-local
passport.authenticate('local', async (err, user) => {
//If user object does not exist => login failed
if (!user) { return res.redirect('/unauthorized'); }
//If all good, log the dude in
req.logIn(user, (err) => {
if (err) { return res.status(401).json({ msg: 'Login Error', }); }
// Send response to the frontend
return res.redirect('/good');
});
});
})(req, res, next);
};
The actual route:
// Import the controller
const {login} = require('../controllers/auth');
// Use it in the route
router.post('/auth/login', login);
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
});
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});
When I use 'passport.authenticate('jwt', { session: false })' in a route, it returns 'Unauthorized'. The passport middleware seems not working. I don't know what is wrong! In the header I'm using the 'Authorization: JWT MY_TOKEN'.
app.js file
// ...code
const app = express();
app.use(bodyParser.json());
app.use(passport.initialize());
require('./config/passport')(passport); // passport middleware file
app.use('/api/auth', auth); // routes
app.use('/api/lists', lists); // routes
.
Passport middleware file:
const { Strategy, ExtractJwt } = require('passport-jwt');
const User = require('../models/User');
const keys = require('./keys');
module.exports = (passport) => {
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = 'MYSECRETKEY';
// HERE IS EXECUTED! <<<<<<<<
passport.use(new Strategy(opts, function(jwt_payload, done) {
// HERE IS NOT BEING EXECUTED! <<<<<<<<
User.findOne({_id: jwt_payload.id}, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
return done(null, user);
} else {
return done(null, false);
}
});
}));
}
.
Login route (creating the token):
router.post('/login', (req, res, next) => {
const { email, } = req.body;
User.findOne({ email: email }, { password: 0 }, (errQuery, resUser) => {
const token = jwt.sign({_id: resUser.id}, 'MYSECRETKEY');
const responseData = {
status: true,
token: `JWT ${token}`
};
res.send(responseData);
});
});
The issue is this line:
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
Because you're calling that configuration option, your header needs to look like:
Authorization: Bearer MY_TOKEN
I use Passport-jwt Strategy for authentication in express project,
here is mt passport-jwt config in this directory: /config/passport.js
var JwtStrategy = require('passport-jwt')
.Strategy,
ExtractJwt = require('passport-jwt')
.ExtractJwt;
var User = require(__dirname + '/../models/user');
var config = require(__dirname+ '/database');
module.exports = function(passport) {
console.log("here: passport-jwt");
var opts = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeader();
opts.secretOrKey = config.secret;
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
User.findOne({
id: jwt_payload.id
}, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
done(null, user);
} else {
done(null, false);
}
});
}));
};
and in account route in /routes/account.js directory i call it this way:
var passport = require('passport');
require(__dirname + '/../config/passport')(passport);
router.post('/', passport.authenticate('jwt', {
session: false
}), function(req, res) { ... }
but the problem is passport function for authentication does not execute. and "here: passport-jwt" did not shown.
where is the problem?
Maybe you could try this:
router.get('/', function(req, res) {
passport.authenticate('jwt',
{
session: false
});
});
First of all in app.js, routes must be declared like this:
after adding these lines:
var passport = require('passport');
app.use(passport.initialize());
you should add these lines:
var account = require(__dirname + '/routes/account')(app, express, passport);
app.use('/account', account);
and in the route itself:
module.exports = function(app, express, passport) {
var router = express.Router();
router.post('/', function(req, res) {
passport.authenticate('jwt', function(err, user) {
if (err) {
res.sendStatus(406);
} else {
if (!user) {
res.sendStatus(400);
} else {...}
}
});
}
}
}
my mistake was that is put console.log("here: passport-jwt"); in the first line of module, but in fact passport.use(..) part executing every time!
and the last thing was the findOne part in passport config, passport.use(...) part, when you want to use native id in MongoDB, you should query _id instead of id!
So, the correct code is:
User.findOne({
_id: jwt_payload.id
}, function(err, user) {
...
});