1. What is this application?
This is a classroom application designed to demonstrate three common API authentication methods:
Username and password are sent in an Authorization header.
A fixed key is sent in a custom header.
The user logs in once and receives a signed token.
2. Application architecture
The server returns either a success response such as 200 OK or an error such as 401 Unauthorized.
3. Project structure and setup
authentication-demo/
├── test.py
├── frontend.html
└── README.html
Install dependencies:
pip install flask pyjwt flask-cors gunicorn
Run:
python test.py
Open:
http://127.0.0.1:5000/playground
http://127.0.0.1:5000/docs4. Configuration in test.py
SECRET_KEY = os.environ.get("SECRET_KEY", "CHANGE_ME_TO_SOMETHING_LONG_RANDOM")
API_KEY = os.environ.get("API_KEY", "student123")
SECRET_KEY signs JWT tokens. API_KEY is the value checked by the API-key endpoint.
USERS = {
"alice": "password123",
"bob": "secret456",
}
This is only a fake in-memory user database for teaching.
5. Basic Authentication
Basic Auth sends a username and password in this form:
Authorization: Basic <base64(username:password)>
Step 1: Create route
@app.route("/basic", methods=["GET"])
def basic_auth():
Step 2: Read header
auth_header = request.headers.get("Authorization")
Step 3: Check Basic prefix
if not auth_header or not auth_header.startswith("Basic "):
return jsonify({"error": "Missing Basic auth header"}), 401
Step 4: Decode Base64
encoded = auth_header.split(" ")[1]
decoded = base64.b64decode(encoded).decode("utf-8")
Example result: alice:password123.
Step 5: Split username and password
username, password = decoded.split(":", 1)
Step 6: Verify
if USERS.get(username) != password:
return jsonify({"error": "Invalid username or password"}), 401
Try it
curl -u alice:password123 http://127.0.0.1:5000/basic
curl -u alice:WRONG http://127.0.0.1:5000/basic
6. API Key Authentication
The client sends a custom header:
X-API-Key: student123
Backend breakdown
key = request.headers.get("X-API-Key")
if key != API_KEY:
return jsonify({"error": "Invalid or missing API key"}), 401
return jsonify({
"message": "Access granted using API Key!",
"method": "API Key Auth"
})
Try it
curl -H "X-API-Key: student123" http://127.0.0.1:5000/apikey
curl -H "X-API-Key: wrong" http://127.0.0.1:5000/apikey7. JWT Authentication
JWT uses two phases:
Phase 1: Login
data = request.get_json(silent=True)
username = data.get("username")
password = data.get("password")
if USERS.get(username) != password:
return jsonify({"error": "Invalid credentials"}), 401
Create expiry time
payload = {
"user": username,
"exp": datetime.datetime.now(datetime.timezone.utc)
+ datetime.timedelta(minutes=30)
}
Sign token
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
Return token
return jsonify({"token": token, "expires_in": "30 minutes"})
Phase 2: Protect endpoint
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
return jsonify({"error": "Missing Bearer token"}), 401
token = auth_header.split(" ")[1]
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
return jsonify({
"message": f"Hello {payload['user']}!",
"method": "JWT Auth"
})
Try it
curl -X POST http://127.0.0.1:5000/login -H "Content-Type: application/json" -d '{"username":"alice","password":"password123"}'
curl -H "Authorization: Bearer <TOKEN>" http://127.0.0.1:5000/jwt8. Understanding JWT parts
header.payload.signature
| Part | Meaning |
|---|---|
| Header | Token type and algorithm. |
| Payload | Claims such as user and expiry. |
| Signature | Proves the token was not changed. |
9. The reusable decorator
@require_jwt
def jwt_protected_v2():
return jsonify({
"message": f"Hello {request.user}!"
})
The decorator performs token checking before the actual function runs. This avoids repeating the same validation code in every protected endpoint.
10. How frontend.html works
- Tabs switch between authentication methods.
- Input boxes allow correct or incorrect values.
fetch()sends real requests to Flask.- CSS classes show active, completed, or failed steps.
- A packet animation represents the request travelling through the network.
- The response panel displays HTTP status and JSON.
Important JavaScript snippets
const $ = id => document.getElementById(id);
This is a shortcut for finding an HTML element.
const sleep = ms => new Promise(r => setTimeout(r, ms));
This pauses an animation.
fetch("/apikey", {
headers: { "X-API-Key": key }
});
This sends the actual HTTP request.
11. HTTP status codes
| Code | Meaning |
|---|---|
| 200 | Request succeeded. |
| 400 | Client sent invalid or incomplete data. |
| 401 | Credentials/token are missing or invalid. |
| 404 | URL does not exist. |
| 405 | Wrong HTTP method. |
| 500 | Unexpected server error. |
12. Suggested classroom experiments
- Run Basic Auth correctly.
- Use a wrong password.
- Remove the Authorization header.
- Use the correct API key.
- Change one character in the API key.
- Login and copy the JWT.
- Call /jwt without a token.
- Tamper with the JWT.
- Use /debug/jwt to inspect the token.
- Send GET to /login and observe 405.
13. Production limitations
- Users are stored in memory.
- Passwords are plain text in this demo.
- Secrets have classroom defaults.
- CORS is open for convenience.
- The JWT debug endpoint should not be exposed publicly.
- There is no database, rate limiting, audit logging, or token revocation.
14. One-page memory summary
Send username and password every time.
Send a fixed application key.
Login once, then send a signed token.