Using the Service

Free forever. Two API calls. Integrate 2FA into any website or application in under an hour.

Forever Free Web Application

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).

Implementation Steps
1
Register & confirm your account

Sign up with your email. A confirmation link will be sent. After confirming, set your password and link your authenticator app.

2
Create a Project

A Project represents the section of your website where 2FA will be implemented. Each project gets a unique public key and secret key.

3
Create a Channel

Configure how OTP messages are sent — choose email or SMS, then select and configure your preferred delivery provider (Postmark, Twilio, Sendgrid, etc).

4
Create a Template

Define the OTP message content. Add a Template Version per language with subject and body. Include the {{OTP_CODE}} variable in the body.

5
Enable 2FA in your app

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.

6
Integrate the API

Use the two API calls below to generate and verify OTP codes. Encrypt the data with your project's secret key using AES-256.

Implementation Notes:
  • For any OTP validation added to your app, you need to redirect the user to the validation form. Recommended: use the OTP-generation endpoint to show the form to the user. If successful login, don't forget to clear the session created before step 2.
  • It's recommended that the validation form has a link that will call the OTP generation function again for expired codes or delivery failures.
  • If using template_request: true, the service returns the template with the OTP — you handle delivery yourself.

API Endpoints

Data Encryption: All 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).
POST https://api.secure-2fa.com/otp/generate

Generates and delivers a new OTP code to the user. Call this after the user successfully enters their email and password.

Required Parameters
Parameter Type Description
encrypted_datastringAES-256 encrypted string: uuid#email_or_phone. Phone in E.164 format (e.g. +14155552671).
public_keystringYour project's public key (32 hex chars, found on the project page).
Optional Parameters
langstringTwo-letter language code (e.g. en, fr). First template language used if omitted.
ip_addressstringUser's IP — compared against the verify call for device validation.
user_agentstringUser's browser agent — used to detect unauthorized device access.
template_requestbooleanDefault: false. If true, returns the rendered template instead of sending the message.
Response (JSON)
{
  "success": true,          // true on success
  "message": "OTP generated successfully.",
  "subject": "Your login code",
                             // subject — only if template_request: true
  "body": "Your code is 482719"
                             // body — only if template_request: true
}
            
POST https://api.secure-2fa.com/otp/verify

Verifies the OTP code entered by the user. Call this when the user submits the 6-digit code on your verification form.

Required Parameters
encrypted_datastringAES-256 encrypted string: uuid#otp_six_digit_code
public_keystringYour project's public key.
Response (JSON)
// Success response:
{
  "verified": true,
  "message": "OTP verified successfully"
}

// Error responses:
{
  "verified": false,
  "message": "Invalid or expired OTP"       // HTTP 401 — expired or wrong code
}
{
  "verified": false,
  "message": "Too many requests. Request a new OTP"  // HTTP 429 — max attempts exceeded
}
{
  "verified": false,
  "message": "Unauthorized device."                 // HTTP 401 — IP or user-agent mismatch
}
            

Integration Examples

Ruby
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
        
PHP
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;
}
        
Python
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)
        

Open-Source — Deploy your own

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.

Developing New Features

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
        
Adding a new delivery provider
  1. In app/models/channel.rb, add a new hash entry to the providers array with the service name, handle, supported methods, and required credentials.
  2. Create a service file in app/services/ with the same filename prefix as the handle (e.g. myservice_service.rb).
  3. If the provider has a maintained Ruby gem, add it to the Gemfile and run bundle install.
  4. Implement the service using the same pattern as existing services. The call must return a JSON with a message identifier and delivery status.
About the developer

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.org
Sections
  • 1 — Free Web App
  • API: POST /otp/generate
  • API: POST /otp/verify
  • Ruby Example
  • PHP Example
  • Python Example
  • 2 — Self-host
  • 3 — New Features
Get Your API Keys

Register a free account to get your project's public key and secret key.

app.secure-2fa.com
Full Book

Download the complete technical book written by Liviu Dumitru with full code listings.

Download PDF