Free forever. Two API calls. Integrate 2FA into any website or application in under an hour.
You can use the service for free at app.secure-2fa.com. Registration requires only a valid email address and an authenticator application (Google Authenticator or Twilio Authy).
Sign up with your email. A confirmation link will be sent. After confirming, set your password and link your authenticator app.
A Project represents the section of your website where 2FA will be implemented. Each project gets a unique public key and secret key.
Configure how OTP messages are sent — choose email or SMS, then select and configure your preferred delivery provider (Postmark, Twilio, Sendgrid, etc).
Define the OTP message content. Add a Template Version per language with subject and body. Include the {{OTP_CODE}} variable in the body.
Link your Project to the Channel and Template. On your user authentication page, add the 2FA validation form that will call the OTP verification endpoint.
Use the two API calls below to generate and verify OTP codes. Encrypt the data with your project's secret key using AES-256.
template_request: true, the service returns the template with the OTP — you handle delivery yourself.encrypted_data parameters are encrypted using AES-256 CBC with a 32-byte symmetric key (your project's secret key). The encrypted string is Base64-encoded. The format before encryption is: uuid#email_or_phone (for generate) or uuid#otp_code (for verify).
require 'openssl' require 'base64' require 'net/http' def encrypt(plain_text, key, iv) cipher = OpenSSL::Cipher::AES256.new(:CBC) cipher.encrypt cipher.key = key cipher.iv = iv Base64.encode64(iv + cipher.update(plain_text) + cipher.final) end def generate_otp(uuid, to, public_key, secret_key, lang: 'en') iv = OpenSSL::Cipher::AES256.new(:CBC).random_iv encrypted = encrypt("#{uuid}##{to}", secret_key, iv) uri = URI.parse('https://api.secure-2fa.com/otp/generate') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true req = Net::HTTP::Post.new(uri.path) req.set_form_data( encrypted_data: encrypted, public_key: public_key, lang: lang ) http.request(req) end def verify_otp(uuid, otp_code, public_key, secret_key) iv = OpenSSL::Cipher::AES256.new(:CBC).random_iv encrypted = encrypt("#{uuid}##{otp_code}", secret_key, iv) uri = URI.parse('https://api.secure-2fa.com/otp/verify') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true req = Net::HTTP::Post.new(uri.path) req.set_form_data( encrypted_data: encrypted, public_key: public_key ) JSON.parse(http.request(req).body)['verified'] end
function encryptData($plainText, $key) { $iv = openssl_random_pseudo_bytes(16); $encrypted = openssl_encrypt( $plainText, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv ); return base64_encode($iv . $encrypted); } function generateOtp($uuid, $to, $publicKey, $secretKey) { $encrypted = encryptData("{$uuid}#{$to}", $secretKey); $ch = curl_init('https://api.secure-2fa.com/otp/generate'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_POSTFIELDS => http_build_query([ 'encrypted_data' => $encrypted, 'public_key' => $publicKey, ]), ]); return json_decode(curl_exec($ch), true); } function verifyOtp($uuid, $otpCode, $publicKey, $secretKey) { $encrypted = encryptData("{$uuid}#{$otpCode}", $secretKey); $ch = curl_init('https://api.secure-2fa.com/otp/verify'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_POSTFIELDS => http_build_query([ 'encrypted_data' => $encrypted, 'public_key' => $publicKey, ]), ]); $response = json_decode(curl_exec($ch), true); return $response['verified'] ?? false; }
import base64 import requests from Crypto.Cipher import AES from Crypto.Random import get_random_bytes def encrypt_data(plain_text, key): iv = get_random_bytes(16) cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv) padding = 16 - len(plain_text) % 16 padded = plain_text + (' ' * padding) return base64.b64encode(iv + cipher.encrypt(padded.encode())).decode() def generate_otp(uuid, to, public_key, secret_key, lang='en'): encrypted = encrypt_data(f"{uuid}#{to}", secret_key) response = requests.post( 'https://api.secure-2fa.com/otp/generate', data={ 'encrypted_data': encrypted, 'public_key': public_key, 'lang': lang } ) return response.json() def verify_otp(uuid, otp_code, public_key, secret_key): encrypted = encrypt_data(f"{uuid}#{otp_code}", secret_key) response = requests.post( 'https://api.secure-2fa.com/otp/verify', data={ 'encrypted_data': encrypted, 'public_key': public_key } ) return response.json().get('verified', False)
The entire project is open-source (MIT License). Clone the GitHub repository and deploy it on your own infrastructure — full data and traffic privacy, no dependency on the hosted service.
$ git clone https://github.com/helpliviu/secure-2fa $ cd secure-2fa $ docker-compose up -d # pre-configured Docker file included
API endpoints, routes, and credentials work identically to the hosted version. Simply change the base URL in your integration to point to your own server.
Fork the repository and extend the application with new delivery providers or features. Start the development environment with:
$ bin/dev # starts Rails server + asset compilers + Sidekiq via Foreman
app/models/channel.rb, add a new hash entry to the providers array with the service name, handle, supported methods, and required credentials.app/services/ with the same filename prefix as the handle (e.g. myservice_service.rb).Gemfile and run bundle install.Liviu Dumitru is a software developer with 20 years of experience, specialized in web application security and backend development. Free assistance available for any integration or security question.
helpliviu.orgRegister a free account to get your project's public key and secret key.
app.secure-2fa.comDownload the complete technical book written by Liviu Dumitru with full code listings.
Download PDF