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

In this article you will learn how to use Node.js, Express, JWT (JSON Web Tokens) and MySQL to create your own Rest API for user authentication – a complete Node.js login system.

This article is the first part of a two-part series to create 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 divided into two parts so that you are not bound to a Vue.js frontend, but can apply the Rest API we are developing in this article to other frontends like Angular or React as well.

This way to part 2: Vue.js Login System with Vuex & Axios

Our goal: Node.js Login – Rest API with Express, JWT and MySQL

We create a Node.js application that runs on our local server. For this purpose we have a MySQL database where our user data is stored. For authentication we have to query this data and can open a session for the user with the help of the JWT (JSON Web Token) extension.

At the end you have an executable application that you can deploy on your own server with the help of this guide. But now we finally want to start! 🙂

Here you can also find the tutorial in video form:

1. What is a Rest API?

A Rest API represents the interface between server and client. Via normal HTTP requests we reach the server and can execute programmed functions such as authenticating a user with the corresponding password.

Since this tutorial is not suitable for absolute beginners, I assume that you are already a bit familiar with Node.js, so we’ll skip the installation and get straight to the exciting part. If you are not familiar with Node.js yet, you can take a look at this Node.js beginner tutorial. After that you will have learned all the basics and can start using your Node.js login system.

2. Install dependencies

So our Node.js app is ready to install the dependencies. We need the following modules:

PackageDescription
expressWith this we create our own webserver for our Rest API
mysqlTo read/write in our database
uuidTo create IDs for later users
bcryptjs (Attention: not bcrypt)For encrypting/decrypting passwords
jsonwebtokenTo handle user sessions
corsSo that we can access the Rest API from our website.

We install these modules using the following CLI command:

npm install bcryptjs express jsonwebtoken mysql uuid cors

3. Set up MySQL database

For the database 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 only need a 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 via our Node.js application, we create our own class file, which we later include in our router.

// lib/db.js

const mysql = require('mysql');

const connection = mysql.createConnection({
	host: 'localhost',
	user: 'node-jwt',
	database: 'node-jwt',
	password: '********'
});

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

4. Setting up an Express Router and creating routes

Our entry file is the index.js and contains the starting of our web servers and the integration of the routes we define in the file routes/router.js.

// index.js

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

// set up port
const PORT = process.env.PORT || 3000;

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

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

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

In the router.js we define our routes and then pack the logic into them. The reason why we use an extra file here is the clarity. If your application has 20 or more routes at some point, the index.js will be chaotic. 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 for registration and /api/login for login. We also have the route /api/secret-route, which you should only be able to call if you are logged in. Currently every user can access it. More about this 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. This means that we check if the user has entered a password and the username complies with the guidelines. We later switch these queries as middleware into the call of our routes.

5. Creating Middleware (Validation)

A middleware is a small program, which is switched between two components. In this case, between our request and the actual registration, we have a middleware that 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({
				msg: '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({
				msg: 'Please enter a password with min. 6 chars'
			});
		}

		// password (repeat) does not match
		if (
			!req.body.password_repeat ||
			req.body.password != req.body.password_repeat
		) {
			return res.status(400).send({
				msg: 'Both passwords must match'
			});
		}

		next();
	}
};

When calling our /sign-up route, our middleware should be executed. To do this, 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;

6. Register (/sign-up) route (with JWT)

To add a new user to the database, we have to check if the username does not yet exist. If the user exists, an error message is issued. If the user does not yet exist, our module bcrypt is used to hash (encrypt) the entered password and then enter all data into the database.

// routes/router.js

router.post('/sign-up', userMiddleware.validateRegister, (req, res, next) => {
  db.query(
    `SELECT * FROM users WHERE LOWER(username) = LOWER(${db.escape(
			req.body.username
		)});`,
    (err, result) => {
      if (result.length) {
        return res.status(409).send({
          msg: 'This username is already in use!'
        });
      } else {
        // username is available
        bcrypt.hash(req.body.password, 10, (err, hash) => {
          if (err) {
            return res.status(500).send({
              msg: err
            });
          } else {
            // has hashed pw => add to database
            db.query(
              `INSERT INTO users (id, username, password, registered) VALUES ('${uuid.v4()}', ${db.escape(
								req.body.username
							)}, ${db.escape(hash)}, now())`,
              (err, result) => {
                if (err) {
                  throw err;
                  return res.status(400).send({
                    msg: err
                  });
                }
                return res.status(201).send({
                  msg: 'Registered!'
                });
              }
            );
          }
        });
      }
    }
  );
});

Important is the function db.escape(), e.g. in line 25. This masks passed parameters to avoid SQL injection. If the entry of the user is successful, the status code 201 (“created”) is returned and the function call is terminated.

Ever heard of Double Opt-In? It involves sending confirmation emails to verify a registration. In this tutorial you will learn how to implement Double Opt-In in your Node.js application (based on this tutorial).

7. Login (/login) route (with JWT)

In addition to the registration process, we have a login route to log in for already registered users. Here you can search for the appropriate database entry by user name. Then the entered password from the database is checked with the help of jwt.compare(). A short SQL query sets the last login date/time in line 44 to the current value.

// routes/router.js

router.post('/login', (req, res, next) => {
  db.query(
    `SELECT * FROM users WHERE username = ${db.escape(req.body.username)};`,
    (err, result) => {
      // user does not exists
      if (err) {
        throw err;
        return res.status(400).send({
          msg: err
        });
      }

      if (!result.length) {
        return res.status(401).send({
          msg: 'Username or password is incorrect!'
        });
      }

      // check password
      bcrypt.compare(
        req.body.password,
        result[0]['password'],
        (bErr, bResult) => {
          // wrong password
          if (bErr) {
            throw bErr;
            return res.status(401).send({
              msg: 'Username or password is incorrect!'
            });
          }

          if (bResult) {
            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({
              msg: 'Logged in!',
              token,
              user: result[0]
            });
          }
          return res.status(401).send({
            msg: 'Username or password is incorrect!'
          });
        }
      );
    }
  );
});

In lines 36 and 37 we pass variables that we want to “store” in the JWT token. This gives us access to these variables in the protected routes.

Earn money with your website or blog

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

You can also specify in line 40 how long the token should be valid. Values like “1h” or “3m” are valid here. You can also read about the individual values and parameters in the documentation.

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

If the login is successful, the user object and the token generated by JWT are returned. This token is important for all routes in which you should be logged in. In part 2 (Vue.js Frontend) you will learn how to pass this token 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

8. Protect routes with login

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

Therefore we create a new middleware in our users.js. The token is taken from the header of the request and verified by JWT.

// middleware/users.js

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

In the marked line you have to enter the same key with which you have already generated the JWT.

To protect a route now, 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!');
});

The req.userData contains the data we have stored in the JWT key (in this case username and userId). This allows us to read user-defined values from the database using the userId for protected routes, for example.

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

Testing Rest API with Postman

9. Conclusion

➡️ Continue with part 2: Vue.js Login System with Vuex & Axios

That?s it! In the first part we have now written a complete Rest API for validation and session handling for our application. You can use this system for your frontend (no matter if Angular, React or Vue). Thanks for reading! 🙂

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