DEV Community

gautam kumar
gautam kumar

Posted on

User Authentication API with Express, JWT, Bcrypt, and MySQL

This application is a simple authentication server built with Express, using JSON Web Tokens (JWT) for session management and bcrypt for securely storing passwords. Users can register and log in to access protected routes. MySQL is used for storing user data.

Steps for Register Route
Check If the input email is already registered.

  • If yes return("User already exists").
  • If no Insert the User details in the DB, return("Success")

Steps for Login Route
Check if the input email exists in the DB or not

  • If NO return("Invalid email")
  • If YES check if the input password is correct for the email or not If NO return("Invalid password").If YES Create the JWT token and send it either in the Cookie or in the response

VarifyAuthToken Middleware for Protected Route

  • Get the token either from the req.cookie or from the req.headers.
  • Valid ate the token using the SECRET_KEY. If it matches return a success response and send the user details. Else send the Invalid Token error.

Technologies Used

1. Express.js: Web framework for handling routes and middleware.
2. bcrypt.js: Library for hashing passwords securely.
3. jsonwebtoken: Library for creating and verifying JWT tokens.
4. mysql2: MySQL client for Node.js with support for Promises.
5. cookie-parser: Middleware for parsing cookies.

Code Breakdown

1. Import Required Libraries

const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const cookieParser = require('cookie-parser');
const mysql = require('mysql2/promise');
Enter fullscreen mode Exit fullscreen mode
  • express: Provides functions to create routes and handle HTTP requests.
  • jsonwebtoken: Used to create and verify JWTs for user sessions.
  • bcryptjs: Used for hashing passwords before storing them in the database.
  • cookie-parser: Middleware to parse cookies from incoming HTTP requests.
  • mysql2/promise: MySQL client with promise support, allowing asynchronous operations.

2. Initialize Express App and Define Constants

const app = express();
const PORT = 3000;
const JWT_SECRET = 'your_jwt_secret_key';
Enter fullscreen mode Exit fullscreen mode
  • app: The Express application instance.
  • PORT: The port the server will listen on.
  • JWT_SECRET: A secret key used to sign JWTs. Replace this with a secure, randomly generated value in production.

3. Database Connection Setup

const db = await mysql.createConnection({
    host: 'localhost',
    user: 'your_MySql_username',
    password: 'your_MySql_password',
    database: 'users'
});

Enter fullscreen mode Exit fullscreen mode
  • db: The MySQL database connection object.
  • mysql.createConnection(): Establishes a connection to the MySQL database using async/await, which is needed for non-blocking queries.
  • host: Yout MySql application hostname, If you are running it on localhost, put localhost only, if you have deployed your MySql to a server, use the server hostname with PORT.
  • user: Your MySql Username
  • password: Your MySql Password
  • database: The Database name

4. Middleware Setup

app.use(express.json());
app.use(cookieParser());
Enter fullscreen mode Exit fullscreen mode
  • express.json(): Middleware to parse JSON request bodies.
  • cookieParser(): Middleware to parse cookies, allowing us to read cookies from req.cookies.

5. Register Route

/register
This route registers a new user by hashing their password and saving it in the database.

app.post('/register', async (req, res) => {
    const { name, email, password } = req.body;

    try {
        // CHECK IF USER ALREADY EXISTS
        const [rows] = await db.execute('SELECT * FROM users WHERE email = ?', [email]);
        if (rows.length > 0) {
            return res.status(400).json({ message: 'User already exists' });
        }

        // HASH THE PASSWORD
        const hashedPassword = await bcrypt.hash(password, 10);

        // SAVE THE USER IN THE DATABASE 
        const result = await db.query('INSERT INTO userDB (name, email, password) VALUES (?, ?, ?)', [user.name, user.email, hashedPassword]);
        if(result[0].insertId){
            res.status(201).json({ message: 'User registered successfully!', insertId: result[0].insertId });
        }
        res.status(500).json({message: 'Something went wrong in DB execution'});
    } catch (error) {
        console.error(error);
        res.status(500).json({ message: 'Server error' });
    }
});

Enter fullscreen mode Exit fullscreen mode
  • db.execute(): Executes a query to check if a user with the given email already exists. If they do, return a 400 status.
  • bcrypt.hash(password, 10): Hashes the password with a salt rounds value of 10 for security.
  • db.execute() (Insert): Saves the user's name, email, and hashed password in the database.

6. Login Route

/login
This route logs in an existing user by checking their credentials and generating a JWT token.

app.post('/login', async (req, res) => {
    const { email, password } = req.body;

    try {
        // CHECK IF USER EXISTS OR NOT
        const [rows] = await db.execute('SELECT * FROM users WHERE email = ?', [email]);
        const user = rows[0];
        if (!user) {
            return res.status(400).json({ message: 'User not found' });
        }

        // CHECK IF INPUT PASSWORD IS VALID FOR INPUT EMAIL
        const isMatch = await bcrypt.compare(password, user.password);
        if (!isMatch) {
            return res.status(400).json({ message: 'Invalid credentials' });
        }

        // CREATE THE JWT TOKEN
        const token = jwt.sign({ id: user.id, name: user.name, email: user.email }, JWT_SECRET, { expiresIn: '1h' });

        // SET THE JWT TOKEN IN THE COOKIE
        res.cookie('token', token, {
            httpOnly: true,
            secure: process.env.NODE_ENV === 'production',
            sameSite: 'Strict',
            maxAge: 3600000 // 1 HR EXPIRATION
        });

        res.json({ message: 'Logged in successfully!' });
    } catch (error) {
        console.error(error);
        res.status(500).json({ message: 'Server error' });
    }
});

