Adding 2FA to your application create an extra layer of security for your users, to protect their detail from scammers and hackers.
Passwords do not completely secure your application, infact, if the only layer of security on your application is password, your application is very vulnerable to scammers and hackers, because 6 in 10 people use the same password across all their accounts and platform, so access to the password, places the user at extreme risk if there's absence of an extra layer of security.
2FA is an extra layer of security to make sure only authorized personnel gain access to certain resources.
2FA might be in the form of security questions & answers, access token, biometric authentication and tokens.
So quickly we are going to be walking through 2FA in node.js using the speakeasy library
REQUIREMENTS
Basic understanding of Javascript
Speakeasy is a onetime passcode generator that supports Google authenticator and other two factor devices. more info
STEP 1
initialize your project by running
npm init
STEP 2
Run this command to install needed dependencies
npm install express mongoose dotenv body-parser speakeasy
STEP 3
Create an app.js file and then create an express app
const express = require("express");
const dotenv = require("dotenv").config();
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.json());
app.use(express.json());
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`server up and running at port ${port}`));
STEP 4
Connect your express app to mongodb using the mongoose package installed earlier.
NOTE: Add your database connection string and password to the .env file
const express = require("express");
const dotenv = require("dotenv").config();
const bodyParser = require("body-parser");
const mongoose = require("mongoose");
const app = express();
// middlewares
app.use(bodyParser);
app.use(express.json());
// connect to database
const db = process.env.DATABASE;
mongoose
.connect(db, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log("database connection successful"));
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`server up and running at port ${port}`));
STEP 5
install nodemon
globally to monitor changes in the app.js file
npm install -g nodemon
then add this command to the scripts object inside your package.json
file
"scripts": {
"start": "nodemon app.js"
},
STEP 6
Open your terminal and npm start
npm start
if you get something like this, then you're good to go, if not go through the codes again, probably you are making a mistake somewhere
[nodemon] 2.0.20
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node app.js`
server up and running at port 5000
database connection successful
STEP 7
Create a model folder, and inside create a token-model.js
file
This file will be used to structure and store the secret string gotten from speakeasy
inside the token-model.js
file, paste in the following codes
const mongoose = require("mongoose");
const tokenSchema = new mongoose.Schema(
{
secret: String,
authIsSet: {
type: Boolean,
default: false,
},
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
module.exports = mongoose.model("Token", tokenSchema);
STEP 8
Create a controller folder, and inside create an authentication-controller.js file
Inside this file, import the Token model from the token-model.js file, import speakeasy and create a simple controller function
const Token = require("../model/token-model");
const speakeasy = require("speakeasy");
exports.generateSecret = async (req, res, next) => {};
STEP 9
Our 2FA process follows 3 flow
Generate a secret key
Generate a set-up key and paste in google authenticator app
Authenticate the token for the first time
GENERATE SECRET KEY
Run this command to generate a secret key, and save it to mongodb
const Token = require("../model/token-model");
const speakeasy = require("speakeasy");
exports.generateSecret = async (req, res, next) => {
const secretToken = speakeasy.generateSecret();
try {
const token = await Token.create({ secret: secretToken.base32 });
return res.status(201).json({
status: "success",
message: "secret token generated",
token: secretToken.base32
tokenId: token.id,
});
} catch (error) {
return res.status(400).json({
status: "error",
message: error.message || "something went wrong",
});
}
};
GENERATE A SET-UP KEY
Generate a set-up key by pasting the secretToken.base32 returned from calling the speakeasy.generateSecret()
command string inside your authenticator app
STEP 10
Create a router folder and inside, create a token-router.js
file and import express and create a simple router.
const express = require("express");
const authenticationController = require("../controller/authentication-controller");
const router = express.Router();
router.post("/generate-secret", authenticationController.generateSecret);
module.exports = router;
import the router into your app.js
file and register the route
app.use("/auth", authRouter);
STEP 11
Now go to postman and fire this endpoint
http://localhost:5000/auth/generate-secret
You should get this response
{
"status": "success",
"message": "secret token generated",
"token": "H4XE22DMGZCD6NCHGJKC6JDSMFJV2T3EHF4DCUTJNNTGGYKWKNIQ"
"tokenId": "68r77hdfbnnmdnmfdju8",
}
STEP 12
Now copy and paste the token inside your google authenticator app, then you are ready to proceed to the final step, which is verifying the token for the first time.
VERIFY THE TOKEN
Now we want to make sure the token on the client and server match.
After entering your set-up key, then enter the token you get to verify against the secret string that was generated in step 9.
Let us create a verifyToken function inside our controller and start the verification process
exports.verifyToken = async (req, res, next) => {
const secretToken = await Token.findById(req.params.id);
const enteredToken = req.body.token;
const verified = speakeasy.totp.verify({
secret: secretToken.token.secret,
encoding: "base32",
token: enteredToken,
});
if (!verified) {
return res.status(403).json({
message: "verification failed",
});
}
await secretToken.authIsSet = true
return res.status(200).json({
status: "success",
message: "verification successful",
verified: verified,
});
};
STEP 13
Create a verify-token
route to fire this request
router.post("/verify-token/:id", authenticationController.verifyToken);
if after firing this endpoint, you get this response, then you have successfully added 2FA to your application!.
CONGRATULATIONS
{
"status": "success",
"message": "verification successful",
"verified": true
}
I hope you found this write up helpful!.
Checkout my codes here github
Let me know your thoughts in the comment section
Top comments (4)
Hello @Candi,
As a DEV.to moderator on #React and #Node, I wanted to take a moment to welcome you to our community.
I've noticed that you've been writing articles for a while now, and I wanted to acknowledge your progress and commitment to sharing your knowledge with others.
Your contributions to the community have not gone unnoticed, and we are grateful for your efforts.
Please continue to share your expertise with us, and don't hesitate to reach out if you have any questions or concerns.
We're all here to support and help each other grow as developers.
Thanks again for being a part of our community.
Jean-Luc
Thank you very much.
The gesture is much appreciated
Hello ! Don't hesitate to put colors on your
codeblock
like this example for have to have a better understanding of your code 😎Thank you very much