I use passport jwt strategy and here is my code snippet:
'use strict';
const config = require('../config');
const User = require('../models/user.model');
const passportJWT = require('passport-jwt');
const ExtractJwt = passportJWT.ExtractJwt;
const JwtStrategy = passportJWT.Strategy;
const jwtOptions = {
secretOrKey: config.secret,
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken()
};
const jwtStrategy = new JwtStrategy(jwtOptions, (jwtPayload, done) => {
console.log(jwtPayload);
User.findById(jwtPayload.sub, (err, user) => {
if (err) return done(err, null);
if (user) {
return done(null, user)
} else {
return done(null, false)
}
})
});
exports.jwtOptions = jwtOptions;
exports.jwt = jwtStrategy;
here I'm setting the strategy and everything works correctly. Now I want to create endpoint to add article with UserId retrieved from the token (article has userId String in its schema).
How am I suppose to get userId from the token?
In new JwtStrategy it's already happening because I have payload which allow me to findById and therefore authenticate the call but should I somehow reuse this file or create new named for example getUserId?
The JWT claim that identifies the user presenting the JWT is sub. If understand your code correctly, jwtPayload.sub contains this claim.
If the value of the sub claim contains what you call the user ID, you are done.
If you want to find the user based on a different value, maybe one derived from the sub, you will have to tell us how this different value is related to the claims contained in the JWT.
While signing the JWT (creating the new token), if you would add the user info in the jwt payload something as below:
..
....
const payload = {
userId: user.id,
email: user.email
};
// CREATE A JWT TOKEN
jwt.sign(payload, secretForJWT, { expiresIn: expirationTimeForJWT },
(err, token) => {
....
..
Then you should be able to retrieve the userId field from the decrypted token as below:
const jwtStrategy = new JwtStrategy(jwtOptions, (jwtPayload, done) => {
console.log(jwtPayload);
const userId = jwtPayload.userId; // YOU SHOULD GET THE USER ID FROM THE PAYLOAD
User.findById(jwtPayload.sub, (err, user) => {
if (err) return done(err, null);
if (user) {
return done(null, user)
} else {
return done(null, false)
}
})
});
exports.jwtOptions = jwtOptions;
exports.jwt = jwtStrategy;
This is my way, hope it's helpful:
const jwt = require('jsonwebtoken');
...
const jwt_payload = jwt.decode(yourJwtInString);
const id = jwt_payload._id;
...
Related
I'm making my first API and got a little confused about jwt tokens. The thing is that when a user sign-ins in the app, I check the data in my database and if everything is correct, I send the user jwt token back so they can access their personal information without sending email and password every time. But how can I know if the token user sends back, is the token I sent them? Should I store every token on my server when I send them to the user and then compare them to the ones users send? What is the best practice to handle this issue?
If you're using MongoDB with mongoose you can add a Schema method that generates the jwt and stores it, and another method that checks that token
const mongoose = require('mongoose');
const jwt = require('jsonwebtoken');
const UserSchema = new mongoose.Schema({
.....
})
UserSchema.methods.generateAuthToken = function () {
const user = this;
const token = jwt.sign({ email: user.email }, process.env.JWT_SECRET).toString();
return token;
};
UserSchema.statics.findByToken = function (token) {
const user = this;
const decoded = jwt.verify(token, process.env.JWT_SECRET);
if (decoded.access === 'userEmail') {
return user.findOne({
email: decoded.email
});
} else {
return Promise.reject();
}
};
call user.generateAuthToken(); in your controller after verification
and check in the middleware for the token
const { User } = require('path/to/model');
const authenticate = async (req, res, next) => {
try {
const token = req.headers.jwtToken;
const user = await User.findByToken(token);
if (!user) {
return Promise.reject();
}
req.user = user;
next();
} catch (err) {
return res.status(401).send(err);
}
};
I am writing a application that uses JWTs for user authentication. I am trying to allow the user to submit or edit resources associated ONLY to there ID which is generated when they create their account in MongoDB only after the user has logged in. My instructor suggested to store the user._id within a cookie but not familiar with that process. Any tips on how I could accomplish this?
Here is an example of how my users are registered within the server
app.get('/register', function (req, res) {
res.sendFile(__dirname + '/public/register.html');
});
app.post('/register', async function (req,res) {
try {
req.body.password = await bcrypt.hash(req.body.password, 10);
var newUser = new User(req.body);
newUser.save(function(error,user){
res.redirect('/login');
});
} catch{
res.redirect('/register');
}
// console.log(newUser);
});
Here is the auth process
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const bcrypt = require('bcrypt');
const opts = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = process.env.secret;
module.exports = new JwtStrategy(opts, async (jwt_payload,done)=> {
const user = await (req.body.email);
if( jwt_payload.email === req.body.email){
return done(null, true)
}
try {
if (await bcrypt.compare(password, user.password)){
return done(null, user);
} else {
return done(null, false, { message: "Password is incorrect"});
}
} catch (e) {
return done(e);
}
})
Here is logging in
app.post('/login', async (req, res) => {
const { username, password } = req.body
const user = await User.findOne({ username }).lean()
if (!user) {
return res.sendFile(__dirname + '/public/401.html');
}
if (await bcrypt.compare(password, user.password)) {
// the username, password combination is successful
const token = jwt.sign(
{
id: user._id,
username: user.username
},
process.env.secret
)
console.log(user._id);
//I need to find a way to send over the user._id to the client side so it can be stored later to allow the user to only update their resources and to view their resources
return res.redirect('/welcome.html');
}
res.sendFile(__dirname + '/public/401.html');
})
EDIT: I am not not wanting to store the token unless that is the only way to pass the user._id to the client side due to security reasoning.
Technologies I am using are, Node.js, Express.js, Mongoose, MongoDB, JWT, passport, jwt-simple, bcrypt, methodOverride, and secrets for resetting the users password and creating the user are stored in an .env.
I am learning Node.js and making my first project. I am using JWT for user authentication on the project. I am using cookies to send JWT and on the server-side I am able to extract the cookie and correctly obtain the expected payload. However I am facing an unexpected behavior as in the authentication function I am always getting signed up by one particular user.
login route:
usersRouter.route('/login')
.post(passport.authenticate('local'), (req,res) => {
console.log('Generating token for: ', req.user._id);
var token = authenticate.getToken({_id: req.user._id});
res.statusCode = 200;
res.cookie('jwt', token, {signed: true});
res.redirect('/');
})
authentication code:
exports.getToken = function(user){
return jwt.sign(user, config.secretKey, {
expiresIn: 3600
});
};
var extractFromCookie = function (req)
{
var token = null;
if(req && req.signedCookies)
token = req.signedCookies.jwt;
return token;
}
var opts = {};
opts.jwtFromRequest = extractFromCookie;
opts.secretOrKey = config.secretKey;
exports.jwtPassport = passport.use(new JwtStrategy(opts, (jwt_payload, done) => {
console.log("Jwt Payload: ", jwt_payload);
User.findOne({id: jwt_payload.sub}, (err,user) => {
if(err){
return done(err,false);
}
else if(user){
console.log('Found user is: ', user);
return done(null, user);
}
else
return done(null,false);
});
}));
exports.verifyUser = passport.authenticate('jwt', {session:false});
Printing in the console
Jwt Payload: { _id: '5f49430180e4092eac7962be', iat: 1599066593, exp: 1599070193 }
Found user is:
{
_id: 5f4904d106efe130084808a4,
.
.
. other details
.
}
How is the search resulting in a user with different id? I am not able to figure out why is this happening. Please help!
Edit: getToken is a function to generate token from the id of the user sent from login router.
It was just some small mistakes after all.
The only thing that was needed to be changed is as follows:
User.findOne({id: jwt_payload.sub}, (err,user) =>
As #Michael pointed out, there's no field name sub in jwt_payload, we need to put _id instead. Also, in the User schema, it's _id field and not id field.
Made these changes, and viola!
You accidentally access to undefined property sub in jwt_payload so you actually send undefined as value to the findOne function.
This should work.
User.findById(jwt_payload._id, (err,user) => {
I am using express & jwt-simple to handle login/register & authenticated requests as a middleware api. I'm trying to create a .well-known endpoint so other api's can authenticate request based on token send in.
Here's my strategy:
module.exports = function() {
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeader();
opts.secretOrKey = securityConfig.jwtSecret;
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
// User.where('id', jwt_payload.id).fetch({withRelated: 'roles'})
console.log('jwt_payload', jwt_payload)
User.where('id', jwt_payload.id).fetch()
.then(user => user ? done(null, user) : done(null, false))
.catch(err => done(err, false));
}));
};
Here's my login route:
router.post('/login', function(req, res) {
const {username, password} = req.body;
Promise.coroutine(function* () {
const user = yield User.where('username', username).fetch();
if(user) {
const isValidPassword = yield user.validPassword(password);
if (isValidPassword) {
let expires = (Date.now() / 1000) + 60 * 30
let nbf = Date.now() / 1000
const validatedUser = user.omit('password');
// TODO: Verify that the encoding is legit..
// const token = jwt.encode(user.omit('password'), securityConfig.jwtSecret);
const token = jwt.encode({ nbf: nbf, exp: expires, id: validatedUser.id, orgId: validatedUser.orgId }, securityConfig.jwtSecret)
res.json({success: true, token: `JWT ${token}`, expires_in: expires});
} else {
res.status(401);
res.json({success: false, msg: 'Authentication failed'});
}
} else {
res.status(401);
res.json({success: false, msg: 'Authentication failed'});
}
})().catch(err => console.log(err));
});
Here's my .well-known route:
router.get('/.well-known', jwtAuth, function(req, res) {
// TODO: look over res.req.user. Don't seem to be the way to get those parameters.
// We dont take those parameters from the decrypted JWT, we seem to grab it from the user in DB.
const { id, orgId } = res.req.user.attributes;
console.log("DEBUG: userId", id)
console.log("DEBUG: USER", res.req.user)
res.json({
success: true,
userId: id,
orgId
});
});
here's my jwtAuth() function:
const passport = require('passport');
module.exports = passport.authenticate('jwt', { session: false });
How would I actually get the token in the route function & decrypt it? All this does right now which works is that it authenticates if true however I need to be able to decrypt the token to send back the stored values. I'm not sure what res.req.user.attributes comes from, is this the token?
Take a look at passport-jwt and in your passport-config (or wherever you initialize passport) setup JWT Strategy:
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const jwtAuth = (payload, done) => {
const user = //....find User in DB, fetch roles, additional data or whatever
// do whatever with decoded payload and call done
// if everything is OK, call
done(null, user);
//whatever you pass back as "user" object will be available in route handler as req.user
//if your user does not authenticate or anything call
done(null, false);
}
const apiJwtOptions: any = {};
apiJwtOptions.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
apiJwtOptions.algorithms = [your.jwt.alg];
apiJwtOptions.secretOrKey = your.jwt.secret;
//apiJwtOptions.issuer = ???;
//apiJwtOptions.audience = ???;
passport.use('jwt-api', new JwtStrategy(apiJwtOptions, jwtAuth));
If you want just decoded token, call done(null, payload) in jwtAuth.
Then in your route files when you want to protect endpoints and have info about user, use as:
const router = express.Router();
router.use(passport.authenticate('jwt-api', {session: false}));
And in handler you should have req.user available. It is configurable to what property of req you store data from auth, req.user is just default.
I am using passport-jwt to generate my tokens but I noticed that the tokens never expire, is there any way to invalidate a particular token according to a rule set for me, something like:
'use strict';
const passport = require('passport');
const passportJWT = require('passport-jwt');
const ExtractJwt = passportJWT.ExtractJwt;
const Strategy = passportJWT.Strategy;
const jwt = require('../jwt');
const cfg = jwt.authSecret();
const params = {
secretOrKey: cfg.jwtSecret,
jwtFromRequest: ExtractJwt.fromAuthHeader()
};
module.exports = () => {
const strategy = new Strategy(params, (payload, done) => {
//TODO: Create a custom validate strategy
done(null, payload);
});
passport.use(strategy);
return {
initialize: function() {
return passport.initialize();
},
authenticate: function() {
//TODO: Check if the token is in the expired list
return passport.authenticate('jwt', cfg.jwtSession);
}
};
};
or some strategy to invalidate some tokens
The standard for JWT is to include the expiry in the payload as "exp". If you do that, the passport-JWT module will respect it unless you explicitly tell it not to. Easier than implementing it yourself.
EDIT
Now with more code!
I typically use the npm module jsonwebtoken for actually creating/signing my tokens, which has an option for setting expiration using friendly time offsets in the exp element of the payload. It works like so:
const jwt = require('jsonwebtoken');
// in your login route
router.post('/login', (req, res) => {
// do whatever you do to handle authentication, then issue the token:
const token = jwt.sign(req.user, 's00perS3kritCode', { expiresIn: '30m' });
res.send({ token });
});
Your JWT Strategy can then look like what you have already, from what I see, and it will automatically respect the expiration time of 30 minutes that I set above (obviously , you can set other times).
You can use the following strategy to generate JWT-token with expiration limit of 1 hr.
let token = jwt.sign({
exp: Math.floor(Date.now() / 1000) + (60 * 60),
data: JSON.stringify(user_object)
}, 'secret_key');
res.send({token : 'JWT '+token})
I created a document in the database that stores the generated tokens and added an expiration date, when the user makes the request check if the token is expired or no.
This is verify strategy that I used.
/* ----------------------------- Create a new Strategy -------------------------*/
const strategy = new Strategy(params, (payload, done) => {
const query = {
token: jwtSimple.encode(payload, credentials.jwtSecret),
expires: {$gt: new Date()}
};
TokenSchema.findOne(query, (err, result) => {
if (err) done(err, null);
if (!result) done(null, null);
done(null, payload);
});
});
passport.use(strategy);
/* -------------------------------------------------------------------------------*/
It's work for me.