Authentication ๐๏ธ is an integral part of building products (at least for the products that require it, not all products require authentication you know ๐). Software engineering over the years has grown giving us various options to keep applications and user data secured, From salting to hashing there are various ways to secure user data (especially passwords) in any application.
So let's talk about salting, shall we? ๐จโ๐ซ
What is Salting?
Generally, salting is the process of adding unique random strings as top-layer security for a user password before the password gets hashed (We would talk about hashing in a bit okay ๐?), the intention here is to create an extra layer of security over the original password(Think about it as a veil of some sort) before the password is hashed.
And hashing ๐?
Hashing is a cryptographic algorithm that converts the password into a fixed number of characters or a hash. (This is a basic way to understand what hashing is about, though it's a bit more technical than this)
By cryptographic I mean a one-way communication technique from the sender to receiver, this is so because the data in question is a sensitive one
Password Encryption with Node.js ๐ป
Salting and hashing can be achieved with a range of programming languages from Java, to Go to Python and Javascript (Using Node.js), but I would be explaining how to get this done using node.js in the most simplified way possible.
For this, we would be using mongoose(this package would allow us to create a schema for our user)
Think of a schema as a model that would include every parameter that would be needed to sign-in an existing user or create a new user (things like name, email, password and all) for our imaginary application (it's imaginary init ๐?)
Also, we would be using bcrypt (this package would allow for hashing and salting of our user password)
Now let's write some code ๐ป, first we create our user schema. Let's create a .js file, say user.js file.
const mongoose = require('mongoose');
const userSchema = mongoose.Schema({
name:{
type: String,
required: true,
trim: true,
},
email:{
type: String,
required: true,
trim: true,
validate:{
validator:(value) =>{
//regex for email validation
const re = /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
return value.match(re);
},
message: 'Please enter a valid email address',
},
},
password:{
required: true,
type: String,
validate:{
validator: (value)=>{
return value.length > 6;
},
message : "Please enter a minimum of six characters for your password",
}
},
})
const User = mongoose.model('User', userSchema);
module.exports = User;
From the code snippet above you see that our application is taking three parameters which are name, email, and password.
๐NB: You notice regex is used to check if the email parameter follows the right sequence in the validate function (I would write about regex in a bit however if you want to verify an email address pattern when creating a schema for your node.js application you can use the code snippet above)
Now to validate our password (The cool part ๐), let's create another .js file say auth.js (auth.js because here we manage everything around authentication, and also handle the logic for salting)
const express = require("express");
const User = require("../models/user");
const bcryptjs = require("bcryptjs");
const authRouter = express.Router();
authRouter.post("/api/signup", async (req, res) => {
try {
const { name, email, password } = req.body;
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({ msg: "User with same email exists!!!" });
}
const hashedPassword = await bcryptjs.hash(password, 8);
let user = new User({
email,
password: hashedPassword,
name,
});
user = await user.save();
res.json(user);
} catch (e) {
res.status(500).json({ error: e.message });
}
});
Let's assume we have an imaginary API that allows for a post request (this is already defined with authRouter.post as defined in the code snippet above).
The authRouter was created using the express package thats aids building API's with node.js, you see the import statement const express = require("express") for express package
and also the express.Router is created, this allows for creation of different API routes and we assign the express.Router to the variable authRouter as you can see in the import statement const authRouter = express.Router() and in the code snippet above we are creating a post request API route using the authRouter.post and the route is defined as "/api/signup"
the userSchema is also imported into our auth.js file (it is defined as const User = require("../models/user"))
From the try-and-catch statement declared you would notice the following
Getting the following parameters (name, email, password) from the client (Think about this as a user trying to sign in or signup) but in this case, the user is trying to signup as defined by the route.
Then we create a variable called existingUser to check if a user already exists in our database(our imaginary database you know ๐, ) using the .findOne to query the database using the email variable to check if that user already exists, and if the user exists the response says User with same email exists!!!
If such a user doesn't exist a new user is created and the user's password is hashed as salting happens in the background using the brycpt package (const bcryptjs = require("bcryptjs")) and the password is saved as hashedPassword as seen in the code snippet above.
๐ The hash function of brycpt package
function hash(s: string, salt: string | number)
Asynchronously generates a hash for the given string.
@param
s
โ String to hash@param
salt
โ Salt length to generate or salt to use
The hash function takes two parameters namely
String: This is the original password to be hashed
Salt: this defines the length of salt to be generated
From the code snippet above you see that the string to be hashed is named password and the length of salt is defined as 8, afterward, the password is hashed and you see that hashedPassword is stored as password in the instance of the userSchema created as the variable user. Once this is done the new account is created and the user data is saved and some error handling is done in case there any error occurs.
Now let's go through the image below (the API in the image below follows the logic of the code we've been working with, and I tried testing the API with thunder-client ) you notice how the password parameter looks like gibberish on the other screen (that's what a hashed password looks like)
It's been quite a ride ๐ฅฑ, thanks for coming to my ted talk ๐๏ธ on password encryption with Node.js ๐.
Bye ๐๐ฝ