Blog | G5 Cyber Security

Secure User Login: Cookies & JWT

TL;DR

Use HTTP-only cookies to store a refresh token and JWTs for authentication. Refresh tokens extend session life without repeatedly asking for passwords, while JWTs handle most requests. Prioritise security by rotating tokens and implementing proper expiry times.

1. Understanding the Problem

Keeping users logged in securely is tricky. Storing passwords directly is a huge no-no. Session IDs can be vulnerable to hijacking. Cookies are convenient but need careful handling. JWTs (JSON Web Tokens) offer a good solution, but they aren’t perfect on their own.

2. The Cookie & JWT Approach

This method combines the strengths of both cookies and JWTs:

3. Step-by-Step Implementation

  1. User Login: When a user logs in successfully:
    • Generate both an Access Token (JWT) and a Refresh Token.
    • Set the Access Token in local storage or session storage on the client side.
    • Set the Refresh Token as an HTTP-only cookie with appropriate security flags (Secure, SameSite). This is crucial!
  2. Subsequent Requests: For each API request:
    • Include the Access Token in the Authorization header (e.g., Bearer <access_token>).
    • If the server detects an invalid or expired Access Token, return a 401 Unauthorized error.
  3. Token Refresh: When the Access Token expires:
    • The client sends a request to a dedicated token refresh endpoint with the Refresh Token (from the cookie).
    • The server validates the Refresh Token.
      • If valid, generate a new Access Token and a new Refresh Token.
      • Return the new Access Token to the client.
      • Set the new Refresh Token as an HTTP-only cookie (overwriting the old one).
  4. Logout: When a user logs out:
    • Delete both the Access Token from client storage.
    • Delete the Refresh Token by setting its expiry date in the past on the server side (or overwrite it with an invalid token).

4. Code Examples

These are simplified examples; adapt them to your specific framework and libraries.

Generating JWTs (Node.js with jsonwebtoken)

const jwt = require('jsonwebtoken');

function generateAccessToken(user) {
  return jwt.sign({ userId: user.id }, 'your_secret_key', { expiresIn: '15m' }); // Short expiry
}

function generateRefreshToken(user) {
  return jwt.sign({ userId: user.id }, 'your_refresh_secret_key', { expiresIn: '7d' }); // Longer expiry
}

Setting Cookies (Node.js with express)

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

app.post('/login', (req, res) => {
  // ... authentication logic...
  const accessToken = generateAccessToken(user);
  const refreshToken = generateRefreshToken(user);

  res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: true, sameSite: 'strict' });
  res.json({ accessToken });
});

Validating Refresh Token (Node.js)

const jwt = require('jsonwebtoken');

function validateRefreshToken(refreshToken) {
  try {
    const decoded = jwt.verify(refreshToken, 'your_refresh_secret_key');
    return decoded;
  } catch (error) {
    return null; // Invalid token
  }
}

5. Security Considerations

Exit mobile version