Skip to content

Latest commit

 

History

History
193 lines (152 loc) · 8.96 KB

File metadata and controls

193 lines (152 loc) · 8.96 KB

Lab: JSON Web Tokens (JWT) vs. Session-Based Authentication

Overview

In this lab, you'll learn how JSON Web Tokens (JWT) provide a modern, stateless alternative to traditional session-based authentication. You'll build a simple API service that issues and verifies JWTs for user authentication, and you'll see how this approach differs from classic session IDs.


Part 1: Session-Based Authentication (The Old Way)

  • Older web apps used session IDs stored in cookies.
  • When a user logged in, the server generated a random session ID, stored it in memory, Redis, or a database, and sent it to the client as a cookie.
  • On each request, the client sent the session ID cookie; the server checked its session store to see if the session was valid and which user it belonged to.
  • Drawback: The server must keep state for every active session, which doesn't scale well and requires extra infrastructure.

Part 2: JWT – Stateless Authentication

  • JWTs embed authentication claims (like username, expiration) directly in a signed token.
  • When a user logs in, the server creates a JWT, signs it with a secret, and sends it to the client (usually as an HttpOnly cookie).
  • On each request, the client sends the JWT; the server verifies the signature and expiration, and grants access if valid.
  • No session IDs or server-side session storage required!
  • JWT does NOT eliminate authentication logic—it just eliminates the need to store session state on the server.

Part 3: JWT Lab Implementation

  • Use a small in-memory user list (usernames and passwords; plaintext for this lab only).
  • When a user logs in successfully:
    • Create a JWT with claims: username, expiration time (e.g., 5 minutes from now).
    • Sign the JWT with a shared server secret.
    • Return the JWT to the client in an HttpOnly cookie.
  • For protected endpoints:
    • Read the JWT from the cookie.
    • Verify the signature and expiration.
    • If valid, grant access; if not, reject the request.
  • No session IDs or token records are stored on the server.

Example (using PyJWT):

pip install pyjwt

# ---
# Example curl commands for login and using JWT cookie:
# curl is not a browser, so cookies must be explicitly saved and reused using -c and -b.
#
# Login and save JWT cookie:
# curl -i -X POST http://localhost:5000/login \
#   -H "Content-Type: application/json" \
#   -d '{"username":"alice","password":"password1"}' \
#   -c cookies.txt
#
# Access a protected endpoint using the saved cookie:
# curl -i http://localhost:5000/protected \
#   -b cookies.txt
# ---
import jwt, datetime
from flask import Flask, request, jsonify, make_response

app = Flask(__name__)
SECRET = 'dev-secret-change-me'

users = {'alice': 'password1', 'bob': 'password2'}

@app.route('/login', methods=['POST'])
def login():
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')
    if users.get(username) == password:
        payload = {
            'username': username,
            'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=5)
        }
        token = jwt.encode(payload, SECRET, algorithm='HS256')
        resp = make_response({'message': 'Logged in'})
        resp.set_cookie('jwt', token, httponly=True, samesite='Lax')
        return resp
    return jsonify({'error': 'Invalid credentials'}), 401

@app.route('/protected')
def protected():
    token = request.cookies.get('jwt')
    if not token:
        return jsonify({'error': 'Missing token'}), 401
    try:
        payload = jwt.decode(token, SECRET, algorithms=['HS256'])
        return jsonify({'message': f'Hello, {payload["username"]}!'}), 200
    except jwt.ExpiredSignatureError:
        return jsonify({'error': 'Token expired'}), 401
    except jwt.InvalidTokenError:
        return jsonify({'error': 'Invalid token'}), 401

@app.route('/logout')
def logout():
    resp = make_response({'message': 'Logged out'})
    resp.set_cookie('jwt', '', expires=0)
    return resp

if __name__ == '__main__':
    app.run(debug=True)

