CSRF (Cross-Site Request Forgery) protection is a fundamental security measure for any web application that handles stateful requests and user authentication. In this article, we will see how to implement it from scratch using Python and a simple HTTP server.
1. What is a CSRF Token
A CSRF token is a unique and unpredictable value generated by the server and associated with the user's session. This token is included in HTML forms and sent with each request that modifies server state. If the received token does not match the one saved in the session, the request is rejected.
2. Server Setup
We’ll create a simple HTTP server using http.server
and store CSRF tokens in the user session via cookies.
import http.server
import http.cookies
import secrets
import urllib.parse
csrf_tokens = {}
class SimpleHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
session_id = self.get_or_create_session()
token = self.generate_csrf_token(session_id)
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(self.render_form(token).encode())
def do_POST(self):
session_id = self.get_or_create_session()
content_length = int(self.headers.get('Content-Length', 0))
body = self.rfile.read(content_length).decode()
post_data = urllib.parse.parse_qs(body)
token = post_data.get("csrf_token", [""])[0]
if not self.validate_csrf_token(session_id, token):
self.send_response(403)
self.end_headers()
self.wfile.write(b"Forbidden: Invalid CSRF token")
return
self.send_response(200)
self.end_headers()
self.wfile.write(b"Form submitted successfully!")
def get_or_create_session(self):
cookie = http.cookies.SimpleCookie(self.headers.get("Cookie"))
if "session_id" in cookie:
return cookie["session_id"].value
session_id = secrets.token_hex(16)
self.send_header("Set-Cookie", f"session_id={session_id}")
return session_id
def generate_csrf_token(self, session_id):
token = secrets.token_hex(32)
csrf_tokens[session_id] = token
return token
def validate_csrf_token(self, session_id, token):
return csrf_tokens.get(session_id) == token
def render_form(self, token):
return f"""
<!DOCTYPE html>
<html>
<body>
<form method="POST">
<input type="hidden" name="csrf_token" value="{token}">
<input type="text" name="data">
<input type="submit" value="Submit">
</form>
</body>
</html>
"""
if __name__ == "__main__":
http.server.HTTPServer(("localhost", 8080), SimpleHandler).serve_forever()
3. How It Works
- On the first request, the server generates a session_id and a csrf_token, saving them respectively in a cookie and in memory.
- The token is included as a hidden field in the HTML form.
- When the user submits the form, the server compares the received token with the one saved for that session.
- If the token is valid, the action is accepted; otherwise, an error is returned.
4. Security Considerations
- This implementation is educational and not scalable for production environments.
- It is advisable to use mature libraries to manage sessions and tokens.
- Protect cookies with the
HttpOnly
attribute and, if possible,Secure
.
Conclusion
We have seen how to implement CSRF protection from scratch in Python using simple techniques. Although more sophisticated solutions exist, understanding how CSRF protection works helps write more secure and mindful code.