Auth & Security/
Lesson

AuthenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token. only protects users if the channelWhat is channel?A typed conduit in Go used to pass values between goroutines - can be unbuffered (synchronous) or buffered (async queue). itself is secure. You can have the world's best password hashingWhat is hashing?A one-way mathematical transformation that turns data (like a password) into a fixed-length string that can't be reversed. Used to store passwords securely., but if cookies are sent over plain HTTPWhat is http?The protocol browsers and servers use to exchange web pages, API data, and other resources, defining how requests and responses are formatted. or JavaScript can read your sessionWhat is session?A server-side record that tracks a logged-in user. The browser holds only a session ID in a cookie, and the server looks up the full data on each request. tokenWhat is token?The smallest unit of text an LLM processes - roughly three-quarters of a word. API pricing is based on how many tokens you use., attackers don't need your password at all. This lesson covers the transport and configuration layer that holds everything together.

Think of it like a bank vault. The door can be thick steel, but if the keys hang on a hookWhat is hook?A special function in React (starting with "use") that lets you add state, side effects, or other React features to a component without writing a class. outside and the alarm is turned off, the vault doesn't help much.

HTTPSWhat is https?HTTP with encryption added, so data traveling between your browser and a server can't be read or tampered with by anyone in between. in production

Why it's non-negotiable

Without HTTPS, any network observer, a coffee shop router, a corporate proxy, a compromised ISP, can read every tokenWhat is token?The smallest unit of text an LLM processes - roughly three-quarters of a word. API pricing is based on how many tokens you use. and cookieWhat is cookie?A small piece of data the browser stores and automatically sends with every request to the matching server, often used for sessions. your app sends. They can also inject content into your pages (MITM attacks). Modern browsers also block certain features (service workers, geolocation, camera access) on non-HTTPS origins.

// Redirect HTTP to HTTPS in production
app.use((req, res, next) => {
  if (process.env.NODE_ENV === 'production' && !req.secure) {
    return res.redirect(`https://${req.headers.host}${req.url}`);
  }
  next();
});

Most hosting platforms (Cloudflare, Railway, Render) handle TLSWhat is ssl/tls?Encryption protocols that secure the connection between a browser and a server, preventing eavesdropping on data in transit. termination for you. If yours doesn't, use Let's Encrypt or a reverse proxyWhat is reverse proxy?A server that sits in front of your app and forwards incoming requests to it, often handling SSL, caching, or load balancing along the way. like Caddy.

02

Secure cookieWhat is cookie?A small piece of data the browser stores and automatically sends with every request to the matching server, often used for sessions. configuration

Cookie flags that matter

Each flag on a cookie changes who can read it and when it gets sent. Getting them wrong is one of the most common auth mistakes in the wild:

res.cookie('sessionId', token, {
  httpOnly: true,                                   // No JS access (blocks XSS theft)
  secure: process.env.NODE_ENV === 'production',   // HTTPS only
  sameSite: 'lax',                                 // CSRF protection
  maxAge: 24 * 60 * 60 * 1000,                    // 24 hours
  path: '/'
});
FlagWhat it doesRisk if missing
httpOnlyBlocks document.cookie access in JSXSS can steal session tokens
SecureOnly sent over HTTPSToken leaks over HTTP connections
SameSite: LaxBlocks cookie in cross-site POST requestsCSRF attacks succeed
SameSite: StrictBlocks cookie in all cross-site requestsStricter CSRF protection, breaks SSO flows
`SameSite
None requires Secure: true and is needed for cross-domain auth flows (like embedding an iframe or using a separate API domain). For most apps, Lax` is the right default.
03

Security headers with HelmetWhat is helmet?An Express middleware package that sets recommended HTTP security headers (CSP, HSTS, X-Frame-Options) in one line.

One line, a dozen protections

Helmet is a middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it. collection that sets HTTPWhat is http?The protocol browsers and servers use to exchange web pages, API data, and other resources, defining how requests and responses are formatted. response headers browsers use to block common attacks. Without it, you're relying on defaults that vary across browser versions:

npm install helmet
import helmet from 'helmet';

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      scriptSrc: ["'self'"],
      imgSrc: ["'self'", 'data:', 'https:'],
      connectSrc: ["'self'", 'https://api.example.com']
    }
  },
  hsts: {
    maxAge: 31536000,      // 1 year
    includeSubDomains: true,
    preload: true
  }
}));
HeaderProtection
Content-Security-PolicyBlocks inline scripts and unauthorized resource loads (XSS mitigation)
X-Frame-OptionsPrevents your page from being loaded in an iframe (clickjacking)
X-Content-Type-OptionsStops MIME sniffing (browser running JS disguised as an image)
Strict-Transport-SecurityTells browsers to always use HTTPS for your domain
Referrer-PolicyControls what URL is sent in the Referer header
04

