Node.js JWT Authentication with RSA-Encrypted Login Passwords

This tutorial shows how to build a secure REST API authentication system using Node.js, Express, MySQL, JWT, and RSA encryption for login passwords.

⚠️ JWT already secures authentication.
RSA is added here to encrypt passwords on the client before sending them to the backend, providing an extra layer of protection.

🧠 How It Works

Authentication Flow

Frontend

Encrypt password using RSA Public Key

Backend

Decrypt password using RSA Private Key Verify password using bcrypt Issue JWT access token

✅ What This Tutorial Covers

  • Node.js + Express authentication

  • MySQL database integration

  • RSA public / private key encryption

  • Secure Register & Login APIs

  • JWT-protected routes

  • Logout handling

  • Production-ready best practices

📦 Requirements

  • Node.js 18+

  • MySQL 8+

  • npm

  • OpenSSL enabled

  • Postman (for testing)

  • Basic JavaScript knowledge

📁 Create node-rsa-auth Project Structure

# Root project mkdir node-rsa-auth cd node-rsa-auth # Source folders mkdir -p src/config src/controllers src/middleware src/routes # Source files touch src/server.js touch src/config/db.js touch src/config/jwt.js touch src/controllers/authController.js touch src/middleware/authMiddleware.js touch src/routes/authRoutes.js # RSA key storage mkdir -p storage/rsa touch storage/rsa/private.pem touch storage/rsa/public.pem # Environment & package files touch .env npm init -y

1️⃣ Node.js Project

cd node-rsa-auth

Open the project in VS Code:

code .

2️⃣ Install Dependencies

npm install express mysql2 bcryptjs jsonwebtoken dotenv cors npm install nodemon --save-dev

Update package.json:

"scripts": { "dev": "nodemon src/server.js" }

3️⃣ Project Structure

node-rsa-auth/ ├── src/ │ ├── config/ │ │ ├── db.js │ │ └── jwt.js │ ├── controllers/ │ │ └── authController.js │ ├── middleware/ │ │ └── authMiddleware.js │ ├── routes/ │ │ └── authRoutes.js │ └── server.js ├── storage/ │ └── rsa/ │ ├── private.pem 🔐 DO NOT COMMIT │ └── public.pem 🔓 ├── .env └── package.json

4️⃣ Environment Configuration

Edit .env:

PORT=3000 DB_HOST=localhost DB_USER=root DB_PASSWORD= DB_NAME=node_rsa_auth JWT_SECRET=super_secret_key JWT_EXPIRES=1h

5️⃣ Database Setup (MySQL)

CREATE DATABASE node_rsa_auth; USE node_rsa_auth; CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100), email VARCHAR(100) UNIQUE, password VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );

✔️ Make sure the database and table exist.

6️⃣ Generate RSA Keys (Login Encryption)

openssl genrsa -out storage/rsa/private.pem 2048 openssl rsa -in storage/rsa/private.pem -pubout -out storage/rsa/public.pem

📁 RSA Key Structure

storage/rsa/ ├── private.pem 🔐 Keep secret (DO NOT COMMIT) └── public.pem 🔓 Safe to share with frontend

7️⃣ MySQL Connection

src/config/db.js

const mysql = require('mysql2/promise'); const db = mysql.createPool({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, }); module.exports = db;

8️⃣ JWT Helper

src/config/jwt.js

const jwt = require('jsonwebtoken'); exports.signToken = (user) => { return jwt.sign( { id: user.id, email: user.email }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES } ); };

9️⃣ Authentication Controller

src/controllers/authController.js

