Skip to content

HTTP Digest Authentication — Secure Challenge-Response Auth for APIs

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about HTTP Digest Authentication. We cover key concepts, practical examples, and best practices to help you master this topic.

HTTP Digest Authentication improves on Basic Auth by hashing credentials with a server-provided nonce, preventing password exposure even without HTTPS encryption.

What You'll Learn

How Digest Auth works, the MD5 challenge-response flow, server implementation, and when Digest Auth is preferred over Basic Auth.

Why It Matters

Unlike Basic Auth which sends passwords in plain Base64, Digest Auth never transmits the password directly. The client proves knowledge of the password by hashing it with a server-provided nonce. This protects against replay attacks even over unencrypted connections.

Real-World Use

Digest Auth is commonly used in embedded devices (routers, IoT, IP cameras), legacy enterprise systems, and situations where HTTPS is unavailable but authentication is required.

flowchart LR
    A["Client"] -->|"Request"| B["Server"]
    B -->|"401 + nonce, realm, opaque"| A
    A -->|"Hash(nonce, username, password, realm)"| B
    B -->|"Compare hash"| C["Match? → 200 OK"]
    B -->|"No match"| D["401 Unauthorized"]
    style A fill:#dbeafe,stroke:#2563eb
    style B fill:#fef3c7,stroke:#d97706
    style C fill:#dcfce7,stroke:#16a34a
    style D fill:#fecaca,stroke:#dc2626

The Challenge-Response Flow

  1. Client requests a protected resource
  2. Server responds with 401 and WWW-Authenticate: Digest header containing realm, nonce, opaque
  3. Client computes MD5(MD5(username:realm:password):nonce:MD5(method:uri))
  4. Client sends Authorization: Digest with the computed hash
  5. Server recomputes the hash and compares

Why Digest Auth Is More Secure

Digest Auth never sends the password. The client proves password knowledge by including it in a hash computation. The server-provided nonce ensures each response is unique, preventing replay attacks.

Code Example: Flask Digest Auth Implementation

from flask import Flask, request, jsonify
import hashlib, os

app = Flask(__name__)

USERS = {"admin": "secret123"}
REALM = "Protected Area"

@app.route("/api/secure")
def secure_endpoint():
    auth = request.headers.get("Authorization", "")
    if not auth.startswith("Digest "):
        return challenge_digest()

    params = parse_digest_auth(auth)
    user = USERS.get(params.get("username"))
    if not user:
        return jsonify({"error": "Unauthorized"}), 401

    ha1 = hashlib.md5(
        f"{params['username']}:{REALM}:{user}".encode()
    ).hexdigest()
    ha2 = hashlib.md5(
        f"GET:{params['uri']}".encode()
    ).hexdigest()
    expected = hashlib.md5(
        f"{ha1}:{params['nonce']}:{ha2}".encode()
    ).hexdigest()

    if params.get("response") == expected:
        return jsonify({"message": "Authenticated", "user": params["username"]})
    return challenge_digest()

def challenge_digest():
    nonce = hashlib.md5(os.urandom(16)).hexdigest()
    header = f'Digest realm="{REALM}", nonce="{nonce}", opaque="{nonce}"'
    return jsonify({"error": "Unauthorized"}), 401, {"WWW-Authenticate": header}

def parse_digest_auth(auth_string):
    params = {}
    for part in auth_string[7:].split(","):
        key, value = part.strip().split("=", 1)
        params[key] = value.strip('"')
    return params

if __name__ == "__main__":
    app.run()

Expected output:

$ curl -v --digest -u admin:secret123 http://localhost:5000/api/secure
< HTTP/1.1 401 UNAUTHORIZED
< WWW-Authenticate: Digest realm="Protected Area", nonce="..."
> Authorization: Digest username="admin", realm="Protected Area", nonce="...", uri="/api/secure", response="..."
< HTTP/1.1 200 OK
{"message":"Authenticated","user":"admin"}

Code Example: Client with curl

# curl handles the Digest challenge-response automatically
curl --digest -u admin:secret123 https://api.example.com/secure

# Without --digest, curl uses Basic Auth
curl -u admin:secret123 https://api.example.com/secure

Code Example: Client with Python

import requests
from requests.auth import HTTPDigestAuth

response = requests.get(
    "https://api.example.com/secure",
    auth=HTTPDigestAuth("admin", "secret123")
)
print(response.status_code)  # 200

The requests library handles the entire Digest challenge-response cycle automatically.

Common Mistakes

1. Storing Passwords in Plain Text

The server needs the original password to compute HA1. Store passwords hashed with MD5(username:realm:password) for Digest Auth compatibility.

2. Reusing Nonces

If the server reuses the same nonce, replay attacks become possible. Generate a unique nonce for each 401 challenge.

3. Using Digest Without Quality of Protection (qop)

The qop parameter with auth value provides counter-measurement and replay protection. Digest without qop is vulnerable to replay attacks.

4. Thinking Digest Replaces HTTPS

Digest protects the password but not the response data. An attacker can still see or modify the API response. Use HTTPS for full protection.

5. Not Handling Multiple Challenges

Clients may need to preemptively send Digest auth (without the initial 401 round-trip). Support both challenge-response and preemptive modes.

Practice Questions

  1. What cryptographic hash does Digest Auth use?
  2. How does Digest Auth prevent replay attacks?
  3. What is the purpose of the nonce in Digest Auth?
  4. Why can't you compare a user's password hash (bcrypt/argon2) directly with Digest Auth?
  5. What are the main advantages of Digest Auth over Basic Auth?

Answers:

  1. MD5 (though MD5 is considered cryptographically broken, it still provides basic protection against casual interception).
  2. The server generates a unique nonce for each challenge. The client includes this nonce in the hash, ensuring the response is valid only for that specific challenge.
  3. The nonce is a one-time random value generated by the server that prevents replay attacks. Each 401 challenge has a unique nonce.
  4. Digest Auth requires the plaintext password (or a specific MD5 hash of it) to compute the response. Modern password hashes (bcrypt, argon2) use a different algorithm and salt.
  5. Digest Auth never sends the password in plain text, protects against replay attacks with nonces, and provides some security even over unencrypted connections.

Challenge: Implement a middleware function require_digest_auth that protects any Flask route with Digest Authentication, supporting multiple users and configurable realm.

FAQ

Is Digest Auth more secure than Basic Auth?

Yes, because Digest Auth never transmits the password — it sends a hash. Digest Auth also protects against replay attacks with nonces. However, it still requires HTTPS for complete protection.

Why is MD5 a problem for Digest Auth?

MD5 is cryptographically broken and vulnerable to collision attacks. While MD5 still provides adequate protection for Digest Auth's use case, it is not recommended for new systems.

Does Digest Auth protect the response body?

No. Digest Auth only protects the credentials. The response body is still sent in plain text (or encrypted by HTTPS). Always use HTTPS.

Is Digest Auth widely supported by HTTP clients?

Yes. curl, wget, requests (Python), axios (Node.js), and most HTTP libraries support Digest Auth natively.

Should I use Digest Auth for new APIs?

No. Use token-based authentication (JWT, OAuth2) or API keys with HTTPS. Digest Auth is primarily for legacy systems.

Mini Project

Create a Flask application that uses Digest Auth to protect an admin dashboard endpoint. Include multiple users with different roles and demonstrate the two-round-trip challenge-response flow using curl.

What's Next

Now that you understand HTTP authentication methods, move to API Key Authentication which is the most common method for public API access.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro