Node.js Login System with Express, JWT & MySQL (Authentication)

In this tutorial we build a Node.js login with Express, JWT and MySQL – a very simple, but powerful Authentication. You can then connect this RestAPI to the frontend in a very flexible way.

This tutorial is the first part of a two-part series on creating a complete login system with Node.js and Vue.js. For this we use Node.js as backend and Vue.js as frontend.

The tutorial is split into two posts so that you are not tied to a specific frontend framework, but can equally use this Node login system we are developing in this post with a Vue.js, React or Angular framework.

As a bonus, I’ll provide you with the complete code in a git repository! 🙂

We are creating a Node.js application. For this we have a MySQL database where our user data is stored. Alternatively, you can replace the database with, for example, a MongoDB database. For authentication, we then need to query this data and can use the JWT (JSON Web Token) package to open a session for the user.

Here you can also find the tutorial in video form:

What is a Rest API?

A Rest API represents the interface between server and client. Using simple HTTP requests, we can reach the server and perform programmed functions, such as authenticating a user with the associated password.

If you are not familiar with Node.js yet, you can take a look at this Node.js beginner tutorial. After that, you’ll have all the basics down and can start working on your Node.js login system.

1. Install dependencies

After you initialize a new project with npm init, we can install the required dependencies. We need the following modules:

PackageDescription
expressWith this we create our own web server for our Rest API
mysqlTo read/write to our database
uuidTo create IDs for later users
bcryptjs (Achtung: nicht bcrypt)To encrypt/decrypt the passwords
jsonwebtokenTo handle the user sessions
corsSo that we can call the Rest API from our website.
dotenvManage environment variables

We install these modules using the following CLI command npm install express mysql uuid bcryptjs jsonwebtoken cors dotenv.

To give you a better overview of where to store which files, here is a small overview:

├── lib
│   └── db.js
├── middleware
│   └── users.js
├── routes
│   └── router.js
├── .env
└── index.js

2. Create MySQL database schema

As database engine I use XAMPP, so I can host my own database locally. Of course you can also use any other (remote) database.

For our login system we need only one table according to the following scheme:

ER model of the "users" table
ER model of the “users” table

In the database, our table then looks like this:

phpMyAdmin view of the "users" table
phpMyAdmin view of the “users” table

So that we can also access this connection through our Node.js application, we create a custom class file that we later include in our router.

// lib/db.js

const mysql = require('mysql');

const connection = mysql.createConnection({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  database: process.env.DB_NAME,
  password: process.env.DB_PASSWORD,
});

connection.connect();
module.exports = connection;

Security relevant information like passwords, PINs or tokens should never be stored directly in the code! Therefore, you create environment variables for such data. These are variables that can be configured differently in the different stages of your application and are never stored in a repository.

Now, to fill these placeholders with the correct content, we create the .env file. Note the point at the beginning. There we create our four used variables:

DB_HOST=localhost
DB_USER=root
DB_PASSWORD=******
DB_NAME=node-login-tutorial

If you manage your code in a repository, be sure to include the .env file in your .gitignore.

3. Set up Express Router and create routes

Our initial file is index.js and includes starting our web server and including the routes we define in routes/router.js.

// index.js

const express = require('express');
const app = express();
const cors = require('cors');

require('dotenv').config();
const PORT = process.env.PORT || 3000;

app.use(express.json());
app.use(cors());

const router = require('./routes/router.js');
app.use('/api', router);

app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

In router.js we define our routes and then wrap the logic in it. The reason why we use an extra file here is for clarity. If your application eventually has 20 or even more routes, there will be a big mess in the index.js. That’s why we outsource our routes.

// routes/router.js

const express = require('express');
const router = express.Router();

const bcrypt = require('bcryptjs');
const uuid = require('uuid');
const jwt = require('jsonwebtoken');

const db = require('../lib/db.js');
const userMiddleware = require('../middleware/users.js');

router.post('/sign-up', (req, res, next) => {});

router.post('/login', (req, res, next) => {});

router.get('/secret-route', (req, res, next) => {
  res.send('This is the secret content. Only logged in users can see that!');
});

module.exports = router;

Here we register the route /api/sign-up to register and /api/login to log in. Also we have the route /api/secret-route, you should be able to call this only if you are logged in. Currently every user can call this route. But more about that later.

