Authentication and authorization are the bedrock of secure systems, but their implementation often reveals a surprising truth: true security isn’t about adding more layers, but about understanding the fundamental relationships between identity, access, and data.

Let’s see this in action with a simplified example of a web application handling user profiles.

# Kubernetes Deployment for User Service
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: user-service
  template:
    metadata:
      labels:
        app: user-service
    spec:
      containers:
      - name: user-service
        image: my-docker-repo/user-service:v1.2.0
        ports:
        - containerPort: 8080
        env:
        - name: JWT_SECRET
          valueFrom:
            secretKeyRef:
              name: auth-secrets
              key: jwt-signing-key
        - name: DATABASE_URL
          value: postgresql://user:password@db.example.com:5432/users
# Python Flask User Service Snippet
from flask import Flask, request, jsonify
import jwt

app = Flask(__name__)

@app.route('/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    auth_header = request.headers.get('Authorization')
    if not auth_header:
        return jsonify({"error": "Authorization header missing"}), 401

    try:
        token = auth_header.split(" ")[1] # "Bearer <token>"
        decoded_token = jwt.decode(token, app.config['JWT_SECRET'], algorithms=["HS256"])
        # AuthZ: Check if the authenticated user can access this specific user's data
        if decoded_token['user_id'] != user_id and not decoded_token.get('is_admin', False):
            return jsonify({"error": "Forbidden"}), 403

        # Simulate fetching user data
        user_data = {"id": user_id, "username": f"user_{user_id}", "email": f"user{user_id}@example.com"}
        return jsonify(user_data)

    except jwt.ExpiredSignatureError:
        return jsonify({"error": "Token expired"}), 401
    except jwt.InvalidTokenError:
        return jsonify({"error": "Invalid token"}), 401
    except Exception as e:
        return jsonify({"error": str(e)}), 500

if __name__ == '__main__':
    app.config['JWT_SECRET'] = 'your_super_secret_key_here' # In production, this comes from env vars/secrets
    app.run(debug=True, port=8080)

This example illustrates Authentication (AuthN) – verifying who a user is, typically via a token like JWT – and Authorization (AuthZ) – determining what an authenticated user is allowed to do. The JWT contains claims like user_id and is_admin, which the user-service uses to make access control decisions. Encryption, while not directly shown in this snippet, would be used to protect sensitive data at rest (in the database) and in transit (e.g., TLS for network communication).

The core problem these patterns solve is preventing unauthorized access and data breaches. Without AuthN, any request could be from anyone. Without AuthZ, even a verified user could potentially access or modify any data. Encryption protects data from being understood even if it’s intercepted or stolen.

Internally, JWTs work by signing the token with a secret key. The server uses the same secret to verify the signature, ensuring the token hasn’t been tampered with and was issued by a trusted authority. Claims within the token (like roles or user IDs) are then used to enforce policies. For AuthZ, this can range from simple attribute-based checks (like comparing user_id to the requested resource owner) to more complex role-based or policy-based access control.

The exact levers you control are the signing keys for your tokens, the claims you include in those tokens, and the logic within your services that interprets those claims to grant or deny access. You also control how data is encrypted, which algorithms are used, and how encryption keys are managed.

A common misconception is that JWTs themselves encrypt the payload; they are signed (ensuring integrity and authenticity) but the payload is typically base64 encoded, not encrypted. For true confidentiality of the payload within the JWT, you’d need to encrypt the payload before encoding and signing it, or use a different mechanism altogether.

The next critical concept to explore is how to manage these secrets and keys securely, especially in distributed systems.

Want structured learning?

Take the full System Design course →