Overview of My Submission
One-time password (OTP) systems provide a mechanism for logging on to a network, service, web and/or mobile application using a unique password that can only be used once, as the name suggests. In many cases such as Financial Applications, it is also used for a 2-Factor Authentication in addition to the static account password.
Other cases include verification of user accounts - email, mobile numbers during application signup.
For any project, one needs to utilize various third-party applications for the OTP Service. Using MongoDB Atlas, MongoDB Realm (Functions, Triggers), it is possible to setup our own one-time passcode service.
Submission Category:
Action Star
Setup
The setup includes MongoDB Atlas, MongoDB Realm.
Other libraries: Nodemailer, GMail Account
Setup Time: 20-30 mins
Atlas
- Create a MongoDB Atlas Project
- Create a Database and Collection The Atlas Database and Collection will be used to store the user and passcode details temporarily.
Realm
Create a Realm Project
- The Realm Project will serve as the backend service and performs the following functions
- Receive Request for Passcode (end-point)
- Create a 6 Digit Random Passcode
- Send Acknowledgement to the Application
- Send Passcode to the user email (using Database Trigger)
- Recieve user (email) and Passcode from application (end-point)
- Check and Confirm if Passcode is correct
- Additional Features like setting up time-based expiration (15 mins) is setup using Triggers (time-based)
Realm Endpoints
Create 2 Endpoints
Note: The endpoints are introduced recently and replace the earlier existing feature of webhooks.
- Select the Create Endpoint Option from the left navigation and create the endpoint.
- Select HTTP Method as Post and Response With Result = ON
One Endpoint is for CreatePasscode and second endpoint is for CheckPasscode
Function: Create 6 Digit Random Passcode
This function returns a 6 digit random numeric code
const CreateCode = (min, max) => {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1) + min); //The maximum is inclusive and the minimum is inclusive
}
Function: Create Passcode Function
This is the Realm Function linked to the endpoint to create the passcode and send acknowledgement to the application. Please note that in passcode field, we are sending 'xxxxx', as passcode will be sent to the user email and not to the application directly.
const { data, user } = JSON.parse(body.text());
const code = CreateCode(100000,999999).toString();
const { v4: uuidv4 } = require('uuid');
var dbse = await context.services.get("mongodb-atlas").db(db-name).collection(collection-name);
const trxn = uuidv4().split('-').join('')
var datx={
user: data.user, // destination user email
memo: data.memo, // memo to include in the email
code: code,
actv: true,
trxn: trxn,
crts: new Date()
}
var docx = await dbse.insertOne(datx)
if (docx) {
response.setHeader( "Content-Type", "application/json");
response.setStatusCode(201)
response.setBody(JSON.stringify({data: {user: data.user, code: 'xxxxxx', trxn: trxn}}));
return
}
else {
response.setHeader( "Content-Type", "application/json");
response.setStatusCode(502)
response.setBody(JSON.stringify({data:{user: data.user, code: 'xxxxxx', trxn: 'xxxxxx'}}));
return
}
Function: Check Passcode Function
This is the check the passcode and time validity of the passcode send from the application front-end
const { data } = JSON.parse(body.text());
var dbse = await context.services.get("mongodb-atlas").db(db-name).collection(collection-name);
var docx = await dbse.findOne({ user: data.user, trxn: data.trxn }, {code:1, actv:1});
if (docx && docx.code == data.code && docx.actv) {
dbse.updateOne({ user: data.user, trxn: data.trxn }, {'$set': {actv:false}}, {upsert: false});
response.setHeader( "Content-Type", "application/json");
response.setStatusCode(200)
response.setBody(JSON.stringify({auth: true}));
return
}
else {
response.setHeader( "Content-Type", "application/json");
response.setStatusCode(409)
response.setBody(JSON.stringify({auth: false}));
return
}
Triggers
The triggers are used to increase the applicationscalability and optimize performance. The project utilizes following triggers
Trigger: Send User Email with Passcode
This is a Database trigger and it will send an email to the user-email as soon as the code is created and record is inserted into the database.
The Trigger is linked to Realm Function which uses the Nodemailer and GMail for sending email notification to the user with the Passcode.
const { user, memo } = JSON.parse(data);
const nodemailer = require('nodemailer');
const basemail = context.values.get('basemail')
const basecode = context.values.get('basecode')
// the basemail and basecode are setup as Realm Function env variables (values). These are the Gmail Account Email and Password
const nodemail = nodemailer.createTransport({ service: 'gmail', auth: {user: basemail, pass: basecode} })
const text = memo.reduce( (a, c) => a + c.text + '\n\n' , '')
+ 'Passcode is valid for 15 mins.\n\n'
+ 'Please do not share the Passcode (OTP) with anyone.\n'
+ 'In case you have not initiated this request please contact our helpdesk.\n'
+ '\n\n\n\n'
+ `Team MongoDB` + '\n'
+ (new Date()) + '\n\n'
+ '------------------------\n'
+ 'Please do not reply to this email. This email has been generated and sent automatically.\n'
+ 'To ensure that you receive business communications, please add this email to your contact list and address book.\n'
var mailOptions = {
from: `OTP Authentication <${(sndr).toLowerCase()}-${basemail}>`,
replyTo: '',
to: user,
subject: head,
text: text
}
const mail = await nodemail.sendMail(mailOptions)
Trigger: Reset Passcode after 15 mins
This trigger is a time-based trigger which resets the created passcode after 15 minutes.
This is triggered every 1 min and checks the database for all transactions that have created timestamp greater than 15 minutes. If found true, it sets the actv (active) field to false. This is a bulk operation using updateMany
var dbse = await context.services.get("mongodb-atlas").db(db-name).collection(collection-name);
const timx = new Date()
timx.setMinutes((new Date()).getMinutes() - 15);
// return ({'new': (new Date()).toISOString(), 'old': (new Date(timx)).toISOString()})
const docx = await dbse.updateMany({ actv: true, crts: {'$lte': timx} }, {'$set': {actv:false}}, {upsert: false});
console.log (`reset count: ${docx.modifiedCount}`)
Summary
In Summary, we have used
MongoDB Atlas, MongoDB Realm Functions and MongoDB Realm Triggers with minimal external libraries to create an authentication service. This removes dependence on third party applications and overall setup time is approx 20-30 mins.
Additional Resources / Info
The usermail and passcode should be further secured in the database by using crypto hash functions. MongoDB Realm has native support for NodeJS Crypto Module.
The same is also to be implemented on the browser / application side when sending the payload for Passcode verification.
This has been skipped here to allow showing the details of the functions / code.
Thank You for reading and I hope this is helpful during your application development.
I look forward to your comments and feedback.
Top comments (0)