const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const bcrypt = require('bcryptjs'); const db = require('../config/db'); const { signToken } = require('../config/jwt'); const privateKey = fs.readFileSync( path.join(__dirname, '../../storage/rsa/private.pem'), 'utf8' ); // Register exports.register = async (req, res) => { const { name, email, password } = req.body; const hash = await bcrypt.hash(password, 10); await db.query( 'INSERT INTO users (name, email, password) VALUES (?, ?, ?)', [name, email, hash] ); res.status(201).json({ message: 'User registered successfully' }); }; // Login (RSA encrypted) exports.login = async (req, res) => { const { email, password } = req.body; let decryptedPassword; try { decryptedPassword = crypto.privateDecrypt( { key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING, }, Buffer.from(password, 'base64') ).toString(); } catch { return res.status(400).json({ message: 'Password decryption failed' }); } const [rows] = await db.query( 'SELECT * FROM users WHERE email = ?', [email] ); if (!rows.length) { return res.status(401).json({ message: 'Invalid credentials' }); } const user = rows[0]; const valid = await bcrypt.compare(decryptedPassword, user.password); if (!valid) { return res.status(401).json({ message: 'Invalid credentials' }); } const token = signToken(user); res.json({ user: { id: user.id, name: user.name, email: user.email }, access_token: token, token_type: 'Bearer', }); }; // Profile exports.profile = async (req, res) => { res.json(req.user); }; // Logout exports.logout = async (req, res) => { res.json({ message: 'Logout handled on client side' }); };

🔟 JWT Middleware

src/middleware/authMiddleware.js

const jwt = require('jsonwebtoken'); module.exports = (req, res, next) => { const authHeader = req.headers.authorization; if (!authHeader) { return res.status(401).json({ message: 'Token required' }); } const token = authHeader.split(' ')[1]; try { req.user = jwt.verify(token, process.env.JWT_SECRET); next(); } catch { res.status(401).json({ message: 'Invalid token' }); } };

1️⃣1️⃣ Routes

src/routes/authRoutes.js

const express = require('express'); const router = express.Router(); const auth = require('../controllers/authController'); const authMiddleware = require('../middleware/authMiddleware'); router.post('/register', auth.register); router.post('/login', auth.login); router.get('/profile', authMiddleware, auth.profile); router.post('/logout', authMiddleware, auth.logout); module.exports = router;

1️⃣2️⃣ Server Setup

src/server.js

require('dotenv').config(); const express = require('express'); const cors = require('cors'); const authRoutes = require('./routes/authRoutes'); const app = express(); app.use(cors()); app.use(express.json()); app.use('/api/auth', authRoutes); app.listen(process.env.PORT, () => { console.log(`Server running at http://localhost:${process.env.PORT}`); });

▶️ Run the Application

npm run dev

Base URL:

http://localhost:3000

🚀 Postman Testing

Register

POST /api/auth/register
{ "name": "StarCode Kh", "email": "starcodekh@example.com", "password": "12345678" }

Login (RSA Encrypted)

Encrypt password using public.pem, then send:

POST /api/auth/login
{ "email": "starcodekh@example.com", "password": "ENCRYPTED_PASSWORD_BASE64" }

Get Profile

GET /api/auth/profile Authorization: Bearer {token}

Logout

POST /api/auth/logout Authorization: Bearer {token}

🔁 Authentication Flow Summary

Register → Password hashed
Login → Password encrypted (RSA)
Backend → Decrypt + bcrypt verify
JWT → Issued
Protected routes → Bearer token

✅ Production Best Practices

  • Always use HTTPS

  • Never commit private.pem

  • Rotate RSA keys

  • Use short-lived JWT tokens

  • Rate-limit login requests

  • Store secrets in .env

🎯 Final Result

You now have a clean, standard, production-ready Node.js authentication system using:

🔐 RSA-encrypted login passwords
🔑 JWT authentication
🛡 MySQL database

Want the full source code?

Download the complete Node.js JWT Authentication with RSA example on my GitHub repo here.

Souy Soeng

Souy Soeng

Hi there 👋, I’m Soeng Souy (StarCode Kh)
-------------------------------------------
🌱 I’m currently creating a sample Laravel and React Vue Livewire
👯 I’m looking to collaborate on open-source PHP & JavaScript projects
💬 Ask me about Laravel, MySQL, or Flutter
⚡ Fun fact: I love turning ☕️ into code!

Post a Comment

CAN FEEDBACK
close