Node.js Authentication — Complete Guide to User Authentication
In this tutorial, you will learn about Node.js Authentication. We cover key concepts, practical examples, and best practices to help you master this topic.
Node.js authentication involves verifying user identity through passwords, tokens, or third-party providers, and maintaining that authentication state across requests.
What You'll Learn
By the end of this tutorial, you'll implement password hashing with bcrypt, JWT token generation and verification, session-based authentication, OAuth2 with Passport.js, and secure login/logout flows.
Why Authentication Matters
Authentication protects user accounts and data. A broken authentication system leads to account takeover, data breaches, and loss of user trust. It's the most critical security feature.
Real-World Use
A SaaS application uses JWT-based authentication. Users log in with email/password, receive a JWT, and include it in the Authorization header for API requests. Tokens expire after 24 hours.
Authentication Learning Path
flowchart LR
A[Realtime Apps] --> B[Authentication]
B --> C[Authorization]
C --> D[File Upload]
D --> E[Caching]
A --> F{You Are Here}
style F fill:#f90,color:#fff
Password Hashing with bcrypt
npm install bcrypt
import bcrypt from "bcrypt";
const saltRounds = 10;
async function hashPassword(plainPassword) {
const hash = await bcrypt.hash(plainPassword, saltRounds);
return hash;
}
async function verifyPassword(plainPassword, hash) {
return await bcrypt.compare(plainPassword, hash);
}
// Usage
const hash = await hashPassword("userPassword123");
const match = await verifyPassword("userPassword123", hash); // true
JWT Authentication
npm install jsonwebtoken
import jwt from "jsonwebtoken";
const JWT_SECRET = process.env.JWT_SECRET || "your-secret-key";
function generateToken(user) {
return jwt.sign(
{ id: user.id, email: user.email, role: user.role },
JWT_SECRET,
{ expiresIn: "24h" }
);
}
function verifyToken(token) {
try {
return jwt.verify(token, JWT_SECRET);
} catch (err) {
return null;
}
}
Login Endpoint
app.post("/api/login", async (req, res) => {
const { email, password } = req.body;
const user = await db.users.findByEmail(email);
if (!user) return res.status(401).json({ error: "Invalid credentials" });
const valid = await verifyPassword(password, user.passwordHash);
if (!valid) return res.status(401).json({ error: "Invalid credentials" });
const token = generateToken(user);
res.json({ token, user: { id: user.id, email: user.email } });
});
Auth Middleware
function authenticate(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).json({ error: "No token provided" });
}
const token = authHeader.split(" ")[1];
const decoded = verifyToken(token);
if (!decoded) return res.status(401).json({ error: "Invalid or expired token" });
req.user = decoded;
next();
}
app.get("/api/profile", authenticate, (req, res) => {
res.json({ user: req.user });
});
Passport.js with OAuth2
npm install passport passport-google-oauth20
import passport from "passport";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "/auth/google/callback"
}, async (accessToken, refreshToken, profile, done) => {
let user = await db.users.findByGoogleId(profile.id);
if (!user) user = await db.users.create({ googleId: profile.id, name: profile.displayName });
done(null, user);
}));
app.get("/auth/google", passport.authenticate("google", { scope: ["profile", "email"] }));
app.get("/auth/google/callback", passport.authenticate("google", { session: false }),
(req, res) => {
const token = generateToken(req.user);
res.redirect(`/dashboard?token=${token}`);
}
);
Common Mistakes
1. Storing Passwords in Plain Text
Never store passwords as plain text. Always hash with bcrypt (or argon2). Hashing prevents credential theft if the database is breached.
2. Using Weak JWT Secrets
A weak secret allows attackers to forge tokens. Use a cryptographically random string of at least 256 bits.
3. Not Setting Token Expiration
Tokens that never expire can be used indefinitely if leaked. Set reasonable expiration (15 min to 24 hours) with refresh tokens.
4. Storing JWT in localStorage
localStorage is accessible by JavaScript (XSS vulnerability). Use httpOnly cookies for web apps.
5. Not Rate Limiting Login
Without rate limiting, attackers brute force passwords. Limit to 5 attempts per IP per minute.
Practice Questions
1. What is the difference between hashing and encryption?
Hashing is one-way (can't reverse). Encryption is two-way (can decrypt with key). Passwords should be hashed, not encrypted.
2. What are the parts of a JWT?
Header (algorithm), payload (claims), signature (verification). Encoded as three base64url segments separated by dots.
3. Why use bcrypt instead of SHA-256 for passwords?
bcrypt is intentionally slow and includes a salt, making brute force and rainbow table attacks impractical.
4. What is the difference between authentication and authorization?
Authentication verifies identity (who you are). Authorization determines access (what you can do).
5. Challenge: Implement a complete login/signup flow with password hashing and JWT.
app.post("/api/signup", async (req, res) => {
const { email, password } = req.body;
const hash = await bcrypt.hash(password, 10);
const user = await db.users.create({ email, passwordHash: hash });
const token = jwt.sign({ id: user.id }, JWT_SECRET, { expiresIn: "24h" });
res.status(201).json({ token });
});
app.post("/api/login", async (req, res) => {
const user = await db.users.findByEmail(req.body.email);
if (!user || !(await bcrypt.compare(req.body.password, user.passwordHash))) {
return res.status(401).json({ error: "Invalid credentials" });
}
res.json({ token: jwt.sign({ id: user.id }, JWT_SECRET, { expiresIn: "24h" }) });
});
FAQ
Mini Project: Auth System
Build a complete authentication system with signup, login, and protected routes.
import express from "express";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
const app = express();
app.use(express.json());
const JWT_SECRET = process.env.JWT_SECRET || "change-me";
const users = [];
app.post("/signup", async (req, res) => {
const { email, password } = req.body;
if (users.find(u => u.email === email)) return res.status(409).json({ error: "Email exists" });
const hash = await bcrypt.hash(password, 10);
users.push({ id: users.length + 1, email, passwordHash: hash });
res.status(201).json({ token: jwt.sign({ email }, JWT_SECRET, { expiresIn: "24h" }) });
});
app.post("/login", async (req, res) => {
const user = users.find(u => u.email === req.body.email);
if (!user || !(await bcrypt.compare(req.body.password, user.passwordHash))) {
return res.status(401).json({ error: "Invalid credentials" });
}
res.json({ token: jwt.sign({ email: user.email }, JWT_SECRET, { expiresIn: "24h" }) });
});
app.listen(3000);
What's Next
Node.js Authorization Node.js File Upload Node.js Caching Redis
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro