I have a node.js login system with passport but I am trying to figure out how to log in a user with either their username or email. I am only able to log in the user with email or username seperately. I don't know how to write the code to cater for both their username and email. So if a user wants to login with username, they can or if the wish to use their email, they also can. Here is my localstrategy code in my users.js file:
passport.use(new LocalStrategy(
function(email, password, done) {
User.getUserByEmail(email, function(err, user, next){
if(err) throw err;
if(!user){
return done(null, false, {message: 'Unknown user'});
}
User.comparePassword(password, user.password, function(err, isMatch){
if(err) throw err;
if(isMatch){
return done(null, user);
} else {
return done(null, false, {message: 'Invalid password'});
}
});
});
}));
And here's my module.exports in my user.js:
module.exports.getUserByEmail = function(email, callback){
var query = {email: email};
User.findOne(query, callback);
}
module.exports.getUserById = function(id, callback){
User.findById(id, callback);
}
module.exports.comparePassword = function(candidatePassword, hash, callback){
bcrypt.compare(candidatePassword, hash, function(err, isMatch) {
if(err) throw err;
callback(null, isMatch);
});
}
The above code only allows user to login with their email. I want users to have the opportunity to login with either their email or username.
You can use getUserById
inside the callback of getUserByEmail
so that both of the queries run for either email
or username
. If you specify email
, then emailOrUserName
will have the email
value and then User.getUserByEmail
will return the user and next it will proceed with comparePassword
.
And if you have username
then emailOrUserName
will have the username
value and then User.getUserByEmail
will not return the user so, it executes User.getUserById
and if the user is found there then it will proceed with User.comparePassword
else it will return Unknown user
passport.use(new LocalStrategy(
function(emailOrUserName, password, done) {
User.getUserByEmail(emailOrUserName, function(err, user, next){
if(err) throw err;
if(!user){
User.getUserById(emailOrUserName, function(err, user, next){
if(err) throw err;
if(!user){
return done(null, false, {message: 'Unknown user'});
}
}
User.comparePassword(password, user.password, function(err, isMatch){
if(err) throw err;
if(isMatch){
return done(null, user);
} else {
return done(null, false, {message: 'Invalid password'});
}
});
});
}));
Expect both username
and password
within your authentication middleware and then proceed with whatever value you have found as conditions to find the user.
Middleware example:
function authenticateUser(req, res, done) {
let username = req.body.username,
password = req.body.password,
email = req.body.email;
let conditions = !!username ? {username: username} : {email: email};
UserModel.findOne(conditions, (err, user) => {
if (err) return done(err);
if (!user) return done(new Error('Incorrect username or email'));
return user.comparePassword(password, user.password)
.then(match => {
if (match) return done();
else return done(new Error('Incorrect password'));
})
.catch(error => {
if (error) return done(new Error(`Unable to validated password. - ${error}`));
});
});
}
Now, a front-end developer — with the right documentation — can now actually use either the username
, email
or both (you will need a bit of JavaScript for both) when building login forms using your endpoint. Here is an example of using both:
HTML:
<form id="login-form" method="POST" action="/login">
<input id="username-or-email" type="text" placeholder="Username or Email" required/>
<input type="password" name="password" placeholder="Password" required/>
<input type="submit"/>
</form>
JavaScript:
// select the form element
let loginForm = document.querySelector('#login-form');
// add a form handler on submit
loginForm.addEventListener("submit", formHandler);
// validate and the set name attribute as appropriate
function formHandler() {
/** W3C Email regex: (RFC5322) */
const email_regex = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
/** Must starts with a letter then can include underscores (_) & hyphens (-) */
const username_regex = /^[a-zA-Z][\w-]+$/;
let input = document.querySelector('#username-or-email');
if (email_regex.test(input.value)) {
// it is an email, send the value as an email
input.setAttribute("name", "email");
} else if (username_regex.test(input.value)) {
// it is a username, send the value as a username
input.setAttribute("name", "username");
} else {
// invalid email or username format, return an error message to user
}
}
So you get to validate and set your dynamic input at the same time. Keep in mind that the regular expression should match your username and email data model as much as possible.
Do this in local strategy
function(username, password, done) {
var criteria = (username.indexOf('@') === -1) ? {username: username} : {email: username};
User.findOne(criteria, function (err, user) { //implementation }
Just got into this situation, and I though I'll share my final code to verify user using either username or email :
userSchema.statics.findByCredentials = async credentials => {
const {
email = undefined,
username = undefined,
password = undefined
} = credentials
if ((!!email && !!username) || (!email && !username)) {
throw new Error('Should provide either email or username.')
}
if (!password) {
throw new Error('Password is required.')
}
const user = await User.findOne(email ? { email } : { username })
if (!user) {
throw new Error('Credentials are invalid!')
}
if (!bcrypt.compare(password, user.password)) {
throw new Error('Credentials are invalid!')
}
return user
}
So I am using this function to verify if the user provided valid credentials, and from my handler, I call the function on the model class.
I add little more on the above answer shared by
user7153178: Mine is related to the backend, using
context.
inside you Login component in the frontend try to
have these
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
then you will be needing this function on your
form:
const handleSubmit = () => {
const user = {
email,
password,
};
if (email === "" || password === "") {
setError("Please fill in your credentials");
} else {
loginUser(user, context.dispatch);
}
};
this is one of the input as part of your form
<Input
placeholder={"Email or Phone Number"}
name={"email"}
id={"email"}
value={email}
onChangeText={(text) =>
setEmail(text.toLowerCase())}
/>
In the backend write your post like that:
router.post("/login", async (req, res) => {
const user = await User.findOne(
{ $or: [{ email: req.body.email }, { phone:
req.body.email }] }
);
if (!user) {
return res.status(400).send("The user not found");
}
if (user && bcrypt.compareSync(req.body.password,
user.passwordHash)) {
const token = jwt.sign(
{
userId: user.id,
isAdmin: user.isAdmin,
},
secret,
{ expiresIn: "1d" }
);
res.status(200).send({ user: user.email, token:
token });
} else {
res.status(400).send("password is wrong!");
}
});
The code below can be used to allow your users to login using either their username/password
if (!req.body.username && !req.body.email) {
res.status(400).send({ message: "All input is required, username/email is missing", status: false });
return;
}
if (!req.body.password) {
res.status(400).send({ message: "All input is required, password is missing", status: false });
return;
}
let whereClause = {};
if (req.body.email) {
whereClause.email = req.body.email;
} else if (req.body.username) {
whereClause.username = req.body.username.toLowerCase();
}
const user = await User.findOne({ where: whereClause });
// If user not found
if (!user) {
return res.status(404).send({ message: "User not found", status: false });
}
You can wrap it in a try-catch block for proper error handling
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.