Rate limitingWhat is rate limiting?Restricting how many requests a client can make within a time window. Prevents brute-force attacks and protects your API from being overwhelmed.

Stopping brute-force attacks

Without rate limiting, an attacker can try millions of password combinations against your login endpointWhat is endpoint?A specific URL path on a server that handles a particular type of request, like GET /api/users. in minutes. Rate limiting imposes a ceiling on how many attempts are allowed from a single IP:

npm install express-rate-limit
import rateLimit from 'express-rate-limit';

// Tight limit for auth endpoints
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15-minute window
  max: 5,                     // 5 attempts max
  message: {
    error: 'Too many attempts',
    retryAfter: '15 minutes'
  },
  standardHeaders: true,
  legacyHeaders: false
});

// Looser limit for general API traffic
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100
});

app.post('/login', authLimiter, loginHandler);
app.post('/register', authLimiter, registerHandler);
app.use('/api/', apiLimiter);
Rate limiting by IP alone isn't perfect, shared NATs or VPNs can cause false positives. For high-value endpoints, combine it with account-level lockout after repeated failures from the same user account.
05

CSRFWhat is csrf?Cross-Site Request Forgery - an attack where a malicious website tricks your browser into sending a request to another site where you're logged in. protection

Understanding the attack

Cross-Site Request Forgery works because browsers automatically attach cookies to every request, even ones triggered by a third-party site. An attacker can create a page that submits a form to your APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses., the user's browser will include their sessionWhat is session?A server-side record that tracks a logged-in user. The browser holds only a session ID in a cookie, and the server looks up the full data on each request. cookieWhat is cookie?A small piece of data the browser stores and automatically sends with every request to the matching server, often used for sessions. without them knowing.

The modern solution is SameSite cookies (covered above). For older browsers or cookie-less auth flows, use CSRF tokens:

import csrf from 'csurf';

const csrfProtection = csrf({ cookie: { httpOnly: true, secure: true } });

app.use(csrfProtection);

// Send token to frontend
app.get('/form', (req, res) => {
  res.render('form', { csrfToken: req.csrfToken() });
});

// Form must include:
// <input type="hidden" name="_csrf" value="{{csrfToken}}">

If you're building a SPAWhat is spa?A Single Page Application that loads once and then dynamically updates content without full page reloads as the user navigates. that uses JWTs in Authorization headers (not cookies), you don't need CSRF tokens, browsers don't automatically attach custom headers to cross-originWhat is origin?The combination of protocol, domain, and port that defines a security boundary in the browser, like https://example.com:443. requests.

06

Account-level security

Lockout after failed attempts

Rate limitingWhat is rate limiting?Restricting how many requests a client can make within a time window. Prevents brute-force attacks and protects your API from being overwhelmed. protects at the IP level. Account lockout protects at the user level, preventing an attacker who rotates IPs:

async function checkLockout(email) {
  const user = await db.getUserByEmail(email);

  if (user.failedAttempts >= 5) {
    const lockoutMs = 30 * 60 * 1000; // 30 minutes
    if (Date.now() - user.lastFailedAt < lockoutMs) {
      throw new Error('Account locked. Try again in 30 minutes.');
    }
    await db.resetFailedAttempts(email);
  }
}

Secure password reset tokens

Password reset links are essentially temporary passwords. They must be random, time-limited, and single-use:

import crypto from 'crypto';

async function createPasswordReset(email) {
  const token = crypto.randomBytes(32).toString('hex');
  const expiresAt = Date.now() + 60 * 60 * 1000; // 1 hour

  await db.saveResetToken(email, {
    token: hashToken(token), // Store the hash, not the token
    expiresAt
  });

  await sendResetEmail(email, token); // Send the plain token
}
Store only a hash of the reset token in your database. If your database is breached, an attacker with the hash can't use it to reset passwords, they'd need the original token.
07

Quick reference

ConcernToolConfig
Security headershelmetapp.use(helmet())
Rate limitingexpress-rate-limitmax: 5 for auth, max: 100 for API
Cookie hardeningres.cookie flagshttpOnly: true, secure: true, sameSite: 'lax'
CSRF (cookie auth)csurf or SameSiteSameSite: Lax is usually enough
Input validationzodValidate before any DB access
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';

app.use(helmet());

const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5 });
app.post('/login', authLimiter, loginHandler);

res.cookie('token', value, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax',
  maxAge: 24 * 60 * 60 * 1000
});