Part 4: JWT Security Notes

  • JWTs are not encrypted by default—just base64-encoded and signed. Anyone can read the contents, but only the server with the secret can create/validate them.
  • Security comes from the signature, not secrecy of the contents.
  • JWTs are stateless: tokens cannot be instantly revoked. Use short expirations and delete the cookie on logout.
  • Since cookies are used, CSRF protections (like SameSite cookies) are important.

Part 5: Tradeoffs and Out of Scope

  • Tradeoffs:
    • Stateless: no server-side session storage, but no instant revocation.
    • Short-lived tokens are safer.
    • Logout = delete the cookie.
  • Out of scope for this lab:
    • OAuth, refresh tokens, public/private key JWTs, multi-service trust, advanced claims.

Challenge: Combine JWT and API Keys for Real-World Auth

For this challenge, modify your previous API key-based Flask app to support both JWT and API keys, just like real services such as Dockerhub or GitHub:

  1. JWT Login and Management:

    • Users log in with username and password.
    • On successful login, issue a JWT (as above) with claims like username, permissions, and expiration.
    • Use a function for handling JWT instead of doing it each requests
    • JWT is sent to the client in an HttpOnly cookie.
    • Authenticated users (with a valid JWT) can:
      • Generate new API keys (long, random, unique).
      • List, update (label/permissions), or revoke (delete) their API keys.
      • Change their username or password.
    • API keys should be stored in-memory (dictionary) for this lab, with fields: value, username, permissions, creation date, label/description.
  2. API Key Usage for CRUD:

    • For app CRUD operations, allow authentication via API key (sent in a header, e.g., X-API-Key).
    • When an API key is used, check its validity and permissions.
    • No JWT is required for API key-authenticated endpoints.
  3. JWT vs API Key:

    • JWT is for user login and managing API keys (interactive, short-lived).
    • API keys are for automation/scripts (long-lived, can be revoked/rotated).
    • This is how real services like Dockerhub work: you log in with a password, then create/manage API keys for automation.

JWT vs API Keys: Explicit Comparison

Feature JWT (Cookie-based) API Keys
Typical Use Interactive login Automation/scripts
Lifetime Short-lived Long-lived
Revocation Not easily revoked Revocable/rotatable
Usage Context Browser-friendly CLI, CI/CD, integrations
Permissions Usually user-wide Permission-scoped

JWTs are for interactive, browser-based sessions, while API keys are for automation, can be scoped, and are easier to manage for long-term integrations.

JWTs are not ideal for automation because they expire quickly, are harder to rotate safely, and are tied to user identity rather than application permissions.


About HttpOnly Cookies and Security

When you set a cookie with the HttpOnly flag (as in the JWT example), it tells the browser not to make this cookie accessible to JavaScript running on the page. This is a key security feature:

  • Protection from XSS: If an attacker manages to inject JavaScript into your site (via a cross-site scripting vulnerability), they cannot read or steal cookies marked as HttpOnly. This helps protect sensitive tokens like JWTs from being exfiltrated.
  • Safer than localStorage/sessionStorage: Data stored in localStorage or sessionStorage is always accessible to JavaScript, so XSS can easily leak tokens stored there. HttpOnly cookies are not exposed to JavaScript at all.
  • Automatic sending: Browsers automatically send cookies (including HttpOnly ones) with every request to the server for the relevant domain/path, so the client code doesn't need to manually attach the token.
  • Not a silver bullet: HttpOnly does not protect against all attacks (e.g., CSRF), but it is a strong defense against token theft via XSS.

Summary:

  • HttpOnly cookies are more secure for storing authentication tokens than regular cookies or browser storage, because browsers do not give JavaScript access to them.

Security Reminders:

  • API keys should be long, random, and never stored in plaintext in production (hashing is best, but plaintext is OK for this lab).
  • JWTs are short-lived and for interactive use; API keys are for automation and can be revoked/rotated.
  • Use HttpOnly and SameSite cookies for JWTs.
  • Never expose secrets in URLs or logs.
  • What happens if you delete a user but their JWT is still valid?
  • Logout deletes the cookie but does not invalidate already-issued JWTs server-side.

This challenge will help you understand how modern platforms combine JWT and API keys for secure, flexible authentication!