A modern Ruby on Rails application featuring two-factor authentication (2FA) with flexible login options using either email or mobile number.
- Flexible Authentication: User authentication with Devise
- Multi-Login Options: Login with Email or Mobile Number (10-digit)
- Two-Factor Authentication (2FA):
- TOTP-based authentication using authenticator apps (Google Authenticator, Authy, Microsoft Authenticator)
- QR code setup for easy configuration
- Backup codes for account recovery
- Manual key entry option
- Secure OTP verification during login
- Modern UI: Responsive design with Tailwind CSS
- Account Security:
- Email confirmation required
- Password recovery
- Account lockout after failed attempts
- Session tracking (IP address, sign-in count, timestamps)
- Encrypted OTP secret storage
- Ruby version: 3.x
- Rails version: 7.1.6
- Database: MySQL
- CSS Framework: Tailwind CSS
- Authentication: Devise
- Frontend: Turbo, Stimulus
- Ruby 3.x
- Rails 7.1.6
- MySQL 8.0+
- Node.js and Yarn (for asset compilation)
- Clone the repository:
git clone <repository-url>
cd two_factor_auth- Install dependencies:
bundle install
yarn install-
Configure database: Update
config/database.ymlwith your MySQL credentials. -
Setup encryption credentials:
# Generate encryption keys for Active Record and 2FA
rails db:encryption:init
# This will output keys that need to be added to credentials
# Add them using the credentials editor:
EDITOR="nano" rails credentials:edit
# Add the following to your credentials file:
# active_record_encryption:
# primary_key: <generated_key>
# deterministic_key: <generated_key>
# key_derivation_salt: <generated_key>
# otp_secret_encryption_key: <generate using: rails secret>- Create and setup database:
rails db:create
rails db:migrate
rails db:seed- Start the development server:
bin/devThe application will be available at http://localhost:3000
The application uses Devise for authentication with custom configuration:
- Authentication Keys: Users can login with either email or mobile number
- Confirmable: Email confirmation required for new accounts
- Lockable: Account locks after failed login attempts
- Trackable: Tracks sign-in count, timestamps, and IP addresses
- Recoverable: Password reset functionality
Update config/environments/development.rb and config/environments/production.rb with your email provider settings:
config.action_mailer.smtp_settings = {
address: 'smtp.gmail.com',
port: 587,
domain: 'example.com',
user_name: ENV['SMTP_USERNAME'],
password: ENV['SMTP_PASSWORD'],
authentication: 'plain',
enable_starttls_auto: true
}Users can register with:
- Name
- Email address
- Mobile number (10-digit format)
- Password
Users can sign in using either:
- Email: user@example.com
- Mobile Number: 9944884488
The system automatically detects which credential type is being used.
- Login to your account
- Navigate to the homepage where you'll see the 2FA settings card
- Click "Enable 2FA" button
- Scan the QR code with your authenticator app:
- Google Authenticator
- Authy
- Microsoft Authenticator
- Any TOTP-compatible app
- Enter the 6-digit code from your app to verify
- Save your backup codes - These are displayed after successful setup and can be used if you lose access to your authenticator app
When 2FA is enabled, the login process works as follows:
- Enter your email/mobile and password on the login page
- If credentials are valid, you'll be redirected to the 2FA verification page
- Enter the 6-digit code from your authenticator app
- Submit to complete login
Important Security Note: You cannot bypass the OTP verification by visiting other pages. The system ensures you remain unauthenticated until OTP is successfully verified.
- Login to your account (with OTP verification if enabled)
- Navigate to the homepage
- Click "Disable 2FA" button
- Enter your password to confirm
- Submit to disable 2FA
- Backup codes are generated when you enable 2FA
- Each code can only be used once
- Store them in a secure location
- Use them if you lose access to your authenticator app
- You can use a backup code in place of the OTP code during login
email- User's email address (unique, indexed)mobile_no- User's mobile number (unique, 10 digits, indexed)name- User's full nameencrypted_password- Bcrypt encrypted password
reset_password_token- Token for password reset (unique, indexed)reset_password_sent_at- Timestamp when reset was requested
confirmation_token- Token for email confirmation (unique, indexed)confirmed_at- Timestamp when email was confirmedconfirmation_sent_at- Timestamp when confirmation email was sentunconfirmed_email- New email waiting for confirmation
failed_attempts- Number of failed login attemptsunlock_token- Token for account unlock (unique, indexed)locked_at- Timestamp when account was locked
sign_in_count- Total number of sign-inscurrent_sign_in_at- Current sign-in timestamplast_sign_in_at- Previous sign-in timestampcurrent_sign_in_ip- Current sign-in IP addresslast_sign_in_ip- Previous sign-in IP address
encrypted_otp_secret- Encrypted TOTP secret keyencrypted_otp_secret_iv- Initialization vector for encryptionencrypted_otp_secret_salt- Salt for encryptionconsumed_timestep- Last used OTP timestep (prevents replay attacks)otp_required_for_login- Boolean flag indicating if 2FA is enabledotp_backup_codes- JSON array of backup codes
rails testThe application follows standard Ruby and Rails conventions.
Assets are automatically compiled when running bin/dev. To manually compile:
rails assets:precompile-
Set environment variables:
SECRET_KEY_BASEDATABASE_URLSMTP_USERNAMESMTP_PASSWORD
-
Precompile assets:
RAILS_ENV=production rails assets:precompile- Run migrations:
RAILS_ENV=production rails db:migrate- Start the server:
RAILS_ENV=production rails server- Password Encryption: bcrypt hashing with salt
- CSRF Protection: Enabled by default in Rails
- SQL Injection Prevention: Parameterized queries via ActiveRecord
- XSS Protection: Automatic HTML escaping in ERB templates
- Session Management: Secure, encrypted session cookies
- Account Lockout: Automatic lockout after multiple failed login attempts
- Email Confirmation: Required for account activation
- Secure Password Reset: Time-limited tokens for password recovery
- IP Tracking: Monitor sign-in locations
- TOTP Algorithm: Time-based One-Time Password (RFC 6238)
- Encrypted Storage: OTP secrets encrypted using AES-256
- Replay Attack Prevention: Consumed timestep tracking
- Backup Codes: One-time use codes for emergency access
- Session-Based OTP Flow: User not authenticated until OTP verified
- No Bypass Protection: Cannot access protected pages without OTP verification
The application implements a secure 2FA flow using a concern-based architecture:
-
AuthenticateWithOtpTwoFactor Concern (
app/controllers/concerns/authenticate_with_otp_two_factor.rb)- Handles the entire 2FA authentication flow
- Intercepts login attempts via
prepend_before_action - Validates OTP codes and backup codes
- Manages session state during authentication
-
Custom Sessions Controller (
app/controllers/users/sessions_controller.rb)- Extends Devise's SessionsController
- Includes the AuthenticateWithOtpTwoFactor concern
- Uses
prepend_before_actionto check for 2FA before standard authentication
-
User Model (
app/models/user.rb)- Implements devise-two-factor gem integration
- Encrypts OTP secrets using attr_encrypted
- Provides methods for:
enable_two_factor!- Enable 2FA for userdisable_two_factor!- Disable 2FA for usergenerate_otp_backup_codes!- Generate backup codesvalidate_and_consume_otp!- Verify OTP codeinvalidate_otp_backup_code!- Consume backup code
Login Request → Password Validation → 2FA Check
↓
2FA Enabled?
↙ ↘
Yes No
↓ ↓
Store user_id Sign in user
in session immediately
↓
Render OTP form
(NOT signed in)
↓
User enters OTP
↓
Validate OTP
↙ ↘
Valid Invalid
↓ ↓
Sign in Show error
Clear Re-render
session OTP form
- devise (4.9.x) - Authentication framework
- devise-two-factor (~> 6.1.0) - 2FA extension for Devise
- attr_encrypted (~> 4.0.0) - Encryption for OTP secrets
- rqrcode - QR code generation for authenticator app setup
- tailwindcss-rails - CSS framework
- turbo-rails - SPA-like page acceleration (disabled for auth forms)
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is available as open source under the terms of the MIT License.
For issues, questions, or contributions, please open an issue in the repository.





