CSC/ECE 517 Spring 2026 - E2604. Finish Password Resets
Demo Video
Click to watch the demo video -> todo
Expertiza Background
What is Expertiza?
Expertiza is an open-source educational web application built with Ruby on Rails and jointly maintained by students and faculty at North Carolina State University (NCSU). It is available publicly on GitHub.
It supports:
- Creating and configuring assignments (individual or team-based).
- Allowing students to submit work (files, links, etc.).
- Enabling peer review and teammate evaluation.
- Letting instructors define rubrics and grading criteria.
Expertiza is designed to promote learning through iterative feedback. Students review each other’s work, reflect, improve, and resubmit. Instructors can monitor progress, manage deadlines, and assess performance.
Reimplementation Repository
Front End Overview
The reimplementation-front-end project replaces Expertiza's aging Rails monolith (server-rendered ERB templates, jQuery, session cookies, no client-side state) with a decoupled React + TypeScript SPA using JWT auth, Redux, Formik/Yup forms, React Bootstrap, and Vite — separating the front end entirely from the Rails API back end.
Back End Overview
The reimplementation-back-end project transforms Expertiza from a server-rendered Rails monolith into a pure JSON REST API using stateless JWT/RSA-256 authentication, a centralized role-based authorization concern, and adds several new features including a Duties system for team role assignment, a MentoredTeam type, a TeamsParticipant join model replacing TeamsUser, an Item/Strategy pattern replacing the Question STI hierarchy, a StudentTask service object, multi-view grade reporting endpoints, a self-registration AccountRequest workflow, full invitation lifecycle management, and auto-generated Swagger API documentation.
Project Background
Motivation
Account recovery and password reset functionality is a critical part of any authentication system. The current site lacks a functional password reset or "forgot password" feature, representing a significant gap in both usability and security.
This created several issues:
- Without a working reset mechanism, there is no sanctioned path for users to change this default credential after account creation. This constitutes a vulnerability that persists indefinitely if left unaddressed.
- A user who forgets their credentials has no means of recovering access independently.
- A standards-compliant password reset mechanism relies on time-limited, single-use tokens delivered to a verified email address. This ensures that only the legitimate account owner can initiate a reset. Without such a mechanism, any ad-hoc reset approach would be either insecure or unverifiable (e.g., allowing resets without proving email ownership).
This project introduces a more structured workflow for handling password reset and account recovery operations.
The goal is to provide a consistent and secure process for users who need to reset or recover their credentials while maintaining proper validation and access control.
Typical operations in this workflow include:
- requesting a password reset
- generating a secure reset token
- validating reset requests
- updating the user's password
- preventing unauthorized reset attempts
The system should ensure that these steps are handled in a secure, predictable, and maintainable way.
Objectives
1. Implement a unified password reset workflow
The system should support a consistent flow for recovering user accounts.
Typical steps include:
- User requests password reset
- System generates a reset token
- User receives reset instructions via email
- User accesses password reset page via email link
- User submits new password
- System validates and updates credentials
This workflow should be easy to maintain and extend.
2. Improve security and validation
Password reset functionality must enforce security best practices.
This may include:
- Ensure forgot password mechanism adheres to industry standards.
- Ensure reset links are generated correctly across development, staging, and production environments.
- Validate forgot and reset password mechanism functions through unit, functional, performance, and integration testing.
The system should ensure that only authorized password reset requests are processed.
3. Improve frontend robustness and validation
The password reset workflow involves several frontend steps, including requesting a reset link and submitting a new password. The frontend should handle various edge cases gracefully.
This project includes a review and refinement of the frontend behavior to ensure consistent handling of errors and validation responses.
Key areas of focus include:
- Handling invalid or expired password reset tokens
- Properly displaying backend validation errors
- Ensuring clear success and failure messages for users
- Confirming that form validation aligns with backend rules
The goal is to ensure that the password reset workflow remains reliable and user-friendly.
Core Changes
In this project:
- The User model or authentication model was enhanced.
- A new password reset workflow was introduced.
- Controller logic was updated to handle reset requests.
- Routes were added to support password reset endpoints.
- Tests were added to verify authentication and recovery behavior.
Password Reset Workflow
Purpose
The password reset workflow allows users who have forgotten their password to regain access to their account securely.
A typical password reset flow may look like:
- User selects "Forgot Password" in the login page.
- User provides email address associated with account.
- System generates a reset token.
- System sends reset email containing link with password token in it.
- User navigates to password reset portal and creates a new password.
- System validates the request and updates credentials
This workflow must ensure both usability and security.
Implementation
The PasswordsController handles two operations in the password reset flow: initiating a reset request and completing it with a new password. Both actions bypass the standard JWT authentication (skip_before_action :authenticate_request!), as they are designed for unauthenticated users.
When a user submits their email to request a password reset.
# POST /password_resets
def create
if @user
token = @user.generate_token_for(:password_reset)
UserMailer.send_password_reset_email(@user, token).deliver_later
end
# Always return a 200 OK to prevent email enumeration attacks
render json: { message: I18n.t('password_reset.email_sent') }, status: :ok
end
A before_action for create. It looks up the user-submitted email address and does nothing if no user is found.
def find_user_by_email
@user = User.find_by(email: params[:email])
end
Assuming the email is valid, a password token is dispatched along with an HTML format email.
generates_token_for :password_reset, expires_in: 15.minutes do
password_salt&.last(10) || updated_at.to_s
end
Navigating to the URL presents the user with the following password reset page.
When a user submits a new password using the token from the email link.
# PATCH/PUT /password_resets/:token
def update
if @user.update(password_params)
render json: { message: I18n.t('password_reset.updated') }, status: :ok
else
render json: { errors: @user.errors.full_messages }, status: :unprocessable_entity
end
end
A before_action for update. It validates and decodes the reset token from the URL.
def find_user_by_token
@user = User.find_by_token_for(:password_reset, params[:token])
unless @user
render json: { error: I18n.t('password_reset.errors.token_expired') }, status: :unprocessable_entity
end
end
If the reset token is invalid or expired(find_by_token_for returns nil), the method renders a 422 Unprocessable Entity error
def password_params
params.require(:user).permit(:password, :password_confirmation)
end
How it works - Token Generation
- Tokens are generated using Rails 7.1's built-in generates_token_for API.
- When a reset is requested, PasswordsController#create calls @user.generate_token_for(:password_reset), which produces a cryptographically signed token containing:
- A purpose tag (password_reset) so the token cannot be reused for other token-based operations.
- An expiry timestamp embedded in the token (15-minute window).
- A fingerprint derived from the last 10 characters of the user's password_salt, falling back to updated_at
- When a reset is requested, PasswordsController#create calls @user.generate_token_for(:password_reset), which produces a cryptographically signed token containing:
How it works - Token Validation
- Validation occurs in PasswordsController#find_user_by_token, run as a before_action on the update endpoint:
- Rails' find_by_token_for performs several checks atomically:
- Signature verification — the token's HMAC signature must be valid (tampered tokens fail immediately).
- Purpose check — the token must have been generated for :password_reset specifically.
- Expiry check — the embedded timestamp must be within the 15-minute window.
- Fingerprint check — the password_salt fragment in the token must match the user's current state.
- If any check fails, nil is returned and the controller renders a 422 Unprocessable Entity with a generic error message ("The token has expired or is invalid.").
- This single error message intentionally does not distinguish between expired vs. tampered tokens, avoiding information leakage.
- Rails' find_by_token_for performs several checks atomically:
How it works - Secure Password Update
- Once the token is validated, the update action applies the new password.
- Strong parameters filter the request, permitting only :password and :password_confirmation
- The user has to be able to confirm the password by writing it again.
- At the storage layer:
- has_secure_password delegates hashing to BCrypt via Rails' ActiveModel::SecurePassword. The raw password is never stored.
- password_confirmation matching is enforced by has_secure_password — Rails raises a validation error if the two fields don't match.
- Minimum length validation (length: { minimum: 6 }) ensures weak passwords are rejected before reaching the database.
- Upon a successful save, password_salt changes, which immediately invalidates the reset token used.
- Strong parameters filter the request, permitting only :password and :password_confirmation
Security Considerations
Password reset systems must be designed carefully to prevent abuse.
Potential risks include:
- Unauthorized password changes
- Brute-force token guessing
- Replay attacks using old reset links
- Account enumeration
- Token leakage via URL
The system addresses these risks:
- A request cannot reach the password update logic without first passing token validation. The update action is gated by find_user_by_token as a before_action.
- Tokens produced by Rails' generates_token_for are HMAC-signed using the application's secret_key_base. They are not short numeric codes or predictable sequences — they are cryptographically random and verifiable only by the server.
- When a password is successfully updated via has_secure_password, the password_salt changes. This causes the embedded fingerprint to no longer match, immediately invalidating the used token. Old links cannot be reused even within the 15-minute window.
- The create action always returns 200 OK with the same message ("If the email exists, a reset link has been sent.") regardless of whether the submitted email matches a real account.
- The 15-minute expiry limits the window of exposure, and the token self-invalidates after use, so a leaked-but-used token is harmless. A leaked-but-unused token within 15 minutes remains a risk, but this is a standard trade-off for URL-based reset flows.
Testing and Verification
Goals of Testing
The testing strategy verifies that password reset functionality works correctly and securely.
Key goals include:
- verifying reset request behavior
- verifying token validation
- verifying password updates
- verifying proper error handling
Model Tests
Model tests may verify:
- TODO: token generation
- TODO: token expiration behavior
- TODO: password validation rules
Controller Tests
Controller tests verify:
- TODO: reset request creation
- TODO: reset token validation
- TODO: successful password update
- TODO: rejection of invalid requests
Security Tests TODO
Security tests ensure that:
- invalid tokens are rejected
- expired tokens cannot be used
- password updates require valid authentication
Collaborators
- Mentor: Prerak Manish Bhandari
- Jose Vargas
- John Weisz
- Jared Monseur
