DEV Community

Olu O
Olu O

Posted on

Sign-In and Sign-Up logic for an Authentication System in Nestjs

One of the most important parts of building an authentication system is having an effective login and sign-up logic that covers important edge cases to prevent data breaches or security concerns. In this write-up, I’ll walk you through how I implemented a basic login and sign-up logic in the Auth Service for an application in Nestjs to cyber attacks such as rainbow table attack. We will cover the logic for password encryption using salt and hashes in the most effective way as well as the logic for retrieving a User from the database during the sign-in process.

A solid sign-up and log-in logic will enhance app security and build user trust and confidence.

Authentication Flow

Basic Authentication Flow

When we break up a typical authentication flow at a high level, it requires creating and saving a User in the database otherwise known as signing up a user, and also having that user access their account at a later time during sign-in.
At some point in time, a client, be it a mobile device, api client, or a web browser, is going to make a request to our application and intend to sign up into our app that would be a POST request to /auth/signup route proving an email and a password. Inside of our server, we are going to take a look at the email the user provided at sign up and we are going to make sure that the email is not already in use since we have to make sure every user has a unique email address. If the user is trying to sign up with an email that is already in use, we immediately return an error otherwise we go to the next step which is to encrypt the user's password. We want to store all our passwords securely in the database.
Once we have encrypted the user password, we will create a new record inside our database saying here is a user, here is their email, and here is their encrypted password. Then we will send back a response to the user. In the response header, we are going to include a cookie that contains the specific ID tied to this user record. A cookie stores a very small amount of information and it is managed by the client's browser. Anytime the user makes a follow-up request from the client browser, they will automatically have that data inside the cookie attached to the request. Sometime later, If they try to make a request back to our application again, maybe a POST request to a create orders route for example, the information contained in the cookie we had sent back in the response header earlier will be attached to the request. We will receive that request in our server, and we are going to confirm the information stored inside that cookie has not been altered by the user. Once we have verified that the cookie data is intact, we are going to look at the User ID inside the cookie and we will fetch that user from our database.

Auth Service Setup

We will then translate this logic described into code and write the createUser() and signIn() methods in our Auth Service. The way our system is set up, we have a directory called Users Module. This module contains the Users Controller, Users Service, Auth Service file. We will write our createUser and signIn methods in our Auth Service file. We will use the find and create methods defined in our Users Service to log and fetch users to the repository/database.

Users Module

Sign-Up Flow

  1. A user will give us an email address and password they want to use e.g. { a@a.com, password123 }. We will check if the email is already in use. If it is, we will return an error, else we will proceed to step 2.

  2. We will generate a random series of numbers inside our related code called salt. For example, our salt may look something like A890B23

  3. We will take the user's password, join them with our salt into a string, and run them through our hashing function. This will return the hashed output. E.g. password123, and salt A890B23 will pass through the hashing function and produce a hash “utyruuueeyyeo23455645wwhrews”

  4. We will join the hashed output and the salt together, separating them with a special character so we can differentiate them. E.g. joining the hashed output and salt together will be “utyruuueeyyeo23455645wwhrews.A890B23”. The special character used in this instance is the period (.)

  5. The value of this hash and the email address will be stored in the password and email columns in our Users database respectively.

For this implementation, we would import randomBytes, scrypt from the nodejs module. randomBytes will be used to help generate our random salt while scrypt will be used as the hashing function.

The sign-up logic would look like:

export Class AuthService {
    constructor(private usersService: UsersService) {}

    createUser(email: string, password: string) {
        // Check if email is in use
        const users = await this.usersService.find(email);
        if (users.length) {
            throw new BadRequestException(`email ${email} already exists`)
        }

        // Hash the users password
            // Generate a salt
            const salt = randomBytes(8).toString('hex');

            // Hash the salt and the password together
            const hash = (await scrypt(password, salt, 32) as Buffer;

            // Join the hashed password and the salt together 
            const hashedPassword = salt + '.' + hash.toString('hex');

            // Create a new user and save it
            const user = await this.usersService.create(email, hashedPassword);

            // Return the user
            return user;

    }

}
Enter fullscreen mode Exit fullscreen mode

Sign-In Flow

  1. A user will give us an email and a password. We will check if the email exists in our database. If it does not, we will return an error “username is invalid”. If it does exist, we will proceed to step 2.
  2. We will go into the database and grab the salt we joined to the hashed output in step 4 of the sign-up flow above, from the stored password associated with the users email address.
  3. We will join the salt and the password together and hash them to give us a hashed output.
  4. We will join the value of this hashed output to the salt again and separate with the special character, in our case period (.) and we will compare this result to the stored password in the database. If there’s a match, we would return the user.
export Class AuthService {
constructor(private usersService: UsersService) {}

    async signIn(email: string, password: string) {
        // Find the user by email
        const [user] = await this.usersService.find(email);
        if (!user) {
          throw new BadRequestException(`user ${email} not found`);
        }

        // Get the hashed password from the database
        const [salt, hash] = user.password.split('.');

        // Hash the salt and the password together
        const hashedPassword = (await scrypt(password, salt, 32)) as Buffer;

        // Compare the hashed password with the hashed password from the database
        if (hash !== hashedPassword.toString('hex')) {
          throw new NotFoundException('invalid password');
        }

        // Return the user
        return user;
      }
  }
Enter fullscreen mode Exit fullscreen mode

This particular technique of hashing the user password with a salt, and then joining the salt and the hashed password in the database prevents our system from rainbow table attacks. This is a kind of attack where a malicious person can get a list of all the different most popular passwords across the world. They can then run through this list and calculate the hash of every one of these common passwords ahead of time. Once they have done this calculation and they have gotten these passwords and hash pairs, they can just store this in a table. If this malicious person ever gets access to our database in some way, they could take a look at our hashed password and they can compare our stored hashed password right there against all the pre-calculated hashes that they ran ahead of time. Once they find a match, they can locate the user’s email address and try to access their account.

This post is powered by Hng Hire and Hng Premium. Visit the website and support the great work.

Top comments (0)