CSRF Protection for Registration Forms
Why We Added This
Without CSRF protection, malicious websites could submit registration forms on behalf of users without their knowledge. An attacker could create a webpage that automatically creates accounts when someone visits it.
Related: REVCON-959, PR #1528
The Problem
CSRF attacks let malicious sites perform actions as if the user did them. For registration, this means:
- Creating unwanted accounts
- Flooding our system with fake registrations
- Using up server resources
Example attack: A user visits evil-site.com which contains a hidden form that posts to our registration endpoint. The user never intended to create an account, but one gets created anyway.
Our Solution
We use CSRF tokens - unique codes that prove the form was loaded from our website:
- When loading the registration page: Generate a random token and store it in DynamoDB
- In the form: Include the token as a hidden field
- When submitting: Verify the token exists and delete it (one-time use)
- If invalid: Reject the request
How It Works
Files involved:
app/services/csrf.server.ts- Creates and validates tokensapp/routes/registration/$product/registration-form-schema.server.ts- Validates tokens in form submissionapp/routes/registration/$product/_index.tsx- Generates tokens for each page load
Process:
- User visits registration page → we generate a UUID token → store in DynamoDB (expires in 15 minutes)
- Form includes hidden
<input name="csrfToken" value="..." /> - User submits form → we check if token exists in DynamoDB → delete it → proceed with registration
- Malicious sites can't get valid tokens because they can't load our page
Technical Details
Storage: DynamoDB table (configured via APP_CSRF_TOKEN_TABLE environment variable)
Token lifetime: 15 minutes
Token format: UUID v4
Validation: Server-side only, integrated with Zod schema validation
Trade-offs
Benefits:
- Stops cross-site form submissions
- Works without JavaScript
- Scales with DynamoDB
Costs:
- Extra DynamoDB read/write per form submission
- Users need new tokens if they refresh the page
- Slightly more complex local development setup
This simple approach effectively prevents CSRF attacks while keeping the user experience unchanged.