Security: "Over 83% of Developers Commit this Critical Mistake when storing User's Password." Are You One of Them?"
danielAsaboro

danielAsaboro @danielasaboro

About: A dedicated Backend Junior developer. proficient in Node.js. Strong in RESTful API development, and database systems. And now, I'm writing about using Dart beyond building beautiful UI with Flutter.

Joined:
May 15, 2023

Security: "Over 83% of Developers Commit this Critical Mistake when storing User's Password." Are You One of Them?"

Publish Date: Jul 9 '23
20 18

Throughout my years of tweaking, designing, and engineering software systems, I've come to realize a fundamental truth: there are no perfect systems, no perfect process, and no perfect solutions — only the necessary tradeoffs.

For instance, It has been repeatedly demonstrated that longer passwords/passphrases are a better measure of security, and not complicated ones.

However, imposing stringent security protocols on users can be inconvenient, indirectly compromising the initial security goal —which is why the National Institute of Standards and Technology (NIST) recommends a minimum password length of 8 characters.

Hashing User's password is another example.

Generally, the more times a password is hashed, the stronger it becomes. However, hashing itself is a resource-intensive and time-consuming task for Computer processors. Thus, finding the optimal compromise is crucial to ensure both protection and user experience.

The same notion applies to storing passwords in NoSQL databases like MongoDB. While these databases offer flexibility and scalability, they may require additional measures to ensure password security.

A common mistake among software architects

The number one benefit of MongoDb, is unlike relational database, it lets you read and write data about a specific entity in one sweep — No complex joins, No rigid schema, and No impedance mismatch!

But most people take this too literally! They end up storing password hashes on the User document. What a big mistake — one which I fell into until recently.

But first, what's wrong with this approach?

"Aren't they hashed?", you ask. I'll explain, so read on.

🛑 Why storing users password on the User document is bad practice ❌💀

One, too many oppurtunities for mistake.

Storing password hashes in the User document increases the attack surface area since the User document is one of the easily accessible object in an application runtime.

Once an attacker gains unauthorized access it, then they have immediate access to both the password hashes and other confidential user data — you definitely don't want that, at least not that simple!

And worse, if an attacker has access to multiple users account, they can compare the hashes and implement a bruteforce. Besides, storing user password in the User document adds more overhead to your data bandwith when you only need it just once: during Authentication.

A popular way developers mitigate this risk is using Mongoose's
schema-level projections(setting the select property to false).


const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema({
    email: {
        type: String,
        required: true
    },
    password: {
        type: String,
        required: true,
        select: false
    }
});


Enter fullscreen mode Exit fullscreen mode

Here, The user's password will be not be retrieved when you do User.find() unless you project it using select('+password')


let foundUser = await User.findOne();
foundUser.password; // undefined

//Using Schema-level projections
foundUser = await User.findOne().select('+password');
foundUser.password; // String containing password hash

Enter fullscreen mode Exit fullscreen mode

The problem with this approach is it limits flexibility and it makes password retrieval unnecessarily tedious. You'd have to use complex queries to ensure you are selecting the right property, at the right time, and not excluding others (just too many chance to fuck up).

And lastly, storing password hash on the User document makes it challenging to enforce regular password updates or implement password complexity requirements.

Your job is to make it as hard as possible to make a major mistake says Technical Architect, Valeri Karpov. How do you do that? Beside outsourcing authentication to a third-party provider like Google, Apple, or Facebook using PassportJs...what follows is how I'm handling User's password for the projects I work on.

And it's simple: Seperate Password from the User's document.

This ensures the impact of a potential breach is mitigated.

User Model


const mongoose = require('mongoose');
const Auth = require('../auth/auth');
const bcrypt = require("bcrypt");

const userSchema = new mongoose.Schema(
    {
        email: {
            type: String,
            required: true,
            unique: true
        },
             // other attributes removed for brevity
    }, { timestamps: true, }
);

const User = mongoose.model('User', userSchema);
module.exports = User;

Enter fullscreen mode Exit fullscreen mode

Auth Model


const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const bcrypt = require('bcrypt');

const authSchema = new Schema({
    user: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User',
        required: true
    },
   type: {
        type: String,
        enum: ['PASSWORD', 'FACEBOOK_OAUTH', 'GOOGLE_OAUTH'],
        default: 'PASSWORD'
        },
           secret: {
        type: String,
        required: true,
    },

}, { timestamps: true });

authSchema.pre('save', async function (next) {
    try {
        const salt = await bcrypt.genSalt();
        this.secret = await bcrypt.hash(this.secret, salt);
        next();
    } catch (error) {
        console.log(error);
        throw error('Something Wrong Happened');
    }
})

const Auth = mongoose.model('auth', authSchema);
module.exports = Auth;

