DEV Community

Cover image for Secure Your Applications with MongoDB in 15 mins
0xSaurav
0xSaurav

Posted on

Secure Your Applications with MongoDB in 15 mins

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

  1. Create a MongoDB Atlas Project
  2. 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

  1. 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
  1. 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.

  1. Select the Create Endpoint Option from the left navigation and create the endpoint.
  2. Select HTTP Method as Post and Response With Result = ON

Image description

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
}
Enter fullscreen mode Exit fullscreen mode

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
  }
Enter fullscreen mode Exit fullscreen mode

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
  }
Enter fullscreen mode Exit fullscreen mode

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.

Image description

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)

Enter fullscreen mode Exit fullscreen mode

Trigger: Reset Passcode after 15 mins
This trigger is a time-based trigger which resets the created passcode after 15 minutes.

Image description

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}`)

Enter fullscreen mode Exit fullscreen mode

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)