JWT - Retrieving Token - mongodb

I am trying to figure out how do I get the token id or a new token for an existing user using JWT. The goal is to take email and password, find it in the mongo DB, and then send back to the client the token, then store the token for the session.
Currently when registering I encrypt the password and store the email, name, and password in a mongoDB.
Based on what I have been learning, the actual token is not stored in the mongoDB, but the token is generated from the _id, which I cant pull for some reason to just resign and get the token back but no luck. Any advice is greatly appreciated its for a personal project.
User.create({
name : req.body.name,
email : req.body.email,
password : req.body.password,
},
function (err, user) {
if (err) return res.status(500).send("There was a problem registering the user.")
// create a token
var token = jwt.sign({ id: user._id }, config.secret);
res.status(200).send({ auth: true, token: token });
});

You can try user._id.toString() or user.id instead of user._id to retrieve the user id.
You may get some idea from the code below how you can use JWT token for verification purpose.
login: (req, res) => {
const { name, password } = req.body;
mongoose.connect(connUri, { useNewUrlParser: true }, (err) => {
let result = {};
let status = 200;
if(!err) {
User.findOne({name}, (err, user) => {
if (!err && user) {
// We could compare passwords in our model instead of below as well
bcrypt.compare(password, user.password).then(match => {
if (match) {
status = 200;
// Create a token
const payload = { user: user.name };
const options = { expiresIn: '2d', issuer: 'anshukumar.me' };
const secret = process.env.JWT_SECRET;
const token = jwt.sign(payload, secret, options);
// console.log('TOKEN', token);
result.token = token;
result.status = status;
result.result = user;
} else {
status = 401;
result.status = status;
result.error = `Authentication error`;
}
res.status(status).send(result);
}).catch(err => {
status = 500;
result.status = status;
result.error = err;
res.status(status).send(result);
});
} else {
status = 404;
result.status = status;
result.error = err;
res.status(status).send(result);
}
});
} else {
status = 500;
result.status = status;
result.error = err;
res.status(status).send(result);
}
});
}
You can also check this GitHub repo which has implemented JWT Token in Node JS.
https://github.com/kumaranshu72/JWT-nodeJS
You may also follow the following tutorial to get a good idea of how to implement JWT authentication : https://scotch.io/tutorials/authenticate-a-node-es6-api-with-json-web-tokens

I think you can access the id without _
User.create({
name : req.body.name,
email : req.body.email,
password : req.body.password,
},
function (err, user) {
if (err) return res.status(500).send("There was a problem registering the user.")
// create a token
var token = jwt.sign({ id: user.id }, config.secret);
res.status(200).send({ auth: true, token: token });
});
user.id instead of user._id

Related

Persist session id in passport-saml login login callback