In line 10 we also include our file for the database connection.

We also include the file ../middleware/users.js, which contains the code to verify the requests. That means, we check there if the user has entered a password and if the username complies with the guidelines. We later switch these queries as middleware into the call of our routes.

4. Create middleware (validation)

A middleware is a small program that is inserted between two components. In this case we have a middleware between our request and the actual registration, which validates the entered data. For the registration, a validation can look like this:

// middleware/users.js

const jwt = require("jsonwebtoken");

module.exports = {
  validateRegister: (req, res, next) => {
    // username min length 3
    if (!req.body.username || req.body.username.length < 3) {
      return res.status(400).send({
        message: 'Please enter a username with min. 3 chars',
      });
    }
    // password min 6 chars
    if (!req.body.password || req.body.password.length < 6) {
      return res.status(400).send({
        message: 'Please enter a password with min. 6 chars',
      });
    }
    // password (repeat) must match
    if (
      !req.body.password_repeat ||
      req.body.password != req.body.password_repeat
    ) {
      return res.status(400).send({
        message: 'Both passwords must match',
      });
    }
    next();
  }
};

When calling our /sign-up route, we want our middleware to be executed. For this we change the marked line as follows:

// routes/router.js

const express = require('express');
const router = express.Router();

const userMiddleware = require('../middleware/users.js');

router.post('sign-up', userMiddleware.validateRegister, (req, res, next) => {});

router.post('login', (req, res, next) => {});

module.exports = router;

5. Create Express Routes

5.1 Implement Register Route

To add a new user to the database, we need to check if the username does not already exist. If the user exists, an error message is displayed. If the user does not exist yet, by means of our module bcryptjs, the entered password is hashed (encrypted) and then all data is entered into the database.

// routes/router.js

router.post('/sign-up', userMiddleware.validateRegister, (req, res, next) => {
  db.query(
    'SELECT id FROM users WHERE LOWER(username) = LOWER(?)',
    [req.body.username],
    (err, result) => {
      if (result && result.length) {
        // error
        return res.status(409).send({
          message: 'This username is already in use!',
        });
      } else {
        // username not in use
        bcrypt.hash(req.body.password, 10, (err, hash) => {
          if (err) {
            return res.status(500).send({
              message: err,
            });
          } else {
            db.query(
              'INSERT INTO users (id, username, password, registered) VALUES (?, ?, ?, now());',
              [uuid.v4(), req.body.username, hash],
              (err, result) => {
                if (err) {
                  return res.status(400).send({
                    message: err,
                  });
                }
                return res.status(201).send({
                  message: 'Registered!',
                });
              }
            );
          }
        });
      }
    }
  );
});

If the user is successfully entered, the status code 201 (“created”) is returned and the function call is terminated.

Ever heard of double opt-in? This is when confirmation emails are sent to verify a signup. In this tutorial you will learn how to implement double opt-in in your Node.js application (building on this tutorial).

5.2 Implement Login Route

Besides the registration process, we have a login route to log in for already registered users. Here the matching database entry is searched based on the username. Then the entered one is checked with the encrypted password from the database using jwt.compare(). A short SQL query in line 45 sets the last login date/time to the current value.

// routes/router.js

[...]
router.post('/login', (req, res, next) => {
  db.query(
    `SELECT * FROM users WHERE username = ?;`,
    [req.body.username],
    (err, result) => {
      if (err) {
        return res.status(400).send({
          message: err,
        });
      }
      if (!result.length) {
        return res.status(400).send({
          message: 'Username or password incorrect!',
        });
      }

      bcrypt.compare(
        req.body.password,
        result[0]['password'],
        (bErr, bResult) => {
          if (bErr) {
            return res.status(400).send({
              message: 'Username or password incorrect!',
            });
          }
          if (bResult) {
            // password match
            const token = jwt.sign(
              {
                username: result[0].username,
                userId: result[0].id,
              },
              'SECRETKEY',
              { expiresIn: '7d' }
            );
            db.query(`UPDATE users SET last_login = now() WHERE id = ?;`, [
              result[0].id,
            ]);
            return res.status(200).send({
              message: 'Logged in!',
              token,
              user: result[0],
            });
          }
          return res.status(400).send({
            message: 'Username or password incorrect!',
          });
        }
      );
    }
  );
});

