Skip to content

Node.js Authentication — Complete Guide to User Authentication

DodaTech Updated 2026-06-28 5 min read

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

What is the best password hashing algorithm?

bcrypt (cost factor 10-12) or argon2id. Both are memory-hard and resist GPU-based attacks.

Should I use JWT or sessions?

JWT scales better horizontally (no server-side storage). Sessions are easier to revoke. Use JWT for APIs, sessions for server-rendered apps.

How do I handle token refresh?

Issue a short-lived access token (15 min) and a long-lived refresh token (7 days). Store refresh tokens in database for revocation.

What is OAuth2?

OAuth2 is an authorization framework where users grant third-party apps limited access without sharing passwords. Used for social login.

How do I implement MFA/2FA?

Use speakeasy (TOTP) to generate secrets. Store in user record. Verify codes on login after password validation.

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