I'm using passport-saml and express-session. I login with my original session id but when the idp response reach the login callback handler, I have another sessionId. Also, since my browser has the session cookie with the original session id, it cannot use the new session id in the login callback, so I cannot authenticate.
interface SamlProvider {
name: string;
config: SamlConfig;
}
const providers: SamlProvider[] = [
{
name: process.env.SAML_ENTITY_ID_1!,
config: {
path: "/login/callback",
entryPoint: process.env.SAML_SSO_ENDPOINT_1,
issuer: process.env.SAML_ENTITY_ID_1,
cert: process.env.SAML_CERT_1!,
...(process.env.NODE_ENV === "production" && { protocol: "https" }),
disableRequestedAuthnContext: true,
},
},
{
name: process.env.SAML_ENTITY_ID_2!,
config: {
path: "/login/callback",
entryPoint: process.env.SAML_SSO_ENDPOINT_2,
issuer: process.env.SAML_ENTITY_ID_2,
cert: process.env.SAML_CERT_2!,
...(process.env.NODE_ENV === "production" && { protocol: "https" }),
disableRequestedAuthnContext: true,
},
},
];
export const samlStrategy = (sessionStore: session.Store) =>
new MultiSamlStrategy(
{
passReqToCallback: true, // makes req available in callback
getSamlOptions: function (request, done) {
// Find the provider
const relayState = request.query.RelayState || request.body.RelayState;
const provider = providers.find((p) => p.name === relayState);
if (!provider) {
return done(Error("saml identity provider not found"));
}
return done(null, provider.config);
},
},
async function (
req: Request,
profile: Profile | null | undefined,
done: VerifiedCallback
) {
if (profile && profile.nameID) {
const { nameID, nameIDFormat } = profile;
const email = profile[
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
] as string;
const firstName = profile[
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"
] as string;
const lastName = profile[
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"
] as string;
// Check if user is in risk database
const user = await myUserService.getByEmail(email);
if (!user) return done(new UserNotFoundError());
// If user has existing session, delete that existing session
sessionStore.all!((err: any, obj: any) => {
const sessions = obj as Array<{
sid: string;
passport?: { user?: { email?: string } };
}>;
const existingSess = sessions.find(
(sess) =>
sess.passport &&
sess.passport.user &&
sess.passport.user.email &&
sess.passport.user.email === email
);
if (existingSess && existingSess.sid) {
sessionStore.destroy(existingSess.sid, (err: any) => {
console.error(err);
return done(Error("failed to delete existing user session"));
});
}
});
return done(null, { nameID, nameIDFormat, email, firstName, lastName });
}
return done(Error("invalid saml response"));
}
);
Here's my login and login callback
app.post("/login/callback", async function (req, res, next) {
passport.authenticate("saml", (err: any, user: ISessionUser) => {
if (err) {
// TODO: Handle specific errors
logger.info({ label: "SAML Authenticate Error:", error: err });
return next(err);
} else {
req.logIn(user, (err) => {
if (err) {
logger.info({ label: "Login Error:", data: err });
return next(err);
}
res.redirect("/");
});
}
})(req, res, next);
});
app.get(
"/auth/saml/login",
passport.authenticate("saml", { failureRedirect: "/", failureFlash: true }),
function (req, res) {
res.redirect("/");
}
);
I experienced a similar issue using Microsoft 365 for authentication. The answer was to pass a randomly-generated nonce to the authentication request - this gets passed back to your app in the callback request. With SAML I think it depends on the provider whether they support such a flow, but it is good practice. You can also use a cookie to maintain state in your app, instead of, or additional to, the session id.

What does it mean if a post with status 200 is left waiting?

I am doing a POST to an enpoint to authenticate users. The endopoint is "/user/login". I make a post and receive status code 200 but the Postman, and also my client, are waiting for the RES object that does not arrive.
This is a screenshot of the API call through Postman:
This is controler in server side:
router.post(
'/user/login',
passport.authenticate('local'),
UserCtrl.getLogin,
)
getLogin = (req, res, next) => {
console.log("req: ", req.body)
console.log('logged in', req.user);
var userInfo = {
username: req.user.username
};
res.json(userInfo)
}
The console prints the lines in the controller, and the user is effectively authenticated, for example:
req: { username: 'fedex', password: 'fedex' }
logged in {
_id: new ObjectId("62a8b00f468c563699d7dfc2"),
username: 'fedex',
password: '$2a$10$cdbh0oCBNpHxxwebsvArLOAFwetVAh5LTnQwk1Lg9kjWkjAWhfxym',
__v: 0
}
probably the problem is in the invocation of the local passport strategy, but I only do the standard:
const strategy = new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, (err, user) => {
if (err) {
return done(err)
}
if (!user) {
return done(null, false, { message: 'Incorrect username' })
}
if (!user.checkPassword(password)) {
return done(null, false, { message: 'Incorrect password' })
}
return done(null, user)
})
}
)
EDITED: If I remove the middleware where the passport.authenticate('local') is invoked, and incorporate the authentication functionality directly in the controller, it works. But what is wrong with calling passport in the route?
BEFORE (does not work):
router.post(
'/user/login',
passport.authenticate('local'),
UserCtrl.getLogin
)
AFTER (adding passport authentication inside the controller, it work)
router.post(
'/user/login',
UserCtrl.getLogin
)

How do you validate password using mongoose for mongoDB in an express app for logging in a user?

