HTTP Digest Authentication — Secure Challenge-Response Auth for APIs
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
- Client requests a protected resource
- Server responds with 401 and
WWW-Authenticate: Digestheader containingrealm,nonce,opaque - Client computes
MD5(MD5(username:realm:password):nonce:MD5(method:uri)) - Client sends
Authorization: Digestwith the computed hash - 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
- What cryptographic hash does Digest Auth use?
- How does Digest Auth prevent replay attacks?
- What is the purpose of the nonce in Digest Auth?
- Why can't you compare a user's password hash (bcrypt/argon2) directly with Digest Auth?
- What are the main advantages of Digest Auth over Basic Auth?
Answers:
- MD5 (though MD5 is considered cryptographically broken, it still provides basic protection against casual interception).
- 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.
- The nonce is a one-time random value generated by the server that prevents replay attacks. Each 401 challenge has a unique nonce.
- 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.
- 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
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