In line 33 and 34 we pass variables that we want to “store” in the JWT token. This gives us access to these variables in the protected routes.

In line 36 you have to pass a key which is used to generate the JWT token, this key is important for the verification later. Here you can enter any string.

Furthermore you can define in line 37 how long the token should be valid. Values like “1h” or “3m” are valid here. The individual values and parameters you can also read in the documentation.

If the password is wrong or the username does not exist, an error message is displayed. This message is intentionally identical, because otherwise a potential attacker can get information about the existence of individual user profiles.

On successful login the user object and the token generated by JWT is returned. This token is important for all routes where you should be logged in. In part 2 (Vue.js Frontend) you will learn how to pass it with every request. If you test the Rest API with Postman, you can specify the token with the key “Authorization” as value according to the following syntax: “Bearer KEY”.

Postman Authorization Header
Postman Authorization Header

5.3 Protect routes with login

The main routes are now ready. We can add new users and log in with existing accounts. Now we still want to protect routes. This means that only logged in users have access to them.

To do this, we create a new middleware in our users.js. Here, the token is taken from the request header and verified by JWT.

// middleware/users.js

  [...]
  isLoggedIn: (req, res, next) => {
    if (!req.headers.authorization) {
      return res.status(400).send({
        message: 'Your session is not valid!',
      });
    }
    try {
      const authHeader = req.headers.authorization;
      const token = authHeader.split(' ')[1];
      const decoded = jwt.verify(token, 'SECRETKEY');
      req.userData = decoded;
      next();
    } catch (err) {
      return res.status(400).send({
        message: 'Your session is not valid!',
      });
    }
  }
  [...]

In the marked line you have to enter the same key you already used to generate the JWT.

Now, to protect a route, you simply include this middleware when calling the route as follows:

// routes/router.js

[...]
router.get('/secret-route', userMiddleware.isLoggedIn, (req, res, next) => {
  console.log(req.userData);
  res.send('This is the secret content. Only logged in users can see that!');
});
[...]

In req.userData are the data that we have stored in the JWT key (in this case username and userId). This allows us to read user-defined values from the database based on the userId, e.g. for protected routes.

When developing a Rest API, errors can occur – this is completely normal. Often some edge cases are not considered. I have summarized how you can select these test cases and test them automatically here: Test Rest API correctly.

View complete code in Git repository

6. Deploy Node Login System

Great, your Node.js login with Express, JWT and MySQL is ready. Now you can build a nice frontend and connect your backend.

How you can then deploy your finished app – i.e. make it live – is described here.

➡️ Click here for part 2: Vue.js Login System with Vuex & Axios

Related Posts
Join the Conversation