I am trying to have a user log in by their email and password. MongoDb docs shows hashing the password with bcrypt in the user model. It also provides a nice way to validate the password in the model as well. My problem is how to I use that validation from the "controller"? I am very aware "if (req.body.password === user.password)" will not work because one is hashed and the other is not.
I have been searching for answers for hours and can't seem to find that connection on how I use that "UserSchema.methods.comparePassword" method in my post request to log in. This isn't completely a real log in, just trying to get the password to validate and send back a key once logged in. Here are the docs: https://www.mongodb.com/blog/post/password-authentication-with-mongoose-part-1
// This is my UserModel
let mongoose = require('mongoose'),
Schema = mongoose.Schema,
bcrypt = require('bcrypt'),
SALT_WORK_FACTOR = 10
var hat = require('hat');
let UserSchema = new Schema({
email: {
type: String,
required: true,
index: {
unique: true
}
},
password: {
type: String,
require: true
},
api_key: {
type: String
}
});
UserSchema.pre('save', function(next) {
var user = this;
// only hash the password if it has been modified (or is new)
if (!user.isModified('password')) return next();
// generate a salt
bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
if (err) return next(err);
// hash the password using our new salt
bcrypt.hash(user.password, salt, function(err, hash) {
if (err) return next(err);
// override the cleartext password with the hashed one
user.password = hash;
user.api_key = hat();
next();
});
});
});
UserSchema.methods.comparePassword = function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if (err) return cb(err);
cb(null, isMatch);
});
};
module.exports = mongoose.model('user', UserSchema);
// This is the sessions.js
let UserModel = require('../../../models/user.model');
var express = require('express');
var router = express.Router();
router.post('/', (req, res, next) => {
UserModel.findOne(
{
$or: [
{ email : req.body.email }
]
}
)
.then(user => {
if (req.body.password === user.password) {
res.setHeader("Content-Type", "application/json");
res.status(200).send(JSON.stringify({
"api_key": `${user.api_key}`
}));
} else {
res.status(404).send("Incorrect email or password")
}
})
.catch(error => {
res.setHeader("Content-Type", "application/json");
res.status(500).send({error})
})
})
module.exports = router
If I just find user by email, everything works fine. Just need to figure out how to use the compare password method in the user model. Thanks!
Maybe have something like this in your model:
User = require('./user-model');
.......
User.findOne({ username: 'jmar777' }, function(err, user) {
if (err) throw err;
user.comparePassword('Password123', function(err, isMatch) {
if (err) throw err;
console.log('Password123:', isMatch); // -> Password123: true
});
........
Other resources:
http://devsmash.com/blog/password-authentication-with-mongoose-and-bcrypt
https://www.abeautifulsite.net/hashing-passwords-with-nodejs-and-bcrypt
https://medium.com/#mridu.sh92/a-quick-guide-for-authentication-using-bcrypt-on-express-nodejs-1d8791bb418f
Hope it helps!

Sails js,find user using either email or username

I am trying to login user using either username or email i.e user can enter either email or password and should be able to log in. However, so far I am able to use one of them.
Here is the code tried so far.
authenticate: function (req, res) {
var username = req.body.username;
var email = req.body.email;
users.findOne({
or: [
{username: username},
{email: email}
]
}).exec(function(err, user) {
console.log(user);
if (err) {
return res.json({err});
} else if (!user) {
var err = new Error('User not found.');
err.status = 401;
return res.json({err});
} else {
require('bcrypt').compare(req.body.password, user.password, function(err, result) {
if(result === true) {
return res.json({user});
} else {
return res.json({err});
}
});
}
});
}
Though sails waterline is not as powerful as direct access to any one db, it can do what you want here and much more. The docs give some good examples of what's possible.
I don't know exactly how you are passing in the username or email to your request object, but something like this should work:
authenticate: function(req, res) {
var username = req.param('username'); // or however you get this
var email = req.param('email'); // or however you get this
Users.findOne({
or: [{username: username}, {email: email}]
}).exec(function(err, user) {
// handle the error, or make use of found user...
});
}
You can also handle the case where user hands you an input that could be either a username or email...
var identifier = req.param('identifier'); // could be a username or an email
And then modify your or array to:
or: [{username: identifer}, {email: identifier}]
if you use the action 2 style, you can do
```
var identifier = inputs.identifier;
var userRecord = await User.findOne({
or : [{username: identifier}, {email: identifier}]
});

Understanding session in sailsJs with Passport

I have had many problems, when I want to get information from user model. I read some solutions, but I didnt understand.
This is my code:
* AuthController
var passport = require('passport');
module.exports = {
_config: {
actions: false,
shortcuts: false,
rest: false
},
login: function(req, res) {
passport.authenticate('local', function(err, user, info) {
if ((err) || (!user)) {
return res.send({
message: info.message,
user: user
});
}
req.logIn(user, function(err) {
if (err) res.send(err);
return res.send({
message: info.message,
user: user
});
});
})(req, res);
},
logout: function(req, res) {
req.logout();
res.redirect('/');
},
signup: function (req, res) {
var data = req.allParams();
User.create({email:data.email,password:data.password,name:data.name}).exec(function(error,user){
if(error) return res.negotiate(err);
if(!user)return res.negotiate(err);
return res.ok();
});
}
};
*view
<h1>List of my dates</h1>
<h1><%= email %></h1>
<h1><%= req.user.name %></h1>
*model
attributes: {
email: {
type: 'email',
required: true,
unique: true
},
password: {
type: 'string',
minLength: 6,
required: true
},
toJSON: function() {
var obj = this.toObject();
delete obj.password;
return obj;
}
},
beforeCreate: function(user, cb) {
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(user.password, salt, function(err, hash) {
if (err) {
console.log(err);
cb(err);
} else {
user.password = hash;
cb();
}
});
});
}
};
Only works if I use res.render('view', {email: req.user.email}) but, I would like to use the user data in many views. I cant write methods with Current user params, becouse dont work.
Thanks.
It is unclear to me what your actual problem is or what the question actually is but I will try to help.
Look here:
login: function(req, res) {
passport.authenticate('local', function(err, user, info) {
if ((err) || (!user)) {
return res.send({
message: info.message,
user: user
});
}
...
})(req, res);
},
There you are adding data (locals) to the ejs and the values are message and user so in the ejs you must reference it as this, so you will use user.name and not req.user.name? I'm not sure why you're binding the (req, res) either.
It's confusing because your ejs uses the email value but I don't see it there as a local so maybe thats your problem, it must be defined?
Consider the following simple example:
// User Controller
// GET request /signin
// The signin form
signin(req, res) {
// Load the view from app/views/*
return res.view('signin', {
title: 'Sign In'
});
},
// POST request to /signin
// This was posted from the signin form
// Use io.socket.post(...) to do this from the signin form
// Can use window.location.replace('/account') on successful request
authenticate(req, res) {
// The data posted, email and password attempt
var data = req.allParams();
// Does it match?
User.findOne({
email: data.email,
// This is stupid, don't ever use plain text passwords
password: data.password
})
.exec(function(err, user) {
// Server related error?
if (err) res.serverError(err.message);
// No user was found
if (!user) res.badRequest('Username or password not found');
// Sign the user in
req.session.userId = user.id;
// User was found
res.ok();
});
},
// GET request to /account
// Displays the users information
// Can use policies to ensure that only an authenticated user may access their own account information
account(req, res) {
// If the user is not signed in
// This is an alternative to using the sails policy isLoggedIn
if (!req.session.userId) res.redirect('/signin');
// Get the users details
User.findOne({
id: req.session.userId
})
.exec(function(err, user) {
// Server related error?
if (err) res.serverError(err.message);
// No user was found
if (!user) res.redirect('/signin');
// Load the ejs file that displays the users information
return res.view('account/index', {
title: 'Account Information',
user: user
});
});
},
// Account View
<p>Email: {{user.email}}</p>
<p>Password: {{user.password}}</p>
Check this out if you want to deal with password encryption: http://node-machine.org/machinepack-passwords
And this if you want to deal with the strength tests (when the user sets the password): https://www.npmjs.com/package/owasp-password-strength-test
This is as passport seems overkill if you're only doing local authentication?