Table of Contents
Authentication vs Authorization
Authentication verifies who a user is, while Authorization determines what an authenticated user can do. This lesson covers implementing both in Node.js applications.
Password Hashing with bcrypt
Never store passwords in plain text. Use bcrypt to hash passwords securely:
# Install bcrypt
npm install bcrypt
const bcrypt = require("bcrypt");
class PasswordManager {
static async hashPassword(plainPassword) {
try {
const saltRounds = 12;
const hashedPassword = await bcrypt.hash(plainPassword, saltRounds);
return hashedPassword;
} catch (err) {
throw new Error("Error hashing password");
}
}
static async comparePassword(plainPassword, hashedPassword) {
try {
const isMatch = await bcrypt.compare(plainPassword, hashedPassword);
return isMatch;
} catch (err) {
throw new Error("Error comparing passwords");
}
}
}
// Usage example
async function demonstratePasswordHashing() {
const password = "mySecretPassword123";
// Hash password
const hashed = await PasswordManager.hashPassword(password);
console.log("Hashed password:", hashed);
// Verify password
const isValid = await PasswordManager.comparePassword(password, hashed);
console.log("Password is valid:", isValid);
// Wrong password
const isInvalid = await PasswordManager.comparePassword("wrongPassword", hashed);
console.log("Wrong password is valid:", isInvalid);
}
demonstratePasswordHashing();
JWT (JSON Web Tokens) Authentication
JWTs are a popular way to handle authentication in modern web applications:
# Install jsonwebtoken
npm install jsonwebtoken
const jwt = require("jsonwebtoken");
const JWT_SECRET = process.env.JWT_SECRET || "your-secret-key";
const JWT_EXPIRES_IN = "7d";
class TokenManager {
static generateToken(payload) {
return jwt.sign(payload, JWT_SECRET, {
expiresIn: JWT_EXPIRES_IN
});
}
static verifyToken(token) {
try {
return jwt.verify(token, JWT_SECRET);
} catch (err) {
throw new Error("Invalid or expired token");
}
}
static decodeToken(token) {
return jwt.decode(token);
}
}
// Middleware for protecting routes
const authenticateToken = (req, res, next) => {
const authHeader = req.headers["authorization"];
const token = authHeader && authHeader.split(" ")[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({
error: "Access token required"
});
}
try {
const decoded = TokenManager.verifyToken(token);
req.user = decoded;
next();
} catch (err) {
return res.status(403).json({
error: "Invalid or expired token"
});
}
};
module.exports = { TokenManager, authenticateToken };
Complete Authentication System
const express = require("express");
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const mysql = require("mysql2/promise");
const app = express();
app.use(express.json());
// Database connection
const pool = mysql.createPool({
host: "localhost",
user: "root",
password: "password",
database: "auth_demo"
});
const JWT_SECRET = process.env.JWT_SECRET || "your-secret-key";
// User registration
app.post("/api/register", async (req, res) => {
try {
const { username, email, password } = req.body;
// Validation
if (!username || !email || !password) {
return res.status(400).json({
error: "Username, email, and password are required"
});
}
if (password.length < 6) {
return res.status(400).json({
error: "Password must be at least 6 characters long"
});
}
// Check if user already exists
const [existingUsers] = await pool.execute(
"SELECT id FROM users WHERE email = ? OR username = ?",
[email, username]
);
if (existingUsers.length > 0) {
return res.status(400).json({
error: "User with this email or username already exists"
});
}
// Hash password
const hashedPassword = await bcrypt.hash(password, 12);
// Create user
const [result] = await pool.execute(
"INSERT INTO users (username, email, password, created_at) VALUES (?, ?, ?, NOW())",
[username, email, hashedPassword]
);
// Generate JWT token
const token = jwt.sign(
{ userId: result.insertId, username, email },
JWT_SECRET,
{ expiresIn: "7d" }
);
res.status(201).json({
message: "User registered successfully",
token,
user: {
id: result.insertId,
username,
email
}
});
} catch (err) {
console.error("Registration error:", err);
res.status(500).json({ error: "Internal server error" });
}
});
// User login
app.post("/api/login", async (req, res) => {
try {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({
error: "Email and password are required"
});
}
// Find user
const [users] = await pool.execute(
"SELECT id, username, email, password FROM users WHERE email = ?",
[email]
);
if (users.length === 0) {
return res.status(401).json({
error: "Invalid email or password"
});
}
const user = users[0];
// Verify password
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
return res.status(401).json({
error: "Invalid email or password"
});
}
// Generate JWT token
const token = jwt.sign(
{ userId: user.id, username: user.username, email: user.email },
JWT_SECRET,
{ expiresIn: "7d" }
);
res.json({
message: "Login successful",
token,
user: {
id: user.id,
username: user.username,
email: user.email
}
});
} catch (err) {
console.error("Login error:", err);
res.status(500).json({ error: "Internal server error" });
}
});
// Authentication middleware
const authenticateToken = (req, res, next) => {
const authHeader = req.headers["authorization"];
const token = authHeader && authHeader.split(" ")[1];
if (!token) {
return res.status(401).json({
error: "Access token required"
});
}
try {
const decoded = jwt.verify(token, JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
return res.status(403).json({
error: "Invalid or expired token"
});
}
};
// Protected route
app.get("/api/profile", authenticateToken, async (req, res) => {
try {
const [users] = await pool.execute(
"SELECT id, username, email, created_at FROM users WHERE id = ?",
[req.user.userId]
);
if (users.length === 0) {
return res.status(404).json({ error: "User not found" });
}
res.json({ user: users[0] });
} catch (err) {
res.status(500).json({ error: "Internal server error" });
}
});
app.listen(3000, () => {
console.log("Auth server running on http://localhost:3000");
});
Role-Based Authorization
// Authorization middleware
const authorize = (roles = []) => {
return async (req, res, next) => {
try {
// Get user role from database
const [users] = await pool.execute(
"SELECT role FROM users WHERE id = ?",
[req.user.userId]
);
if (users.length === 0) {
return res.status(404).json({ error: "User not found" });
}
const userRole = users[0].role;
// Check if user has required role
if (roles.length && !roles.includes(userRole)) {
return res.status(403).json({
error: "Insufficient permissions"
});
}
req.user.role = userRole;
next();
} catch (err) {
res.status(500).json({ error: "Authorization error" });
}
};
};
// Admin-only route
app.get("/api/admin/users",
authenticateToken,
authorize(["admin"]),
async (req, res) => {
try {
const [users] = await pool.execute(
"SELECT id, username, email, role, created_at FROM users"
);
res.json({ users });
} catch (err) {
res.status(500).json({ error: "Internal server error" });
}
}
);
// Moderator or Admin route
app.delete("/api/posts/:id",
authenticateToken,
authorize(["admin", "moderator"]),
async (req, res) => {
// Delete post logic
res.json({ message: "Post deleted" });
}
);
Session-Based Authentication
const express = require("express");
const session = require("express-session");
const MySQLStore = require("express-mysql-session")(session);
const app = express();
app.use(express.json());
// Session store configuration
const sessionStore = new MySQLStore({
host: "localhost",
port: 3306,
user: "root",
password: "password",
database: "auth_demo"
});
// Session middleware
app.use(session({
key: "session_cookie_name",
secret: "session_cookie_secret",
store: sessionStore,
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 1000 * 60 * 60 * 24 // 24 hours
}
}));
// Login route
app.post("/api/login", async (req, res) => {
// ... authentication logic ...
if (isPasswordValid) {
// Store user info in session
req.session.userId = user.id;
req.session.username = user.username;
req.session.isAuthenticated = true;
res.json({
message: "Login successful",
user: {
id: user.id,
username: user.username,
email: user.email
}
});
}
});
// Session-based authentication middleware
const requireAuth = (req, res, next) => {
if (req.session && req.session.isAuthenticated) {
next();
} else {
res.status(401).json({ error: "Authentication required" });
}
};
// Protected route
app.get("/api/dashboard", requireAuth, (req, res) => {
res.json({
message: "Welcome to dashboard",
user: {
id: req.session.userId,
username: req.session.username
}
});
});
// Logout route
app.post("/api/logout", (req, res) => {
req.session.destroy((err) => {
if (err) {
return res.status(500).json({ error: "Could not log out" });
}
res.json({ message: "Logged out successfully" });
});
});
Exercise
Build a complete authentication system that includes:
- User registration with email verification
- Login with JWT tokens
- Password reset functionality
- Role-based access control
- Rate limiting for authentication endpoints
What's Next?
In the next lesson, we'll explore testing Node.js applications and implementing proper error handling.