Implementing CSRF Protection from Scratch in Python

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.

Back to top