35 Comments

  1. Vincent says:

    Ahoj, veľmi sa mi páčil tvoj návod na prihlasovaciu registráciu s vue a js. Ale mám jednu otázku ohľadom štruktúry priečinkov. Všetky súbory, ktoré vytvorím v tejto časti, by sa mali nahrať niekde vo finálnom projekte VUE alebo by som ich mal nejako roztriediť a umiestniť do rôznych priečinkov. Pretože keď som začal robiť váš tutoriál riadok po riadku v časti 4, dostal som 28 chýb 😅. Som v tom nový, ďakujem za odpoveď.

    1. Vincent says:

      //In english sorryHi, I really liked your tutorial on login register with vue and js.But I have one question about the folder structure. All of the files that I will make in this part one should be uploaded somewhere in the final VUE project or should I somehow sort it and put it in different folders. Because when I started doing your tutorial line by line on section 4, I got 28 errors 😅. I’m new to this, thanks for the reply.Ahoj, veľmi sa mi zapáčil váš návod na login register

      1. Lorenz says:

        You can put it in different folders:

        • put all your server files in a server folder and
        • all the builded vue files in a client folder

        This is the structue I use for my projects. You need to run npm run build to create the production files for Vue. You also have to add the following lines in your main Node.js file to handle the frontend files:

        // handle production
        if (process.env.NODE_ENV === "production") {
        // static folder
        app.use(express.static(__dirname + "/public"));

        // handle SPA
        app.get(/.*/, (req, res) => res.sendFile(__dirname + "/public/index.html"));
        }

  2. Deepa` says:

    Hi,I have used your Signup Api and it is successfully registered. but when will I get json token.please help me out.

    1. Lorenz says:

      You will get a JSON token when you use the /login route. Check out step 7 on this tutorial 

  3. Francou says:

    Hi,Very nice article, thanks! It’s the best I found on internet for implementing JWT authentification with mySql database.I’m newer on NodeJS and I really want to use MySQL for my database as the mainstream mongoDB seems to be overcomplicated to configure locally…So after implementing this authentification, I found 2 things on internet that I want to share here:I see that the body-parser package is not an obligation if you use NodeJS version 4 and above (and also some older versions), you can simply use a middleware that is embedded on express like this: app.use(express.json())The use of the mysql package is deprecated, since you’re only able to use a 24 years old  authentication method to access your mysql database (mysql_native_password), but with the use of the newer well maintained mysql2 package, you can use the newer and more secured authentication method in mySql 8 (caching_sha2_password). Hope this can help! 

    1. Lorenz says:

      Thanks! 🙂

      And thank you for your hints. I myself use the express.json() method and I need to update this post. I never heard of the mysql2 package before. But I will check it out and update this post as soon as possible.

  4. AnhNT says:

    Hi sir, can you help me?
    i have a problem that: my code is running good without any bugs, but when im trying to test with postman, im doing tutorial like you said: “Tab “body”, format “raw”, select “JSON” and parse the data like this:
    {
    “username”: “test”,
    “password”: “test”,
    }”
    but it show a error: “SyntaxError: Unexpected token } in JSON at position 51” when im trying “POST method” at “http://localhost:3000/api/sign-in”
    Do you know how to fix that, im new to nodejs, so i dont have enough knowledges to fixed all this bugs, even im trying to google it 🙁

    1. Lorenz says:

      Hey! Did you really enter the ” after the last closing bracket? That is wrong. You can test the JSON string with this tool: https://jsonformatter.curiousconcept.com/

  5. Papa Ken says:

    middleware/users.js needs
    const jwt = require(‘jsonwebtoken’);

    This didn’t work for me: token = req.headers.authorization.split(‘ ‘)[1];
    With express 4, I replaced that with : token = req.get(‘Authorization’);
    while the header is in the form in this curl example:
    curl -H “Content-Type: application/json” -H “Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1…….” http://localhost:3000/api/secret-route

  6. Steve D says:

    Thanks a lot for this great post!
    However, i met a problem with the secret-route. I post login with success, copy the token, and add it to authorisation after ‘Bearer’ and i always get “msg”: “Your session is not valid!”. Has someone get the same error?

    1. Lorenz says:

      I’m glad!
      Try to debug your incoming header data.

      const token = req.headers.authorization.split(' ')[1];
      Output this on your console. Maybe you set it wrong in Postman, if you use this program.

      1. Steve D says:

        It logs me the token, I get the same error. I had try with Angular and i get the same too

        1. Steve D says:

          In the middleware, jwt wasn’t defined … :p. Thanks again for this tuto!

  7. franco says:

    I wish I could deploy on xampp, how to do it?

    1. Lorenz says:

      I don’t know if that works with xampp. But if you want to deploy it localy, you can just download Node.js for Windows/Mac manually here: https://nodejs.org/en/

      LH

  8. ahsandev says:

    solved by config url/encoded to body parser
    app.use(bodyParser.urlencoded({ extended: false }))

    1. Lorenz says:

      Oh, perfect! 😉

      1. Ainur R says:

        i put that bodyparser url/encoded on my script but still get same error like you

    2. Ainur says:

      where you input this script?

      1. Erick says:

        I have the same error i have put it in index.js
        app.use(express.urlencoded({extended:false}));

        and i get the error
        {
        “msg”: “Your session is not valid!”
        }

  9. ahsandev says:

    i get some error,
    {
    “msg”: “Please enter a username with min. 3 chars”
    }
    im trying add body username at postman but still error
    can you share the github source code?

    1. Lorenz says:

      The whole code is listed on this page.. Check if you added the data correctly to postman:
      Tab “body”, format “raw”, select “JSON” and parse the data like this:
      {
      "username": "test",
      "password": "test",
      }

      LH

Your email address will not be published. Required fields are marked *

bold italic underline strikeThrough
insertOrderedList insertUnorderedList outdent indent
removeFormat
createLink unlink
code

This can also interest you