Enter fullscreen mode Exit fullscreen mode
  • bcrypt.compare(password, user.password): Verifies if the hashed password matches the one stored in the database.
  • jwt.sign(): Creates a JWT that includes user information (e.g., ID, name, and email). The token expires in 1 hour. jwt.sign() method takes two arguments payload, JWT_SECRET, options(optional)
  • res.cookie(): Sets a cookie with the JWT, secured by httpOnly (only accessible by the server) and sameSite settings.

7. JWT Verification Middleware

The verifyAuthToken middleware ensures that only requests with a valid JWT token can access protected routes.

const verifyAuthToken = (req, res, next) => {
    const token = req.cookies.token;
    if (!token) return res.status(403).json({ message: 'Token is missing.' });

    try {
        const decoded = jwt.verify(token, JWT_SECRET);
        req.user = decoded;
        next();
    } catch (err) {
        res.status(401).json({ message: 'Invalid token.' });
    }
};

Enter fullscreen mode Exit fullscreen mode
  • req.cookies.token: Extracts the token from cookies.
  • jwt.verify(token, JWT_SECRET): Verifies the token using the JWT secret key. If valid, decoded contains the token’s payload, which is assigned to req.user.
  • next(): Proceeds to the next middleware if the token is valid.

8. Protected Route - /protected

A sample protected route accessible only to authenticated users. It returns a personalized greeting using the user’s name from the token.

app.get('/protected', verifyAuthToken, (req, res) => {
    res.json({ message: `Hello, ${req.user.name}! This is a protected route.` });
});

Enter fullscreen mode Exit fullscreen mode
  • verifyAuthToken: Middleware applied to check the user’s authentication status.
  • req.user.name: Accesses the user’s name from the decoded JWT payload.

9. Start the Server

The server listens on the defined PORT.

app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

Enter fullscreen mode Exit fullscreen mode

Full Code

const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const cookieParser = require('cookie-parser');
const mysql = require('mysql2/promise');

const app = express();
const PORT = 3000;
const JWT_SECRET = 'your_jwt_secret_key';

// MySQL CONNECTION SETUP
const db = await mysql.createConnection({
    host: 'localhost',
    user: 'your_MySql_username',
    password: 'your_MySql_password',
    database: 'users'
});

// MIDDLEWARE
app.use(express.json());
app.use(cookieParser());

// ROUTES
app.post('/register', async (req, res) => {
    const { name, email, password } = req.body;

    try {
        // CHECK IF USER ALREADY EXISTS
        const [rows] = await db.execute('SELECT * FROM users WHERE email = ?', [email]);
        if (rows.length > 0) {
            return res.status(400).json({ message: 'User already exists' });
        }

        // HASH THE PASSWORD
        const hashedPassword = await bcrypt.hash(password, 10);

        // SAVE THE USER IN THE DATABASE
        const result = await db.query('INSERT INTO userDB (name, email, password) VALUES (?, ?, ?)', [user.name, user.email, hashedPassword]);
        if(result[0].insertId){
            res.status(201).json({ message: 'User registered successfully!', insertId: result[0].insertId });
        }
        res.status(500).json({message: 'Something went wrong in DB execution'});
    } catch (error) {
        console.error(error);
        res.status(500).json({ message: 'Server error' });
    }
});

// LOGIN ROUTE
app.post('/login', async (req, res) => {
    const { email, password } = req.body;

    try {
        // CHECK IF USER EXISTS OR NOT
        const [rows] = await db.execute('SELECT * FROM users WHERE email = ?', [email]);
        const user = rows[0];
        if (!user) {
            return res.status(400).json({ message: 'User not found' });
        }

        // CHECK IF INPUT PASSWORD IS VALID FOR INPUT EMAIL
        const isMatch = await bcrypt.compare(password, user.password);
        if (!isMatch) {
            return res.status(400).json({ message: 'Invalid credentials' });
        }

        // CREATE THE JWT TOKEN
        const token = jwt.sign({ id: user.id, name: user.name, email: user.email }, JWT_SECRET, { expiresIn: '1h' });

        // SET JWT TOKEN IN THE COOKIE
        res.cookie('token', token, {
            httpOnly: true,
            secure: process.env.NODE_ENV === 'production',
            sameSite: 'Strict',
            maxAge: 3600000 // 1 HR EXPIRATION TIME
        });

        res.json({ message: 'Logged in successfully!' });
    } catch (error) {
        console.error(error);
        res.status(500).json({ message: 'Server error' });
    }
});

// MIDDLEWARE TO VERIFY THE JWT TOKEN
const verifyAuthToken = (req, res, next) => {
    const token = req.cookies.token;
    if (!token) return res.status(403).json({ message: 'Token is missing.' });

    try {
        const decoded = jwt.verify(token, JWT_SECRET);
        req.user = decoded;
        next();
    } catch (err) {
        res.status(401).json({ message: 'Invalid token.' });
    }
};

// PROTECTED ROUTE
app.get('/protected', verifyAuthToken, (req, res) => {
    res.json({ message: `Hello, ${req.user.name}! This is a protected route.` });
});

// START THE SERVER
app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

Enter fullscreen mode Exit fullscreen mode

Summary

This application:

  1. Allows user registration by hashing passwords and storing user data in MySQL.
  2. Supports secure login with JWT authentication.
  3. Uses cookies to store the JWT on the client side.
  4. Provides middleware for verifying JWTs before granting access to protected routes.

Top comments (0)