Auth & Security/
Lesson

When you log into GitHub and then open the GitHub mobile app on a different device, still logged in, that cross-device experience is possible because modern auth doesn't rely on a shared server-side 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.. JWTs (JSON Web TokensWhat is jwt?JSON Web Token - a self-contained, signed token that carries user data (like user ID and role). The server can verify it without a database lookup.) let the server prove your identity without consulting a central store at all. That's powerful, and it comes with trade-offs worth understanding before you reach for the library. This lesson picks up where sessions left off and shows you how JWTs work end-to-end.

What a JWTWhat is jwt?JSON Web Token - a self-contained, signed token that carries user data (like user ID and role). The server can verify it without a database lookup. actually is

A JWT is three Base64URL-encoded JSONWhat is json?A text format for exchanging data between systems. It uses key-value pairs and arrays, and every programming language can read and write it. objects joined by dots:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.Header  (algorithm + token type)
eyJ1c2VySWQiOjQyLCJyb2xlIjoidXNlciJ9.Payload (claims: userId, role, expiry…)
SflKxwRJSMeKKF2QT4fwpMe...Signature (HMAC of header + payload)

The header and payloadWhat is payload?The data sent in the body of an HTTP request, such as the JSON object you include when creating a resource through a POST request. are just Base64, anyone can decode and read them. The signature is what makes tampering detectable. When the server receives a 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. it re-computes the signature using the secret key and compares it to the one in the token. If they match, the payload is trustworthy. If someone modified the payload (say, changing their role from "user" to "admin"), the signatures won't match and the token is rejected.

Important
"Signed" does not mean "encrypted." The payload is readable by anyone who holds the token. Never put passwords, credit card numbers, or other secrets in a JWT payload. Stick to identifiers and roles.

You can inspect any JWT at jwt.io.

02

Generating and verifying tokens

npm install jsonwebtoken
import jwt from 'jsonwebtoken';

const JWT_SECRET = process.env.JWT_SECRET;
const JWT_EXPIRES_IN = '15m';     // Access token: intentionally short
const REFRESH_EXPIRES_IN = '7d';  // Refresh token: longer lived

function generateTokens(user) {
  const accessToken = jwt.sign(
    { userId: user.id, role: user.role },
    JWT_SECRET,
    { expiresIn: JWT_EXPIRES_IN }
  );

  const refreshToken = jwt.sign(
    { userId: user.id, type: 'refresh' },
    JWT_SECRET,
    { expiresIn: REFRESH_EXPIRES_IN }
  );

  return { accessToken, refreshToken };
}

// Login endpoint
app.post('/auth/login', async (req, res) => {
  const { email, password } = req.body;

  const user = await db.findUserByEmail(email);
  if (!user || !await verifyPassword(password, user.passwordHash)) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  const tokens = generateTokens(user);

  // Persist the refresh token so we can revoke it later if needed
  await db.saveRefreshToken(user.id, tokens.refreshToken);

  res.json({
    accessToken: tokens.accessToken,
    refreshToken: tokens.refreshToken,
    expiresIn: 900  // 15 minutes in seconds
  });
});

// Middleware: verify access token on every protected request
function requireAuth(req, res, next) {
  const authHeader = req.headers.authorization;

  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Token required' });
  }

  const token = authHeader.substring(7); // strip "Bearer "

  try {
    const decoded = jwt.verify(token, JWT_SECRET);
    req.userId = decoded.userId;
    req.userRole = decoded.role;
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid or expired token' });
  }
}

The try/catch around jwt.verify() is not optional, the library throws on an invalid or expired 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.. Without it, an unhandled error crashes the request and leaks stack traces to the client.

03

The refresh tokenWhat is refresh token?A long-lived credential used solely to obtain new short-lived access tokens without requiring the user to log in again. pattern

Access tokens expire in minutes. That's the point, if one gets stolen, the damage window is short. But you don't want users to log in again every 15 minutes. Refresh tokens solve this: they live longer (days or weeks) and are used exclusively to get new access tokens.

