Comparing Session-Based and Token-Based Authentication

A while back, a senior colleague of mine was migrating a service from session-based authentication to token-based authentication. That got me curious. I decided to dive deeper into the topic, experiment a bit, and see how these approaches work in practice.

What’s the Difference?

The key difference between session-based and token-based authentication lies in where the state lives:

  • In session-based authentication, the server maintains the state (session information).
  • In token-based authentication, the state is encoded in the token and lives on the client side.

Session-Based Authentication

In session-based authentication, after a user logs in, the server creates a session and assigns it a session ID. This session ID is stored on the client side (usually in a cookie). For every subsequent request, the client sends the session ID back to the server. The server checks its session store to validate the user.

Here’s a quick rundown of how it works with some Flask magic:

Example: Session-Based Authentication in Python

from flask import Flask, request, session, jsonify

app = Flask(__name__)
app.secret_key = 'supersecretkey'  # Used to sign session cookies

@app.route('/login', methods=['POST'])
def login():
    username = request.json.get('username')
    password = request.json.get('password')

    # Dummy credentials check
    if username == 'user' and password == 'pass':
        session['user_id'] = username  # Store the user's ID in the session
        return jsonify(message="Login successful"), 200
    return jsonify(message="Invalid credentials"), 401

@app.route('/protected', methods=['GET'])
def protected():
    if 'user_id' in session:
        return jsonify(message=f"Hello, {session['user_id']}!"), 200
    return jsonify(message="Unauthorized"), 401

@app.route('/logout', methods=['POST'])
def logout():
    session.pop('user_id', None)  # Remove user ID from session
    return jsonify(message="Logged out"), 200

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

How It Works:

  1. When the user logs in, the server creates a session and stores the user_id.
  2. The client gets a session cookie, which is sent with every request.
  3. The server checks the session store to validate the user.

Session-Based Authentication

It’s simple, but the server has to maintain the session state, which can get tricky when scaling.

Token-Based Authentication

Token-based authentication is all about moving the state to the client. After the user logs in, the server generates a token (often a JWT) that contains the user information. The client stores the token and sends it with every request. The server validates the token to authenticate the user.

Here’s how I played around with it:

Example: Token-Based Authentication in Python

import jwt
from flask import Flask, request, jsonify

app = Flask(__name__)
SECRET_KEY = 'supersecretkey'

# Create a JWT
def create_jwt(user_id):
    return jwt.encode({'user_id': user_id}, SECRET_KEY, algorithm='HS256')

# Decode a JWT
def decode_jwt(token):
    try:
        return jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
    except jwt.ExpiredSignatureError:
        return None
    except jwt.InvalidTokenError:
        return None

@app.route('/login', methods=['POST'])
def login():
    username = request.json.get('username')
    password = request.json.get('password')

    # Dummy credentials check
    if username == 'user' and password == 'pass':
        token = create_jwt(username)
        return jsonify(token=token), 200
    return jsonify(message="Invalid credentials"), 401

@app.route('/protected', methods=['GET'])
def protected():
    auth_header = request.headers.get('Authorization')
    if not auth_header or not auth_header.startswith('Bearer '):
        return jsonify(message="Unauthorized"), 401

    token = auth_header.split(" ")[1]
    decoded = decode_jwt(token)
    if decoded:
        return jsonify(message=f"Hello, {decoded['user_id']}!"), 200
    return jsonify(message="Unauthorized"), 401

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

How It Works:

  1. The server generates a token with the user’s information and sends it to the client.
  2. The client sends the token in the Authorization header for subsequent requests.
  3. The server validates the token using its secret key.

Token-Based Authentication

This approach is stateless, meaning the server doesn’t need to maintain a session store. Perfect for scaling!

Comparing the Two Approaches

Here’s a quick summary of what I learned:

Session-Based Authentication

  • The server maintains the session state.
  • Easier to invalidate sessions.
  • Great for single-domain applications where scaling isn’t a concern.

Token-Based Authentication

  • The client holds the state (in the form of a token).
  • Scales well horizontally because the server is stateless.
  • Ideal for distributed systems or APIs.

Further Reading

End
Built with Hugo
Theme Stack designed by Jimmy