class LoginCode < ApplicationRecord
belongs_to :user
CODE_LENGTH = 6
TTL = 10.minutes
attr_reader :code
scope :active, -> { where(consumed_at: nil).where("expires_at > ?", Time.current) }
scope :expired, -> { where("expires_at <= ?", Time.current) }
def self.generate_for(user)
transaction do
user.login_codes.active.update_all(consumed_at: Time.current)
plaintext = format("%0#{CODE_LENGTH}d", SecureRandom.random_number(10**CODE_LENGTH))
record = create!(
user: user,
digest: BCrypt::Password.create(plaintext),
expires_at: TTL.from_now
)
record.instance_variable_set(:@code, plaintext)
record
end
end
def verify?(submitted)
return false if expired? || consumed_at.present?
BCrypt::Password.new(digest) == submitted.to_s
end
def consume!
update!(consumed_at: Time.current)
end
def expired?
expires_at <= Time.current
end
end
class LoginCodeMailer < ApplicationMailer
default from: "security@example.com"
def otp_email(login_code)
@user = login_code.user
@code = login_code.code
@expires_in_minutes = LoginCode::TTL.in_minutes.to_i
raise ArgumentError, "plaintext code unavailable" if @code.blank?
mail(
to: @user.email,
subject: "Your one-time sign-in code"
)
end
end
class SessionsController < ApplicationController
def create
user = User.find_by(email: params[:email].to_s.downcase.strip)
if user
login_code = LoginCode.generate_for(user)
LoginCodeMailer.otp_email(login_code).deliver_later
end
# Neutral response regardless of whether the email exists.
render json: { message: "If that account exists, a code is on its way." }, status: :accepted
end
def verify
user = User.find_by(email: params[:email].to_s.downcase.strip)
login_code = find_active_code(user)
if login_code&.verify?(params[:code])
login_code.consume!
reset_session
session[:user_id] = user.id
render json: { message: "Signed in." }, status: :ok
else
render json: { error: "Invalid or expired code." }, status: :unauthorized
end
end
private
def find_active_code(user)
return nil unless user
user.login_codes.active.order(created_at: :desc).first
end
end
This snippet shows a passwordless / two-factor login step in Rails built around a single-use, time-limited one-time password (OTP). The core idea is that the plaintext code is never stored: only a hashed digest lives in the database, the code is emailed to the user, and verification re-hashes the submitted value and compares it in constant time before marking the token consumed.
In LoginCode model, generate_for creates a fresh record inside a transaction that first active-scopes out any prior unused codes for that user, preventing several valid codes from floating around at once. A six-digit code is produced with SecureRandom.random_number, hashed with BCrypt::Password.create, and only the digest is persisted alongside an expires_at timestamp. The plaintext is returned via an attr_reader so the caller can mail it, but it is never written to the row. verify? checks expired? and consumed_at before comparing digests, and consume! stamps consumed_at so a code cannot be replayed. The scopes active and expired keep those state checks declarative and reusable.
LoginCodeMailer is a conventional ActionMailer::Base subclass; otp_email pulls the freshly generated plaintext off the model and exposes it to the view template, so the digest-only storage rule is never violated at the delivery layer.
SessionsController wires it together. create looks up the user by email, calls LoginCode.generate_for, and hands the returned object — carrying its transient code — to the mailer via deliver_later, keeping the request fast. It always renders the same neutral response to avoid leaking which addresses exist. verify reloads the newest active code, calls verify?, and on success runs consume! and establishes the session; failure paths cover expired, already-used, and mistyped codes uniformly.
The trade-offs are worth noting: bcrypt makes brute-forcing a leaked digest expensive, but for short numeric codes the real defense is a short expiry plus rate limiting on verify, which a production system would add around find_active_code. Constant-time comparison via BCrypt::Password#== guards against timing attacks. This pattern suits email-based 2FA or magic-code login where a durable, auditable, replay-resistant token is needed.
Related snips
class CommentsController < ApplicationController
before_action :set_post
def create
@comment = @post.comments.build(comment_params)
System test: asserting Turbo Stream responses
class Post < ApplicationRecord
belongs_to :author, class_name: 'User'
has_many :comments, dependent: :destroy
scope :published, -> { where.not(published_at: nil).where('published_at <= ?', Time.current) }
scope :draft, -> { where(published_at: nil) }
ActiveRecord scopes for reusable query logic
payload = {
sub: user.id,
iss: 'https://auth.example.com',
aud: 'codesnips-api',
exp: 15.minutes.from_now.to_i,
iat: Time.now.to_i,
JWT issuance and verification without common footguns
module Api
module V1
class UsersController < BaseController
def show
user = User.includes(:profile).find(params[:id])
ETags for conditional requests and caching
class PostsController < ApplicationController
def index
@posts = Post.includes(:author)
.order(created_at: :desc)
.page(params[:page])
.per(10)
Turbo Frames: infinite scroll with lazy-loading frame
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
Share this code
Here's the card — post it anywhere.