Building BandTools: Sudo Mode

10 June 2026

I’ve been building BandTools, a newsletter platform for musicians. As the application has grown, so has the surface area of sensitive actions a signed-in user can perform: changing account settings, enabling two-factor authentication, and viewing recovery codes. Signing in once and staying signed in is convenient, but it creates a problem if someone else gains physical access to an unlocked browser session.

Sudo mode addresses this. The name comes from the Unix sudo command, which lets you run a single privileged command after re-entering your password, without starting a new login session. Web applications use the same idea under various names; GitHub calls it “sudo mode”, and it’s the pattern I adopted for BandTools.

The goal is straightforward: even if you’re already signed in, certain actions require you to prove your identity again. That protects critical changes from being made via an unattended session at a shared computer, or from a session left open on a laptop that’s briefly out of sight.

BandTools tracks sudo mode with a session timestamp. When a user successfully re-authenticates, the current time is stored in session[:sudo_mode_authenticated_at]. On subsequent requests, the application checks whether that timestamp falls within a configurable timeout window. The default is thirty minutes, controlled by the sudo_mode_timeout_minutes system setting.

There’s a sensible exemption: if the user’s main login session was created within the timeout period, sudo mode is implicitly active. There’s no point asking someone to re-enter their password immediately after they’ve just signed in.

When a protected action is requested and sudo mode isn’t active, the user is redirected to a dedicated re-authentication screen. After successful verification they’re sent back to whatever they were trying to do.

The core logic lives in a Rails concern at app/controllers/concerns/sudo_mode.rb. Any controller that needs protection mixes in the SudoMode module and declares which actions require sudo mode using a class method:

module SudoMode
  extend ActiveSupport::Concern

  included do
    before_action :require_sudo_mode!, if: -> { self.class.sudo_protected_actions.include?(action_name.to_sym) }
  end

  class_methods do
    def require_sudo_for(*actions)
      @sudo_protected_actions = actions
    end

    def sudo_protected_actions
      @sudo_protected_actions || []
    end
  end

  private

  def require_sudo_mode!
    return if sudo_mode_active?

    to = request.fullpath
    redirect_to new_sudo_session_path(to:)
  end

  def sudo_mode_active?
    return false unless Current.user

    timeout = SystemSetting.sudo_mode_timeout_minutes.minutes

    Time.at(session[:sudo_mode_authenticated_at] || 0) > timeout.ago ||
      (Current.session&.created_at && Current.session.created_at > timeout.ago)
  end

  def activate_sudo_mode!
    session[:sudo_mode_authenticated_at] = Time.current.to_i
  end
end

The require_sudo_for class method stores the list of protected action names. A before_action filter runs require_sudo_mode! only when the current action is in that list. If sudo mode isn’t active, the filter stashes the requested URL and redirects to the sudo session screen.

The sudo_mode_active? method implements the two conditions described above: either the sudo timestamp is recent enough, or the login session itself is recent enough. Storing the timestamp as an integer epoch value keeps the session payload small and avoids serialisation surprises.

Protecting a controller action is then a one-liner. For example, the two-factor authentication setup controller declares:

class TwoFactorAuthController < ApplicationController
  include SudoMode

  require_sudo_for :new
end

Similar declarations protect account settings, recovery codes, API token management, and custom domains.

The SudoSessionsController handles verification. For users who signed in with a password, the flow is familiar: enter your password again, and on success the sudo timestamp is set and you’re redirected back.

def create
  username = Current.user.username
  password = params[:password]

  if User.authenticate_by(username:, password:).present?
    session[:sudo_mode_authenticated_at] = Time.current.to_i
    redirect_to safe_redirect_path
  else
    flash.now[:alert] = "The password you entered is incorrect. Please try again."
    @redirect_path = safe_redirect_path
    render :new, status: :unprocessable_content
  end
end

For users who signed in via Google, prompting for a password they may not have set would be confusing. Instead, the sudo screen shows which Google account they’re signed in as and offers a “Continue with Google” button that triggers a fresh OAuth flow.

The application tracks how the user originally signed in via session[:signed_in_via_google], set during the sign-in process (including after completing two-factor authentication). When the sudo screen detects this flag and the user has a linked Google account, it stores the redirect path in session[:sudo_google_redirect] and renders the Google re-auth view.

After OAuth completes, the callback controller checks whether this is a sudo re-authentication rather than a normal sign-in:

def handle_sudo_google_auth(auth)
  redirect_path = session.delete(:sudo_google_redirect)
  linked_account = Current.user.linked_accounts.find_by(provider: auth.provider, uid: auth.uid)

  if linked_account
    session[:sudo_mode_authenticated_at] = Time.current.to_i
    redirect_to url_from(redirect_path) || root_path
  else
    redirect_to new_sudo_session_path(to: redirect_path),
      alert: "The Google account you authenticated with does not match your linked account."
  end
end

The OAuth UID must match the user’s linked Google account. A mismatch sends the user back to the sudo screen with an error, which prevents someone from authenticating with a different Google account to gain elevated access. If a user signed in via Google but has since unlinked their Google account, the sudo screen falls back to the password prompt.

Sudo mode is enforced across the parts of BandTools where the consequences of unauthorised access are highest:

  • Account management: editing account settings and confirming account deletion
  • Two-factor authentication: setting up 2FA and viewing recovery codes
  • API tokens and custom domains: creating, editing, and revoking credentials

The pattern is consistent: include SudoMode, call require_sudo_for with the action names, and the filter handles the rest. Views can also check sudo_mode_active? if they need to reflect the current state, although in practice the controller filter is sufficient for enforcement.

Sudo mode isn’t a substitute for proper session management, strong passwords, or two-factor authentication. It’s an additional layer that closes a specific gap: the gap between “someone is signed in” and “someone at this keyboard right now is the person who signed in”. For a SaaS application handling musician mailing lists and payment details, that’s a gap worth closing.