基于Clearance gem构建「更新邮箱」功能是否有规范实现方案?
Great question! While Clearance doesn’t include a pre-built email update feature out of the box, there’s a secure, idiomatic way to implement it that fits right into Clearance’s lightweight, extensible design—no need to reinvent the wheel. Here’s a step-by-step breakdown of the standard pattern:
1. Add Routes
First, define routes for editing the user’s profile, submitting the email update request, and handling email confirmation:
# config/routes.rb Rails.application.routes.draw do # Keep your existing Clearance routes resources :users, only: [:edit] do patch :update_email, on: :member get :confirm_email, on: :member, param: :token end end
2. Update the User Model
Add fields to track email confirmation status. Start with a migration:
rails generate migration AddEmailConfirmationFieldsToUsers email_confirmed:boolean email_confirmation_token:string rails db:migrate
Then update the model to set defaults and add validations:
# app/models/user.rb class User < ApplicationRecord include Clearance::User validates :email, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP } after_initialize :set_default_email_confirmed private def set_default_email_confirmed self.email_confirmed ||= false end end
3. Build the Controller Logic
Create or extend a UsersController to handle the email update flow, including critical security checks:
# app/controllers/users_controller.rb class UsersController < ApplicationController before_action :require_login def edit @user = current_user end def update_email @user = current_user # Verify current password to prevent session hijacking if @user.authenticate(params[:current_password]) new_email = params[:user][:email].strip.downcase if @user.email != new_email && @user.update( email: new_email, email_confirmed: false, email_confirmation_token: SecureRandom.urlsafe_base64 ) # Send confirmation to the new email UserMailer.email_confirmation(@user).deliver_now redirect_to edit_user_path, notice: "A confirmation link has been sent to your new email. Please verify it to complete the update." else flash[:alert] = "Failed to update email. Check that the address is valid and not already in use." render :edit end else flash[:alert] = "Current password is incorrect." render :edit end end def confirm_email @user = User.find_by(id: params[:id], email_confirmation_token: params[:token]) if @user && !@user.email_confirmed @user.update!(email_confirmed: true, email_confirmation_token: nil) # Optional: Sign out to force fresh authentication with new email sign_out redirect_to sign_in_path, notice: "Your email has been updated! Please sign in with your new address." else redirect_to root_path, alert: "Invalid or expired confirmation link." end end end
4. Create the Edit Profile View
Build a form for users to enter their current password and new email:
<!-- app/views/users/edit.html.erb --> <h2>Update Your Account</h2> <div class="card"> <div class="card-body"> <% if flash[:alert] %> <div class="alert alert-danger mb-3"><%= flash[:alert] %></div> <% end %> <% if flash[:notice] %> <div class="alert alert-success mb-3"><%= flash[:notice] %></div> <% end %> <%= form_with(model: @user, url: update_email_user_path(@user), method: :patch) do |form| %> <div class="mb-3"> <%= form.label :current_password, "Current Password (required to confirm changes)", class: "form-label" %> <%= form.password_field :current_password, class: "form-control", required: true %> </div> <div class="mb-3"> <%= form.label :email, "New Email Address", class: "form-label" %> <%= form.email_field :email, class: "form-control", value: @user.email, required: true %> </div> <%= form.submit "Update Email", class: "btn btn-primary" %> <% end %> </div> </div>
5. Set Up the Confirmation Email
Create a mailer to send the confirmation link:
# app/mailers/user_mailer.rb class UserMailer < ApplicationMailer default from: "your-app@example.com" def email_confirmation(user) @user = user @confirmation_url = confirm_email_user_url(user, token: user.email_confirmation_token) mail(to: user.email, subject: "Confirm Your New Email Address") end end
And the corresponding email template:
<!-- app/views/user_mailer/email_confirmation.html.erb --> <h1>Confirm Your New Email</h1> <p>Hi there,</p> <p>Click the link below to confirm your new email address for your account:</p> <p><%= link_to "Confirm Email Address", @confirmation_url %></p> <p>If you didn't request this change, please ignore this email—your account will remain unchanged.</p>
Key Security & Usability Notes
- Mandatory Password Check: Verifying the current password blocks attackers who’ve hijacked a user’s session from changing their email.
- Email Confirmation: Requiring confirmation prevents lockouts from typos and blocks malicious email changes.
- Session Invalidation: Optionally signing the user out after confirmation forces fresh authentication with the new email, adding extra security.
- Email Normalization: Stripping whitespace and downcasing emails avoids duplicate accounts from case variations.
This implementation aligns with Clearance’s philosophy—keeping core auth logic minimal while letting you extend functionality safely without overriding internals.
内容的提问来源于stack exchange,提问作者hoffm




