DEV Community

loading...
Cover image for Project: Basic Authentication System

Project: Basic Authentication System

ulzahk profile image Ulzahk ・6 min read

Table Of Contents

When you hear or see the word authentication you relate it to identification and that is a correct approach to this term. We just need to add permissions to perform a specific action in an app and we will have a more complete definition for the technology area.

In this project we are going to have a series of authentication systems in order to better understand what they need, how they are built and how they work.

Of course being these kind of security systems, they need something to protect, in this occasion we established a list of Pokémon cards with which the user will get the permission to see them and interact with the search engine to sort them in order of pokemon type or by the name of the pokemon.

For this post we will cover the first authentication structure. So let's get started

Project Structure

We will use two repositories, one to manage the visual part, user interface and forms with technologies such as React for the development of interfaces, Sass to style those interfaces and Webpack to compile the application in a lighter and more adaptable format for browsers.

The second repository will be used to handle requests, database queries and information transformation. We will use technologies such as Node to be able to handle JavaScript from the Backend side, Express to create endpoints faster and PostgreSQL to handle the connection to this type of database and queries.

Finally we have the servers these would be the platforms, Vercel to host both parts working and ElephantSQL that offers PostgreSQL databases ideal for projects.

Authentication Level Zero

Alt Text

In order to understand how the project would be if it did not have authentications, this section is created where it is simulated that the data is exposed and any user can manipulate it without having permissions to do so.

It is also a quick way to know what we are protecting, a list of cards of the first generation Pokémons. By fetching the information from PokéAPI, we get pokémons with their name, types, an image that represents them and their identifier as a label.

Authentication Level One

Alt Text

For this first level we are going to build a simple login with username and password without email verification. In order to understand how it would be useful to us and what disadvantages it presents.

This will start to work the moment the user fills the form for the creation of an account correctly and presses the create account button. This will send a request to the Backend, to confirm if the data is correct, mainly that all the data is complete.

If so, the password is encrypted first, then the new user's information is written into the database, and then a correct response is sent to the Frontend to redirect the user to the login form.

This can be seen in the following diagram:

Alt Text

Here I share the function that handles the endpoint (file UsersController.js):

class UsersController{
  async createUser(req, res){
    const {body: user} = req;
    try {
      const createdUser = await usersService.createUser({ user });
      res.status(201).json({
        message: 'User created',
        user: createdUser
      });
    } catch (err) {
      console.log(err);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

And this is the function in the Backend, where we verify the table fields, add a unique identifier and encrypt the password before writing the information (file UsersService.js):

const { client } = require('../../config/database');
const { v4: uuid }  = require('uuid');
const bcrypt = require('bcrypt');

class UsersService {
  constructor(){
    this.table = 'users',
    this.fields = 'id, username, password, email'
  }

  async createUser({ user }){
    const { username, password, email, fullName } = user
    try {
      const id = uuid();
      const encriptedPassword = await bcrypt.hash(password, 10);
      const lowerCaseEmail = email.toLowerCase();
      const userCreated = await client.query(
        `INSERT INTO ${this.table}(${this.fields}) VALUES (
          '${id}',
          '${username}',
          '${encriptedPassword}',
          '${lowerCaseEmail}',
        )`
      )
      return userCreated.rowCount;
    } catch (err) {
      console.error(err);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Alt Text

For the login a very similar process is done, what varies is the Backend processing where the existence of that user is confirmed, the password is verified to be correct and if everything is OK a response is sent with a JSON Web Token.

This token will be stored locally in Frontend with the browser's window.localStorage.setItem() function so that it can be used in requests that require it as a value in a header.

Diagram of the login process:

Alt Text

Function that handles the login endpoint:

  async loginUser (req, res){
    const { user, password } = req.body;
    try {
      if(!user || !password) res.status(401).send('Invalid information');

      let userData;

      const userDataByUsername = await usersService.getUserByUsername({user});
      if(userDataByUsername.length === 0) {
        const userDataByEmail = await usersService.getUserByEmail({user});
        if(userDataByEmail.length === 0) res.status(401).send('Invalid information');
        userData = userDataByEmail;
      } else {
        userData = userDataByUsername;
      };

      const comparedPassword = await bcrypt.compare(password, userData.password);
      if(!comparedPassword) res.status(401).send('Invalid information');
      const token = jwtAuthenticationService.JWTIssuer({user: userData.id}, '15 min');
      res.status(200).json({ token: token })
    } catch (err) {
      console.log(err)
    }
  }
Enter fullscreen mode Exit fullscreen mode

Function to consult users by username:

  async getUserByUsername({ user }){
    try {
      const userData = await client.query(`SELECT * FROM ${this.table} WHERE username='${user}'`)
      return userData.rows[0] || [];
    } catch (err) {
      console.error(err)
    }
  }
Enter fullscreen mode Exit fullscreen mode

Function to consult users by email:

  async getUserByEmail({ user }){
    try {
      const lowerCaseEmail = user.toLowerCase()
      const userData = await client.query(`SELECT * FROM ${this.table} WHERE email='${lowerCaseEmail}'`)
      return userData.rows[0] || [];
    } catch (err) {
      console.error(err)
    }
  }
Enter fullscreen mode Exit fullscreen mode

Finally, the last thing that happens is that Frontend performs a query using the token to bring the user information and display the username.

Alt Text

This is the function that takes care of this endpoint:

  async listUserById(req, res){
    const { bearertoken } = req.headers;
    if(!bearertoken) res.status(401).json({message: 'Request without token'})

    const tokenData = await jwtAuthenticationService.JWTVerify(bearertoken)
    if(tokenData === undefined) res.status(401).json({message: 'Invalid token'})

    const userId = tokenData.user;

    try {
      const userData = await usersService.getUserById({ userId });
      res.status(200).json({
        message: 'User listed',
        user: {
          id: userData.id,
          username: userData.username,
          email: userData.email,
        }
      })
    } catch (err) {
      console.log('listUserById error: ', err);
    }
  }
Enter fullscreen mode Exit fullscreen mode

Authentication Level One Advantages and Disadvantages

Advantages

  1. Easy to implement in any application
  2. Quick way to create users and be able to relate them to the other services of the application.
  3. Gradually more verifications and safety elements can be added.

Disadvantages

  1. It has a low level of security compared to other authentication structures.
  2. In case of password loss, it is necessary to contact support directly to change the password.
  3. If maintained in this manner without implementing further security measures, there is a risk of being breached.

If you noticed the account creation endpoint has no user verification so someone can create an account with the same email and username without any restriction.

How did we prevent this situation from happening? Share your answer in the comments

Lastly, now that you know the functionality of this application I invite you to review it, try it and leave me your suggestions to improve it.

If you want to review the application documentation, here I share the repositories:

References

  1. PokéAPI: https://pokeapi.co/
  2. ElephantSQL: https://www.elephantsql.com/
  3. JSON Web Token: https://jwt.io/
  4. bcrypt for NodeJs: https://www.npmjs.com/package/bcrypt

Discussion (2)

Collapse
sqlrob profile image
Robert Myers

You should use parameters on the queries, not generate the query text. You're vulnerable to SQL injection in the user service.

Collapse
ulzahk profile image
Ulzahk Author

Thanks Robert, I will keep this in mind for future updates.

Forem Open with the Forem app