auth/securing-auth-routes/SKILL.md
Securing authentication routes with CSRF protection, rate limiting, HTTPS enforcement, and brute force mitigation.
npx skillsauth add 7a336e6e/skills securing-auth-routesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
4 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Harden all authentication-related endpoints against common attacks by implementing CSRF protection, rate limiting, HTTPS enforcement, security headers, and brute force mitigation.
For browser-based forms that are not purely API-driven, use SameSite cookies combined with a CSRF token.
import secrets
from flask import session, request, abort
def generate_csrf_token() -> str:
"""Generate a CSRF token and store it in the session."""
if "csrf_token" not in session:
session["csrf_token"] = secrets.token_urlsafe(32)
return session["csrf_token"]
def validate_csrf_token():
"""Validate the CSRF token from the request against the session."""
token = request.form.get("csrf_token") or request.headers.get("X-CSRF-Token")
if not token or token != session.get("csrf_token"):
abort(403, description="CSRF validation failed")
For API endpoints that use Authorization: Bearer headers (not cookies), CSRF protection is typically unnecessary because the token must be explicitly attached by the client. SameSite=Strict cookies provide an additional layer of protection for cookie-based auth.
Apply strict rate limits to all authentication endpoints to prevent brute force and credential stuffing attacks.
from functools import wraps
from datetime import datetime, timedelta, timezone
# In-memory store for demonstration; use Redis in production
rate_limit_store: dict[str, list[datetime]] = {}
def rate_limit(max_requests: int, window: timedelta):
"""Decorator to rate limit an endpoint by client IP."""
def decorator(f):
@wraps(f)
def wrapped(*args, **kwargs):
ip = request.remote_addr
key = f"{f.__name__}:{ip}"
now = datetime.now(timezone.utc)
# Clean old entries
entries = rate_limit_store.get(key, [])
entries = [t for t in entries if now - t < window]
if len(entries) >= max_requests:
return jsonify({"error": "Too many requests. Try again later."}), 429
entries.append(now)
rate_limit_store[key] = entries
return f(*args, **kwargs)
return wrapped
return decorator
Recommended limits:
| Endpoint | Limit | |----------------|------------------------------| | Login | 5 attempts per minute per IP | | Registration | 3 attempts per hour per IP | | Password reset | 3 attempts per hour per IP | | Token refresh | 10 per minute per IP |
@auth_bp.route("/login", methods=["POST"])
@rate_limit(max_requests=5, window=timedelta(minutes=1))
def login():
# ... login logic
pass
@auth_bp.route("/register", methods=["POST"])
@rate_limit(max_requests=3, window=timedelta(hours=1))
def register():
# ... registration logic
pass
Redirect all HTTP requests to HTTPS and set the HSTS header to instruct browsers to always use HTTPS.
from flask import redirect, request
@app.before_request
def enforce_https():
if not request.is_secure and not app.debug:
url = request.url.replace("http://", "https://", 1)
return redirect(url, code=301)
@app.after_request
def set_security_headers(response):
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
return response
Apply these headers to every response:
| Header | Value | Purpose |
|------------------------------|----------------------------------------|-----------------------------------------|
| Strict-Transport-Security | max-age=31536000; includeSubDomains | Enforce HTTPS for 1 year |
| X-Content-Type-Options | nosniff | Prevent MIME type sniffing |
| X-Frame-Options | DENY | Prevent clickjacking |
| Referrer-Policy | strict-origin-when-cross-origin | Limit referrer information leakage |
Combine rate limiting per IP with per-account lockout and exponential backoff.
import math
BASE_DELAY_SECONDS = 1
MAX_DELAY_SECONDS = 900 # 15 minutes
def get_lockout_delay(failed_attempts: int) -> int:
"""Calculate exponential backoff delay based on failed attempts."""
if failed_attempts < 3:
return 0
delay = min(BASE_DELAY_SECONDS * (2 ** (failed_attempts - 3)), MAX_DELAY_SECONDS)
return int(delay)
def check_brute_force(user) -> bool:
"""Return True if the user is currently locked out."""
if user.failed_login_attempts < 3:
return False
delay = get_lockout_delay(user.failed_login_attempts)
if user.last_failed_login:
unlock_time = user.last_failed_login + timedelta(seconds=delay)
if datetime.now(timezone.utc) < unlock_time:
return True
return False
Log all failed authentication attempts for security monitoring and incident response.
import logging
security_logger = logging.getLogger("security")
def log_failed_login(email: str, ip: str, reason: str):
security_logger.warning(
"Failed login attempt",
extra={
"email": email,
"ip": ip,
"reason": reason,
"timestamp": datetime.now(timezone.utc).isoformat(),
},
)
Rate limit exceeded (429):
{ "error": "Too many requests. Try again later." }
CSRF failure (403):
{ "error": "CSRF validation failed" }
Successful request with security headers:
HTTP/1.1 200 OK
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
development
Implement features using the Red-Green-Refactor cycle to ensure testability and correctness from the start.
data-ai
Manage the `tasks.md` ledger with strict locking and collision avoidance protocols to allow multiple agents to work in parallel safely.
development
The git-workflow skill defines branching conventions, commit message formats, and pull request standards that all agents must follow for consistent version control.
development
The environment-config skill standardizes how agents manage environment variables, secrets, and application configuration across local development and deployed environments.