app.post('/auth/refresh', async (req, res) => {
  const { refreshToken } = req.body;

  if (!refreshToken) {
    return res.status(401).json({ error: 'Refresh token required' });
  }

  try {
    const decoded = jwt.verify(refreshToken, JWT_SECRET);

    // Confirm the token hasn't been revoked
    const storedToken = await db.findRefreshToken(decoded.userId);
    if (storedToken !== refreshToken) {
      return res.status(401).json({ error: 'Invalid refresh token' });
    }

    const user = await db.findUserById(decoded.userId);
    const tokens = generateTokens(user);

    // Rotate: replace old refresh token with new one
    await db.saveRefreshToken(user.id, tokens.refreshToken);

    res.json(tokens);
  } catch (err) {
    return res.status(401).json({ error: 'Invalid refresh token' });
  }
});

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. rotation (issuing a new refresh token on every refresh) is a security best practice. If a refresh token is stolen and used, the legitimate user's next refresh attempt will fail, alerting you to a potential compromise.

Gotcha
JWTs cannot be "revoked" the way a session can be destroyed. Once an access token is issued it is valid until it expires. If you need instant revocation (for account bans, security incidents) you must maintain a server-side blocklist, which reintroduces state. This is not a flaw; it's a trade-off you choose deliberately.
04

Sending tokens from the client

// After login, store the tokens
localStorage.setItem('accessToken', response.accessToken);
localStorage.setItem('refreshToken', response.refreshToken);

// Attach the access token to every API request
fetch('/api/protected', {
  headers: {
    'Authorization': `Bearer ${localStorage.getItem('accessToken')}`
  }
});

// Intercept 401s and refresh automatically
async function fetchWithAuth(url, options = {}) {
  let token = localStorage.getItem('accessToken');

  let response = await fetch(url, {
    ...options,
    headers: { ...options.headers, 'Authorization': `Bearer ${token}` }
  });

  if (response.status === 401) {
    const newToken = await refreshAccessToken();
    response = await fetch(url, {
      ...options,
      headers: { ...options.headers, 'Authorization': `Bearer ${newToken}` }
    });
  }

  return response;
}

The code above uses localStorage for simplicity, but HttpOnlyWhat is httponly?A cookie flag that prevents JavaScript from reading the cookie. It stops XSS attacks from stealing session tokens or authentication cookies. cookies are more secure for web apps, JavaScript cannot read them, so a successful XSSWhat is xss?Cross-Site Scripting - an attack where malicious JavaScript is injected into a web page and runs in other users' browsers, stealing data or hijacking sessions. attack cannot steal the 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.. localStorage is acceptable if you have a robust Content Security PolicyWhat is content security policy?An HTTP header that tells the browser which sources of scripts, styles, and other resources are allowed to load on your page, blocking unauthorized code. and your app has a low XSS risk surface.

05

Sessions vs. JWTWhat is jwt?JSON Web Token - a self-contained, signed token that carries user data (like user ID and role). The server can verify it without a database lookup.: choosing the right tool

Neither approach is universally better. The right choice depends on your architecture.

FactorSessionsJWT
StateStateful (server stores data)Stateless (token is self-contained)
RevocationInstant (destroy the record)Delayed (must wait for expiry or use a blocklist)
ScalabilityRequires shared session storeWorks across any number of servers
Mobile / SPAAwkward (cookies require careful CORS setup)Natural fit (Authorization header)
MicroservicesEach service must hit the session storeEach service verifies the token independently
Implementation complexityLowMedium (token rotation, storage strategy)

Use sessions when you're building a traditional server-rendered web app and need instant logout or account revocation. Use JWTs when you're building an APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. consumed by mobile clients, SPAs on a different domain, or a microservicesWhat is microservices?An architecture where an application is split into small, independently deployed services that communicate over the network, each owning its own data. architecture where statelessness is a design requirement.