Middleware is the entry point for every request. Putting auth here — not in per-route decorators — means you cannot forget it on a new route. New endpoints inherit the gate by default; making them public requires an explicit opt-in.
The check order matters
Public-first (cheap, no DB hits), then the killswitch (PIN disabled), then bypass (cheap), then blacklist (one DB hit, returns immediately on hit), then session check (slowest), then login redirect. A 403 for a known-bad IP costs less than a session lookup.
Getting the real client IP
Behind a reverse proxy, request.client.host shows the proxy's IP, not the actual client. You need the X-Forwarded-For header — but only trust it from a known proxy.