Enter fullscreen mode Exit fullscreen mode

Other than security, this approach also follow a software design concept: Principle of Data Locality. This principle states that a document should contain all data necessary to display a web page for that document. More on that here.

Security shouldn't be an afterthought

At least not in this era where data breaches and privacy concerns have become way too common. And so, securing your User's passwords should be a top priority, not just an afterthought.

As developers, we have a responsibility to prioritize user data security. It's not enough to use strong algorithms in hashing and salting password — How accessible are they to unauthorized personnels?

This is why we must embrace the Seperation of concerns and understand that Password are sensitive information which deserve special attention.

Let us strive for a future where user privacy is upheld. Together, we can create a safer digital landscape and inspire others to follow suit. This article is just one way, there's many more.

Comments 18 total

  • danielAsaboro
    danielAsaboroJul 9, 2023

    It's my default choice, but there are cases where people are sceptical to connect their social accounts with your app...(when working on apps used my old folks) this is where an email and password comes in :)

    tough work, but it's a must

  • György Márk Varga
    György Márk VargaJul 10, 2023

    Nice article! What about when using Auth.js?

    • danielAsaboro
      danielAsaboroJul 10, 2023

      No, I don't. In fact, this is my first time of hearing about it.

      Also, correct me if I'm wrong...AuthJs is a library? if so...I use PassportJs, haven't got any reason to search for alternatives yet...However, Auth Js seems like an interesting library(simple to implement
      at first sight)

      • György Márk Varga
        György Márk VargaJul 10, 2023

        Yes, It is simple. Best for using on Next.js full stack projects.

        • danielAsaboro
          danielAsaboroJul 11, 2023

          Yeah, I read that too...

          I probably need a project that will force me to look into Next.js (an hackathon maybe), what do you suggest, Varga?

          • György Márk Varga
            György Márk VargaJul 12, 2023

            Maybe have a look at my latest article if you want to get deeper inside Vercel and the Next.js ecosystem. HERE you can find it.

  • David
    DavidJul 10, 2023

    Thank you for the article,
    I have a question, when you use a 3rd party service like Google, do you still store/create the user in your database. I guess I want to better understand the flow for signup and login authentication when you use a 3rd party auth service. Thanks.

    • danielAsaboro
      danielAsaboroJul 11, 2023

      Yes, you do, David.

      The only difference is you don't have to store their password. Google/whoever will do the authentication part and give you a unique id for that particular user which you can store if they don't exist in your db or simply sign them in if they do.

      I suggest looking into PassportJs or AuthJs (as I've also learnt from the discussion here). Feel free to reach out to me directly if you need any help or guidance.

      • David
        DavidJul 11, 2023

        Oh, that is true I will try it out. I am actually using next-auth.

        Thank you very much that is very kind of you, I will actually be taking you up on your offer, because I am kind of tackling this issue at the moment with a personal project and I want to have better understanding of auth process.

        I also researched and saw access token and refresh token as means to having better security, what is your perspective on this?
        Thanks.

        • danielAsaboro
          danielAsaboroJul 11, 2023

          This is also my my first time hearing about next-auth...we are really blessed to have so many options as developers.

          Acess Token and refreshtoken aren't that difficult terms. in simpler terms, accesstoken is the key you give to users to enter a gated place on your site after validating that they are who they are(authentication).

          Refresh token just makes the process of issuing Acess Token seamless for the user so they don't have to reenter their details everytime their accesstoken expires

          reach out through: @AsaboroD on twitter; we can take it up from there ;)

          • David
            DavidJul 11, 2023

            I can't send u message directly but I sent hello as a tweet.

  • Mike Stemle
    Mike StemleJul 11, 2023

    I like a lot of the points in this article. I think I’ve decided that I’m never going to write a password system again. Auth0, cognito, Okta, and various OAuth identity providers make it so that storing user passwords is no longer necessary.

    I avoid storing user passwords like the plague.

    • danielAsaboro
      danielAsaboroJul 11, 2023

      Thanks for pointing that out, Michael...that means a lot to me.

      I'm also with you on the identity providers thing. Once you start integrating Oauth and the rest, you never want to go back...even for the end users...but some situations, one I found myself a few months ago still require going the old way....it is what it is.

      software is about democraticizing acess.

  • Jon Randy 🎖️
    Jon Randy 🎖️Jul 11, 2023

    Security: "Over 83% of Developers Commit this Critical Mistake when storing User's Password." Are You One of Them?"

    Storing the user's password IS a critical mistake.

  • Arowolo Ebine
    Arowolo EbineJul 13, 2023

    Thanks you so much. You have really enlightened me about this. Am really grateful for this.

    • danielAsaboro
      danielAsaboroAug 5, 2023

      You are welcome, Ebine. It's a pleasure to do so.

Add comment