A technical walkthrough of the Secure-2FA architecture — built on Ruby on Rails 7, PostgreSQL, Redis, and Sidekiq.
The architecture is broken into modular components. The backend is a Ruby on Rails API that exposes two OTP endpoints and a management interface for developers.
POST /otp/generate receives the encrypted data and delivers the OTP code; POST /otp/verify receives and validates the user's submission. Both are detailed in the Using the Service section.
Prerequisites: Ruby 3.1+, Rails 7, Node.js v18+, Yarn, PostgreSQL, and Foreman. Install Rails 7 if needed:
# Install Rails 7 $ gem install rails -v 7 # Create the application $ rails new secure-2fa -j esbuild --css bootstrap -d postgresql # Start the app $ cd secure-2fa $ bin/rails db:prepare $ bin/dev
The Rails server starts at http://localhost:3000. The bin/dev command uses Foreman to start Rails, asset compilers, and Sidekiq together.
Add the required gems to Gemfile and run bundle install:
# Authentication gem 'devise' gem 'devise-two-factor' gem 'rqrcode' # Background jobs gem 'sidekiq' gem 'redis' # OTP delivery providers gem 'twilio-ruby' gem 'mailjet' gem 'plivo' gem 'sendgrid-ruby'
Configure Sidekiq with Redis, set up database encryption keys, and initialize Devise:
# Configure Devise with async email via Sidekiq $ bin/rails generate devise:install # Generate encryption keys $ bin/rails credentials:edit active_record_encryption: primary_key: ... deterministic_key: ... key_derivation_salt: ...
The Client model handles developer authentication. Registration is email-only; after confirming the email, the client sets a password and links an authenticator app (TOTP). Backup codes are provided if the authenticator is unavailable.
# Generate Devise for Client model $ bin/rails generate devise Client # Generate custom controllers $ bin/rails generate devise:controllers clients -c=registrations confirmations sessions # Custom OTP controllers $ bin/rails generate controller clients/two_factor_auths $ bin/rails generate controller clients/passwords
The session controller redirects clients to a TOTP verification page after successful password login. Backup codes allow access when the authenticator app is unavailable.
Rails scaffold generates controllers, views, and routes for the three main backend resources:
$ bin/rails generate scaffold Project client:reference title:string public_key:string public_key_iv:string secret_key:string secret_key_iv:string channel:reference template:reference expiration_seconds:integer max_attempts:integer webhook_url:string webhook_url_iv:string $ bin/rails generate scaffold Channel client:reference title:string method:string provider:string credentials:text $ bin/rails generate scaffold Template client:reference title:string markup:string # Model-only (no controller/views needed) $ bin/rails generate model User project:reference uuid:string uuid_iv:string $ bin/rails generate model Verification user:reference otp_code:string otp_code_iv:string otp_sent_at:datetime attempts:integer to:string to_iv:string lang:string template_request:boolean delivery_identifier:string delivery_status:string $ bin/rails generate model TemplateVersion template:reference subject:string body:text lang:string $ bin/rails generate model Log client:reference project:reference user:reference message:text level:string $ bin/rails db:migrate
The OTP controller handles the two API endpoints. It decrypts the incoming data using AES-256, manages OTP generation, delivery, verification, and logging:
$ bin/rails generate controller otp generate verify # Key controller logic: # generate — decrypts data, finds/creates user, # generates OTP, triggers delivery via Sidekiq # verify — decrypts data, validates OTP code, # checks expiry, max attempts, device match # fires webhook if configured
The OTP delivery is handled asynchronously via a Sidekiq background worker, so the API response returns immediately while the email or SMS is sent in the background:
# app/jobs/otp_delivery_job.rb class OtpDeliveryJob < ApplicationJob queue_as :default def perform(verification_id) verification = Verification.find(verification_id) service = verification.channel.provider # Routes to the correct service class # e.g. PostmarkService, TwilioService, SmtpService "#{service.camelize}Service" .constantize .new(verification) .send_otp end end
The application supports multiple email and SMS delivery providers. Each is a service class in app/services/ that follows the same interface:
The first registered Client is automatically set as admin. Admin accounts have access to the full Clients list and the Sidekiq dashboard for monitoring background jobs.
The Dashboard (root path) displays application logs. Logs are also shown per-project and per-user page. All log levels: info, warning, danger, success.
secure-2fa/
├── app/
│ ├── controllers/
│ │ ├── application_controller.rb
│ │ ├── otp_controller.rb
│ │ ├── projects_controller.rb
│ │ ├── channels_controller.rb
│ │ ├── templates_controller.rb
│ │ └── clients/
│ │ ├── registrations_controller.rb
│ │ ├── confirmations_controller.rb
│ │ ├── sessions_controller.rb
│ │ ├── passwords_controller.rb
│ │ └── two_factor_auths_controller.rb
│ ├── jobs/
│ │ ├── otp_delivery_job.rb
│ │ └── webhook_call_job.rb
│ ├── models/
│ │ ├── client.rb
│ │ ├── project.rb
│ │ ├── channel.rb
│ │ ├── template.rb
│ │ ├── template_version.rb
│ │ ├── user.rb
│ │ ├── verification.rb
│ │ ├── log.rb
│ │ └── concerns/
│ │ └── check_linked_projects.rb
│ ├── services/
│ │ ├── postmark_service.rb
│ │ ├── sendgrid_service.rb
│ │ ├── mailjet_service.rb
│ │ ├── twilio_service.rb
│ │ ├── plivo_service.rb
│ │ ├── clicksend_service.rb
│ │ └── smtp_service.rb
│ └── views/
│ ├── projects/
│ ├── channels/
│ ├── templates/
│ └── clients/
├── config/
│ ├── routes.rb
│ ├── initializers/
│ │ ├── devise.rb
│ │ └── sidekiq.rb
│ └── sidekiq.yml
├── db/
│ ├── migrate/
│ └── schema.rb
├── Gemfile
├── Procfile
└── Dockerfile
Views, CSS, JavaScript and Docker configuration are in the open-source repository.
View RepositorySee the API documentation and integration examples in all major programming languages.
Using the Service →