This is a small demonstration Flask app that shows several common web app features you can point out to someone learning the basics:
- A simple login flow using environment-backed credentials and
flask-loginfor session management. - CSRF protection via
Flask-WTF(with the webhook endpoint intentionally CSRF-exempt). - A webhook receiver endpoint that can verify an HMAC-SHA256 signature.
- An example API endpoint protected by a simple token header.
- Rate limiting using
Flask-Limiter(in-memory by default for demo). - A small client script and a UI demo to show the server performing an external GET request.
This repo is for demonstration only. Do not copy these auth patterns directly into production.
- Create and activate a virtual environment (macOS / zsh example):
python3 -m venv .venv
source .venv/bin/activate- Install dependencies:
pip install -r requirements.txt- Copy the example env and edit values:
cp .env.example .env
# edit .env and set SECRET_KEY, WEBHOOK_SECRET, etc.- Run the app:
FLASK_APP=app FLASK_ENV=development flask runNow open http://127.0.0.1:5000 in your browser.
SECRET_KEY— Flask session/CSRF secret. Set to a long random string.DEMO_USER/DEMO_PASS— Simple demo credentials used by the/loginform.API_KEY— Token expected by the/api/dataendpoint when called withX-API-KEYheader or?api_key=query param.WEBHOOK_SECRET— If set, the/webhookendpoint will validate HMAC-SHA256 signatures.
All variables are read using python-dotenv (the .env file is loaded automatically by the app during startup).
All example requests return JSON (or HTML for pages) and are intentionally simple.
-
GET /— Home page (rendered HTML). Shows links and a demo widget to trigger a server-side GET. -
GET, POST /login— Simple login form. UseDEMO_USER/DEMO_PASSfrom.envto authenticate. On success the session is established viaflask-loginand the user can access protected pages. -
GET /logout— Logs out the current session and clears the session cookie. -
GET /protected— A route decorated with@login_required(only accessible after login). -
POST /webhook— Webhook receiver. Characteristics:- Expects a JSON body (Content-Type: application/json).
- CSRF is exempted (webhooks are server-to-server and don't use browser cookies).
- If
WEBHOOK_SECRETis set, the endpoint verifies an HMAC-SHA256 signature using the raw request body. - Supported signature headers (checked in this order):
X-Signature,X-Hub-Signature-256,X-Hub-Signature.- The header may be
sha256=<hex>or just<hex>— the code normalizes either form.
- The header may be
- On signature mismatch the endpoint returns HTTP 401. On success it returns
{ "status": "received" }.
Example curl (no signature):
curl -X POST -H "Content-Type: application/json" -d '{"hello": "world"}' http://127.0.0.1:5000/webhook
Example curl with signature (shell example computing HMAC-SHA256):
SECRET=webhook-secret BODY='{"hello":"world"}' SIG=$(printf "%s" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -binary | xxd -p -c 256) curl -X POST -H "Content-Type: application/json" -H "X-Signature: sha256=$SIG" -d "$BODY" http://127.0.0.1:5000/webhook
-
GET /api/data— Example API endpoint protected by a token. Characteristics:- Requires
X-API-KEYheader or?api_key=query parameter that matchesAPI_KEYfrom.env. - Rate-limited by the configured limiter.
- Returns a JSON payload that includes the current session username (if any).
Example curl:
curl -H "X-API-KEY: secret-token" http://127.0.0.1:5000/api/data - Requires
-
GET /demo/get— Server-side demo route that performs a GET to another URL and returns the result.- Query params:
url— target URL (defaulthttps://httpbin.org/get)api_key— optional key sent asX-API-KEYheader to the target
- This is the server-side operation triggered by the UI demo on the home page.
Example (server will request httpbin and return its JSON):
curl "http://127.0.0.1:5000/demo/get?url=https://httpbin.org/get" - Query params:
templates/index.htmlincludes a small demo card where you can enter a URL and optional API key and click Send GET. That triggers a call to the server's/demo/getroute and displays the server's response.scripts/send_get_example.pyis a tiny CLI client that performs the same GET (useful to show how to call an API from Python).
- The server reads the raw request body bytes (
request.get_data()), not the parsed JSON. This is important because the HMAC must operate on the exact byte sequence the sender hashed. - The server expects an HMAC-SHA256 hex digest created using the shared
WEBHOOK_SECRETand the raw payload. - Header formats supported:
X-Hub-Signature-256: sha256=<hex>(common for GitHub-style signatures)X-Signature: <hex>orX-Hub-Signature: <hex>(also accepted)
- The server computes
hmac.new(secret, payload_bytes, hashlib.sha256).hexdigest()and useshmac.compare_digestto avoid timing attacks.
CSRF protection is intended to protect browser-based, cookie-bearing sessions from cross-site requests. Webhooks are server-to-server and normally not run with a browser context, so the /webhook route is explicitly exempt from CSRF checks in this demo. Do not remove signature verification in real systems.
Flask-Limiter is configured with default in-memory storage for simplicity; it prints a warning on startup. For production use, configure a persistent backend (Redis, Memcached, etc.) as described in the Flask-Limiter docs.
- Replace environment-backed plain-password demo auth with a proper user model and password hashing (bcrypt/argon2).
- Use a secure storage backend for rate limiting and for storing secrets.
- Use an
Authorization: Bearer <token>style for APIs and rotate long-lived tokens. - Add replay protection for webhooks (timestamps + allowed skew) and log delivery metadata.
- If clicking the demo button shows no output, open the browser DevTools console and Network tab to see if the fetch to
/demo/getsucceeded and what the response was. - Check the Flask server console where you ran
flask runfor logged errors such as connection timeouts or exceptions (demo_get:prints are included in the server logs for debugging).
- Add a small
scripts/send_webhook.pyscript that signs payloads usingWEBHOOK_SECRETso you can demo a valid/invalid signature. - Add unit tests for the webhook signature verification helper.
- Wire in a persistent limiter backend (Redis) and add environment-specific configs.
If you'd like, I can add the send_webhook.py demo script and a small test that demonstrates a valid signed request and a rejected request with an invalid signature.