Implement Cross-Site Request Forgery (CSRF) Protection in Go
Web applications are vulnerable to Cross-Site Request Forgery (CSRF) attacks where an attacker tricks a victim into performing an unwanted action on a web application in which they are currently authenticated. This challenge requires you to implement a robust CSRF protection mechanism in a Go web application to mitigate these attacks.
Problem Description
Your task is to build a simple Go web application that demonstrates and implements CSRF protection for forms that modify data. The application should have at least two routes:
- A GET route to display a form for submitting data (e.g., creating a new user, updating a profile). This form should include a hidden CSRF token.
- A POST route that handles the form submission. This route must validate the CSRF token before proceeding with the data processing.
You need to use a secure method to generate and validate CSRF tokens. The tokens should be unique per session or request, and invalidated after use (or regenerated for subsequent requests).
Key Requirements:
- Token Generation: Implement a mechanism to generate unique and unpredictable CSRF tokens.
- Token Inclusion: The generated token must be embedded within the HTML form, typically as a hidden input field.
- Token Validation: The POST handler must extract the submitted token and compare it against the token stored on the server (e.g., in the user's session).
- Error Handling: If the token is missing or invalid, the application should return an appropriate error response (e.g., HTTP 403 Forbidden).
- Security: Tokens should be difficult to guess or tamper with. Consider using a secure random number generator.
- Session Management: You will need a basic session management mechanism to store the CSRF token on the server-side, associated with the user's session. For simplicity in this challenge, you can simulate sessions using a global map or a simple in-memory store, but acknowledge that a real application would use more robust session storage.
Expected Behavior:
- When a user requests the form page (GET), a unique CSRF token is generated and embedded in the form.
- When a user submits the form with a valid token, the action is processed successfully.
- When a user submits the form with an invalid, missing, or outdated token, the action is rejected with an error.
Edge Cases to Consider:
- A user submitting the form without JavaScript enabled (CSRF token will be in the HTML form).
- A user submitting the form with JavaScript (CSRF token can be sent via AJAX, but for this challenge, focus on form submission).
- Handling multiple concurrent requests from the same user.
- Token expiration (though for this challenge, you can focus on validation against the current session token).
Examples
Example 1: Successful Form Submission
Request 1 (GET /form):
Method: GET
Path: /form
Server Response 1:
Status: 200 OK
Content-Type: text/html
<!DOCTYPE html>
<html>
<head>
<title>Submit Data</title>
</head>
<body>
<form action="/submit" method="post">
<input type="hidden" name="_csrf_token" value="generated_secure_token_abc123">
<label for="data">Data:</label>
<input type="text" id="data" name="data"><br><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
Explanation: The server generates a unique CSRF token (generated_secure_token_abc123), stores it in the user's session, and embeds it as a hidden field in the HTML form.
Request 2 (POST /submit):
Method: POST
Path: /submit
Content-Type: application/x-www-form-urlencoded
_csrf_token=generated_secure_token_abc123&data=some_value
Server Response 2:
Status: 200 OK
Content-Type: text/plain
Data submitted successfully!
Explanation: The server retrieves the CSRF token from the session, compares it with the submitted token (generated_secure_token_abc123). Since they match, the submission is processed.
Example 2: Failed Form Submission (Invalid Token)
Request 1 (GET /form):
Method: GET
Path: /form
Server Response 1:
Status: 200 OK
Content-Type: text/html
<!DOCTYPE html>
<html>
<head>
<title>Submit Data</title>
</head>
<body>
<form action="/submit" method="post">
<input type="hidden" name="_csrf_token" value="generated_secure_token_xyz789">
<label for="data">Data:</label>
<input type="text" id="data" name="data"><br><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
Explanation: A CSRF token (generated_secure_token_xyz789) is generated and embedded.
Request 2 (POST /submit):
Method: POST
Path: /submit
Content-Type: application/x-www-form-urlencoded
_csrf_token=tampered_or_old_token_456&data=some_value
Server Response 2:
Status: 403 Forbidden
Content-Type: text/plain
CSRF token validation failed.
Explanation: The server compares the submitted token (tampered_or_old_token_456) with the one stored in the session (generated_secure_token_xyz789). They do not match, so the request is rejected.
Example 3: Failed Form Submission (Missing Token)
Request 1 (GET /form):
Method: GET
Path: /form
Server Response 1:
Status: 200 OK
Content-Type: text/html
<!DOCTYPE html>
<html>
<head>
<title>Submit Data</title>
</head>
<body>
<form action="/submit" method="post">
<input type="hidden" name="_csrf_token" value="generated_secure_token_pqr111">
<label for="data">Data:</label>
<input type="text" id="data" name="data"><br><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
Explanation: A CSRF token (generated_secure_token_pqr111) is generated and embedded.
Request 2 (POST /submit):
Method: POST
Path: /submit
Content-Type: application/x-www-form-urlencoded
data=some_value
Server Response 2:
Status: 403 Forbidden
Content-Type: text/plain
CSRF token validation failed.
Explanation: The server expects a CSRF token in the form data, but it is missing. The request is rejected.
Constraints
- The Go application must be runnable using standard Go tooling.
- You are expected to use the standard
net/httppackage for handling HTTP requests and responses. - For token generation, use
crypto/randfor cryptographically secure randomness. - Session management can be simplified using an in-memory store (e.g.,
map[string]string) for this challenge. Avoid using external session libraries if possible to focus on the CSRF logic itself. - The CSRF token should be approximately 32 bytes in length (or an equivalent secure random string).
Notes
- Consider using a library like
github.com/gorilla/csrffor inspiration on how to structure your CSRF logic, but implement the core token generation and validation yourself for this challenge. - Think about how the CSRF token should be associated with a user's session. For a simple challenge, a cookie could be used to identify the session.
- The server-side storage of the CSRF token is crucial. It should be stored securely and be accessible for validation during POST requests.
- Ensure your token generation is truly random and not predictable.
- When regenerating tokens, consider the trade-offs between security and user experience. For this challenge, regenerating a token for each new form display is sufficient.