Nuxt 3 Authentication API with JWT and RSA Encrypted Login

Nuxt 3 Authentication API with JWT and RSA Encrypted Login

In this tutorial, you will learn how to build a secure, production-ready authentication REST API using Nuxt 3 (Nitro) as the backend.

We implement JWT authentication for stateless access control and add RSA encryption to securely encrypt user passwords on the client before sending them to the server. On the backend, passwords are decrypted using a private key and verified with bcrypt, ensuring strong security during login.

This step-by-step guide covers user registration, login, JWT-protected routes, profile access, logout, and Postman testing, all powered by a MySQL database. You’ll also learn essential security best practices for real-world applications.

📌 Overview

Build a secure REST API authentication system using:

  • Nuxt 3 backend (Nitro, no Express)

  • MySQL database

  • JWT for authentication tokens

  • RSA encryption to encrypt passwords on the client before sending to the backend

⚠️ JWT already secures authentication; RSA encrypts passwords in transit adding extra protection.

🧠 How Authentication Works

StepDescription
FrontendEncrypt password with RSA Public Key
BackendDecrypt password with RSA Private Key
Verify decrypted password with bcrypt
Issue JWT token on successful login
Protect routes with JWT middleware

✅ What This Tutorial Covers

  • Nuxt 3 backend setup (no Express)

  • MySQL integration with mysql2

  • RSA public/private key encryption helpers

  • Register & Login APIs

  • JWT-protected routes with middleware

  • Profile & Logout APIs

  • Postman testing

  • Production best practices

📦 Requirements

  • Node.js 18+

  • Nuxt 3

  • MySQL 8+

  • npm

  • OpenSSL (for key generation)

  • Postman (for API testing)

1️⃣ Create Nuxt 3 Project

npx nuxi init nuxt-rsa-auth cd nuxt-rsa-auth

2️⃣ Install Backend Dependencies

npm install mysql2 bcryptjs jsonwebtoken

3️⃣ Create Project Structure

Create folders:

mkdir -p server/api/auth \ server/middleware \ server/utils \ server/db \ storage/rsa

Create files:

touch server/api/auth/register.post.js \ server/api/auth/login.post.js \ server/api/auth/profile.get.js \ server/api/auth/logout.post.js \ server/middleware/auth.js \ server/utils/jwt.js \ server/utils/rsa.js \ server/db/mysql.js \ .env \ nuxt.config.ts \ .gitignore

4️⃣ Configure .gitignore

node_modules/ .env storage/rsa/private.pem

5️⃣ Environment Variables (.env)

DB_HOST=localhost DB_USER=root DB_PASSWORD= DB_NAME=nuxt_rsa_auth JWT_SECRET=super_secret_key JWT_EXPIRES=1h

6️⃣ Database Setup (MySQL)

CREATE DATABASE nuxt_rsa_auth; USE nuxt_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 );

7️⃣ Generate RSA Keys (OpenSSL)

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

8️⃣ MySQL Connection (server/db/mysql.js)

import mysql from 'mysql2/promise' export const db = mysql.createPool({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, })

9️⃣ JWT Helper (server/utils/jwt.js)

import jwt from 'jsonwebtoken' export function signToken(user) { return jwt.sign( { id: user.id, email: user.email }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES } ) } export function verifyToken(token) { return jwt.verify(token, process.env.JWT_SECRET) }

🔟 RSA Decryption Helper (server/utils/rsa.js)

import fs from 'fs' import path from 'path' import crypto from 'crypto' const privateKey = fs.readFileSync( path.resolve('storage/rsa/private.pem'), 'utf8' ) export function decryptPassword(encrypted) { return crypto.privateDecrypt( { key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING, }, Buffer.from(encrypted, 'base64') ).toString() }

1️⃣1️⃣ Register API (server/api/auth/register.post.js)

import bcrypt from 'bcryptjs' import { db } from '../../db/mysql' export default defineEventHandler(async (event) => { const { name, email, password } = await readBody(event) const hash = await bcrypt.hash(password, 10) await db.query( 'INSERT INTO users (name, email, password) VALUES (?, ?, ?)', [name, email, hash] ) return { message: 'User registered successfully' } })

1️⃣2️⃣ Login API (server/api/auth/login.post.js)

import bcrypt from 'bcryptjs' import { db } from '../../db/mysql' import { decryptPassword } from '../../utils/rsa' import { signToken } from '../../utils/jwt' export default defineEventHandler(async (event) => { const { email, password } = await readBody(event) const decrypted = decryptPassword(password) const [rows] = await db.query( 'SELECT * FROM users WHERE email = ?', [email] ) if (!rows.length) { throw createError({ statusCode: 401, statusMessage: 'Invalid credentials' }) } const user = rows[0] const valid = await bcrypt.compare(decrypted, user.password) if (!valid) { throw createError({ statusCode: 401, statusMessage: 'Invalid credentials' }) } return { user: { id: user.id, name: user.name, email: user.email }, access_token: signToken(user), token_type: 'Bearer', } })

1️⃣3️⃣ JWT Middleware (server/middleware/auth.js)

import { verifyToken } from '../utils/jwt' export default defineEventHandler((event) => { const url = event.node.req.url const urlWithoutQuery = url.split('?')[0] const publicRoutes = ['/api/auth/register', '/api/auth/login'] const isPublic = publicRoutes.some(route => urlWithoutQuery.startsWith(route)) if (isPublic) return const auth = getHeader(event, 'authorization') if (!auth || !auth.startsWith('Bearer ')) { throw createError({ statusCode: 401, statusMessage: 'Token required or invalid format' }) } const token = auth.split(' ')[1] try { event.context.user = verifyToken(token) } catch { throw createError({ statusCode: 401, statusMessage: 'Invalid token' }) } })

1️⃣4️⃣ Profile API (server/api/auth/profile.get.js)

export default defineEventHandler((event) => { return event.context.user })

1️⃣5️⃣ Logout API (server/api/auth/logout.post.js)

export default defineEventHandler(() => { return { message: 'Logout handled on client side' } })

▶️ Run the Application

npm run dev

Base URL:
http://localhost:3000/api/auth

🚀 Postman Testing

APIMethodBody / HeadersDescription
RegisterPOST{ "name": "StarCode Kh", "email": "starcodekh@example.com", "password": "12345678" }Creates a new user
LoginPOST{ "email": "starcodekh@example.com", "password": "ENCRYPTED_PASSWORD_BASE64" }Returns JWT token after decrypt+auth
ProfileGETHeader: Authorization: Bearer YOUR_JWT_TOKENGets logged-in user profile
LogoutPOSTHeader: Authorization: Bearer YOUR_JWT_TOKENHandles logout (client-side)

✅ Production Best Practices

  • Use HTTPS everywhere

  • Never commit private.pem

  • Rotate RSA keys periodically

  • Use short-lived JWT tokens

  • Rate-limit login requests

  • Store secrets securely in .env

🎯 Final Result

You now have a clean, standard, production-ready Nuxt 3 authentication API using:

  • 🔐 RSA-encrypted login passwords

  • 🔑 JWT authentication tokens

  • 🛡 MySQL database backend

Want the full source code?
Download the complete Nuxt 3 JWT Authentication with RSA example from 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