Reputation: 151
I'm setting a server and want to respond the user with an JWT, after he loged in. I'm using passport for now, but don't know how to implement the verification JWT in this case.
Here's the route to log in:
app.route('/login')
.get(users.renderLogin)
.post(users.auth);
When the user loged in, i send his Token with some information:
exports.auth = function (req, res, next){
passport.authenticate('local', {session: false}, (err, user, info) => {
if (err) { return next(err); }
console.log(err);
if (!user) { return res.redirect('/login'); }
req.logIn(user, function(err) {
if (err) { return next(err); }
// Loged in
const userInfo = {
username: user.username,
name: user.name,
age: user.age,
groupid: user.groupid,
email: user.email,
}
const token = jwt.sign(userInfo, process.env.JWT_SEC);
res.json({
user,
token
})
// res.redirect('/');
});
})(req, res, next);
};
After that, i need verify JWT token in every user call. Someone has any tips?
Thank you
Upvotes: 0
Views: 743
Reputation: 192
From passport :
Passport's sole purpose is to authenticate requests, which it does through an extensible set of plugins known as strategies.
Moreover from passport-local :
The local authentication strategy authenticates users using a username and password. The strategy requires a verify callback, which accepts these credentials and calls done providing a user.
Therefore, passport's local strategy will only authenticate requests made with both a username
and a password
provided. Hence the client will have to send these credentials every time it wants to reach the app.
So, to authenticate a request using web tokens you need to provide a login process that sets a JWT. (The client will then have to send the token only, without storing and transmitting the clear password each time)
To do so, there are a lot of packages available on npm. I personaly use and recommend jsonwebtoken
Assuming you have a user model such as
const UserSchema = new mongoose.Schema({
email: {
type: String,
unique: true,
required: true,
maxlength: 254,
trim: true,
match: /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/
// Ref : RFC 5322 compliant regex
// Visualizer : https://regexper.com/#(%3F%3A%5Ba-z0-9!%23%24%25%26'*%2B%2F%3D%3F%5E_%60%7B%7C%7D~-%5D%2B(%3F%3A%5C.%5Ba-z0-9!%23%24%25%26'*%2B%2F%3D%3F%5E_%60%7B%7C%7D~-%5D%2B)*%7C%22(%3F%3A%5B%5Cx01-%5Cx08%5Cx0b%5Cx0c%5Cx0e-%5Cx1f%5Cx21%5Cx23-%5Cx5b%5Cx5d-%5Cx7f%5D%7C%5C%5C%5B%5Cx01-%5Cx09%5Cx0b%5Cx0c%5Cx0e-%5Cx7f%5D)*%22)%40(%3F%3A(%3F%3A%5Ba-z0-9%5D(%3F%3A%5Ba-z0-9-%5D*%5Ba-z0-9%5D)%3F%5C.)%2B%5Ba-z0-9%5D(%3F%3A%5Ba-z0-9-%5D*%5Ba-z0-9%5D)%3F%7C%5C%5B(%3F%3A(%3F%3A(2(5%5B0-5%5D%7C%5B0-4%5D%5B0-9%5D)%7C1%5B0-9%5D%5B0-9%5D%7C%5B1-9%5D%3F%5B0-9%5D))%5C.)%7B3%7D(%3F%3A(2(5%5B0-5%5D%7C%5B0-4%5D%5B0-9%5D)%7C1%5B0-9%5D%5B0-9%5D%7C%5B1-9%5D%3F%5B0-9%5D)%7C%5Ba-z0-9-%5D*%5Ba-z0-9%5D%3A(%3F%3A%5B%5Cx01-%5Cx08%5Cx0b%5Cx0c%5Cx0e-%5Cx1f%5Cx21-%5Cx5a%5Cx53-%5Cx7f%5D%7C%5C%5C%5B%5Cx01-%5Cx09%5Cx0b%5Cx0c%5Cx0e-%5Cx7f%5D)%2B)%5C%5D)
// SFSM : https://en.wikipedia.org/wiki/Finite-state_machine
// Xcrowzz' note : Seems to work for 99.99% of email addresses, containing either local, DNS zone and/or IPv4/v6 addresses.
// Xcrowzz' note : Regex can be targeted for Catastrophic Backtracking (https://www.regular-expressions.info/catastrophic.html) ; See https://www.npmjs.com/package/vuln-regex-detector to check them before someone DDOS your app.
},
password: {
type: String,
required: true,
minlength: 8,
maxlength: 254,
select: false
},
username: {
type: String,
required: false,
unique: true,
maxlength: 254
}
});
module.exports = mongoose.model('User', UserSchema);
Now, it's time to implement the JWT generation and signature, which should be done when a user successfully logs in. Aka, when both the username
and password
provided in the body request matches the DB (Don't forget to compare the clear password with the hash stored using bcrypt.compare
)
const jwt = require('jsonwebtoken');
[...]
exports.logUser = async (req, res) => {
if (!req.body.email || !req.body.password) { return res.status(400).send('Bad Request');}
else {
await UserModel.findOne({ email: req.body.email }, async (err, user) => {
if (err) return res.status(500).send('Internal Server Error');
if (!user) return res.status(404).send('Not Found');
let passwordIsValid = await bcrypt.compare(req.body.password, user.password);
if (passwordIsValid) {
let token = jwt.sign({ id: user._id }, secret, { expiresIn: 86400 }); // 24 Hours
return res.status(200).send({ auth : true, token: token });
} else {
return res.status(404).send('Not Found');
}
}).select('+password'); // Overrides model's 'select:false' property of the password model's property in order to compare it with given plaintext pwd.
}
};
let token = jwt.sign({ id: user._id }, secret, { expiresIn: 86400 });
creates the token based on an id
and a previously defined secret
that should be a very long and random string, the whole integrity and security of the token can be compromised by a weak secret
.
This is the most basic configuration for a JWT, some researches will prove you that this code is not production ready. But in your case, that will do the trick.
Finally, you have to design the authentication middleware that compares the provided token with the one previously signed. Once again, it uses the secret
to compare them.
const secret = (process.env.TokenSuperSecret) ?
process.env.TokenSuperSecret : 'SuperSecret';
const jwt = require('jsonwebtoken');
const passport = require('passport');
const CustomStrategy = require('passport-custom');
passport.use('jwt', new CustomStrategy(async (req, callback, err) => {
const token = req.headers['x-access-token'];
if (!token) return callback(err);
await jwt.verify(token, secret, (err, decoded) => {
if (err) return callback(err);
req.userId = decoded.id;
callback(null, req);
});
}));
exports.isAuthenticated = passport.authenticate('jwt', {
session: false
}, null);
jwt.verify(token, secret, (err, decoded) => {}
returns a promise containing the decoded userId
(that you encoded with jwt.sign
during the login process). Feel free to pass it around your middlewares to know which user is currently performing the request.
So long, and thanks for all the fish !
Upvotes: 2