Module 1 - Intro to Authentication

Introduction to Authentication

Authentication is a complex and vital topic in web development. It's complex because new security threats emerge daily, and it's crucial because most of our lives depend on online services and computer systems that hold sensitive data.

Learning Objectives

Authentication Fundamentals

Understand the difference between authentication and authorization, and learn how to implement secure authentication systems.

Password Security

Master password hashing, storage best practices, and protection against common security vulnerabilities.

Session Management

Learn how to implement and manage user sessions securely using modern web technologies.

Module Content

Authentication Basics

  • What is Authentication?
  • Authentication vs Authorization
  • Common Authentication Methods

Password Security

  • Password Hashing
  • bcrypt Implementation
  • Password Strength Requirements

Session Management

  • Session Basics
  • Session Storage
  • Session Security

Authentication Fundamentals

Authentication vs Authorization

Authentication is the process by which our Web API verifies a client's identity that is trying to access a resource. This is different from authorization, which comes after authentication and determines what type of access a user should have.

Adding authentication to a Web API requires that an API can:

  • Register user accounts
  • Login to prove identity
  • Logout of the system to invalidate the user's access until they log in again
  • Add a way for users to reset their passwords

Proper authentication is difficult. There is a constant race between security experts with innovative ways to protect our information and attackers coming up with ways to circumvent those security measures.

Password Security

The rule of thumb is: NEVER, EVER, under no circumstances, store user passwords in plain text. There are two main approaches to secure password storage:

Password Hashing vs. Encryption

  • Encryption goes two ways. It utilizes plain text and private keys to generate encrypted passwords and then reverses the process to match an original password.
  • Cryptographic hashing only goes one way: parameters + input = hash. It is pure; given the same parameters and input, it generates the same hash.

If the database of users and keys is compromised with encryption, it's possible to decrypt the passwords to their original values. This is why cryptographic hashing is the preferred method for storing user passwords.

Code Example - Hashing with bcrypt:

const bcrypt = require('bcryptjs');

// Hashing a password before saving to DB
async function registerUser(user) {
  const rounds = 8; // complexity/work factor
  const hash = await bcrypt.hash(user.password, rounds);
  
  // Store user with hashed password
  return saveUser({
    ...user,
    password: hash
  });
}

Password Strength & Security

Password length alone is not enough to slow password guessing, but long passwords are generally better than short, complicated ones. It's a trade-off between convenience and security.

Brute-Force Attack Mitigation

A common way that attackers circumvent hashing algorithms is by pre-calculating hashes for all possible character combinations using a rainbow table. To defend against this, we use:

Key Derivation Functions = [Hash] + [Time]

We use algorithms optimized for security rather than speed. This adds computational cost to password verification, making brute-force attacks much more time-consuming.

Code Example - Verifying passwords with bcrypt:

const bcrypt = require('bcryptjs');

async function loginUser(username, password) {
  // Get user from database
  const user = await getUserByUsername(username);
  
  if (!user) {
    return null; // User not found
  }
  
  // Compare provided password with stored hash
  const isValid = await bcrypt.compare(password, user.password);
  
  if (isValid) {
    return user; // Password matches
  } else {
    return null; // Invalid password
  }
}

Session Management

Protecting Routes - Middleware

To restrict access to resources and allow access only for authenticated users, we can create middleware functions:

Code Example - Authentication middleware:

// Middleware to check if user is authenticated
function restricted(req, res, next) {
  if (req.session && req.session.user) {
    next(); // User is authenticated, proceed
  } else {
    res.status(401).json({ 
      message: 'You must be logged in to access this resource' 
    });
  }
}

// Example of a protected route
app.get('/api/protected-resource', restricted, (req, res) => {
  res.json({ 
    message: 'You have access to this protected resource',
    user: req.session.user
  });
});

// Role-based authorization middleware
function checkRole(role) {
  return (req, res, next) => {
    if (req.session && req.session.user && req.session.user.role === role) {
      next();
    } else {
      res.status(403).json({ 
        message: 'Access forbidden: insufficient privileges' 
      });
    }
  };
}

// Admin-only route
app.get('/api/admin', restricted, checkRole('admin'), (req, res) => {
  res.json({ message: 'Admin dashboard data' });
});

In-Memory Sessions

Sessions are a way to persist authentication information across requests. After a user authenticates, the server creates a session and sends a session identifier to the client, which is then included in subsequent requests.

Code Example - Setting up express-session:

const express = require('express');
const session = require('express-session');
const app = express();

app.use(session({
  name: 'session-cookie',
  secret: 'this should be stored in an env variable',
  cookie: {
    maxAge: 1000 * 60 * 60, // 1 hour in milliseconds
    secure: process.env.NODE_ENV === 'production', // use HTTPS in production
    httpOnly: true, // prevents client-side JS from reading the cookie
  },
  resave: false,
  saveUninitialized: false,
}));

Implementing Login and Logout

Once we have session management set up, we can implement login and logout functionality:

Code Example - Login/Logout routes:

// Login route
app.post('/api/login', async (req, res) => {
  const { username, password } = req.body;
  
  try {
    const user = await loginUser(username, password);
    
    if (user) {
      // Create session and store user info
      req.session.user = {
        id: user.id,
        username: user.username,
        role: user.role
      };
      res.status(200).json({ 
        message: 'Logged in successfully',
        user: req.session.user 
      });
    } else {
      res.status(401).json({ 
        message: 'Invalid credentials' 
      });
    }
  } catch (error) {
    res.status(500).json({ 
      message: 'Server error during login' 
    });
  }
});

// Logout route
app.get('/api/logout', (req, res) => {
  // Destroy the session
  if (req.session) {
    req.session.destroy(err => {
      if (err) {
        res.status(500).json({ message: 'Logout failed' });
      } else {
        res.status(200).json({ message: 'Logged out successfully' });
      }
    });
  } else {
    res.status(200).json({ message: 'No active session' });
  }
});

Guided Project

Authentication Implementation

Build a secure authentication system using Node.js and Express

View Project

Resources

Tools & Libraries

  • bcryptjs - For password hashing
  • express-session - For session management
  • client-sessions - Alternative session management library