A complete guide to integrating Google Authenticator using AuthenticatorAPI.com
This guide covers adding Google Authenticator-compatible TOTP two-factor authentication to Python applications — whether you are using Flask, Django, FastAPI, or a plain script. The only dependency you need is the standard requests library.
requests library (pip install requests). A database (SQLite, PostgreSQL, etc.) to store per-user secrets.Use Python's secrets module (cryptographically secure) to generate a random Base32 secret for each user at the point they enable 2FA.
import secrets import string def generate_totp_secret(length: int = 32) -> str: """Generate a cryptographically random Base32 secret.""" # Base32 alphabet: A-Z and 2-7 alphabet = string.ascii_uppercase + '234567' return ''.join(secrets.choice(alphabet) for _ in range(length)) # Example usage secret = generate_totp_secret() # e.g. 'JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PX' # Store encrypted — never store plaintext secrets from cryptography.fernet import Fernet cipher = Fernet(ENCRYPTION_KEY) # key from environment variable encrypted_secret = cipher.encrypt(secret.encode()).decode() # Save encrypted_secret to the database for this user
Call the Pair endpoint to get a QR code image, then embed it in your setup page so the user can scan it.
import requests from urllib.parse import urlencode def get_qr_code_html(app_name: str, user_email: str, secret: str) -> str: """Returns an HTML img tag containing the QR code for pairing.""" params = { 'AppName': app_name, 'AppInfo': user_email, 'SecretCode': secret, } base_url = 'https://www.authenticatorApi.com/pair.aspx' response = requests.get(base_url, params=params, timeout=10) response.raise_for_status() return response.text # Returns an <img> tag # Flask route example from flask import session, render_template # @app.route('/settings/2fa/setup') def setup_2fa(): secret = generate_totp_secret() session['pending_2fa_secret'] = secret # store until confirmed qr_html = get_qr_code_html('MyApp', session['user_email'], secret) return render_template('2fa_setup.html', qr=qr_html, secret=secret)
Once the user has set up 2FA, validate every login by calling the Validate endpoint.
def validate_totp_pin(pin: str, secret: str) -> bool: """Returns True if the 6-digit pin is valid for the given secret.""" if not pin.isdigit() or len(pin) != 6: return False params = {'Pin': pin, 'SecretCode': secret} response = requests.get( 'https://www.authenticatorApi.com/Validate.aspx', params=params, timeout=10 ) response.raise_for_status() return response.text.strip().lower() == 'true' # Flask login route example from flask import request, redirect, url_for, flash # @app.route('/login/verify', methods=['POST']) def verify_2fa(): pin = request.form.get('pin', '') secret = get_user_secret(session['user_id']) # decrypt from DB try: valid = validate_totp_pin(pin, secret) except requests.RequestException: flash('Could not reach authentication service. Try again.', 'error') return redirect(url_for('verify_2fa_form')) if valid: session['2fa_verified'] = True return redirect(url_for('dashboard')) else: flash('Invalid code. Please try again.', 'error') return redirect(url_for('verify_2fa_form'))
In Flask, create a decorator that enforces 2FA verification before allowing access to protected views.
from functools import wraps from flask import session, redirect, url_for def require_2fa(f): @wraps(f) def decorated_function(*args, **kwargs): if not session.get('user_id'): return redirect(url_for('login')) if user_has_2fa(session['user_id']) and not session.get('2fa_verified'): return redirect(url_for('verify_2fa_form')) return f(*args, **kwargs) return decorated_function # Usage: @app.route('/dashboard') @require_2fa def dashboard(): return render_template('dashboard.html')
For Django, the same logic applies but using Django's ORM and middleware patterns.
# views.py import requests from django.contrib.auth.decorators import login_required from django.shortcuts import redirect, render from django.contrib import messages from .models import UserProfile @login_required def verify_2fa(request): if request.method == 'POST': pin = request.POST.get('pin', '') profile = UserProfile.objects.get(user=request.user) secret = profile.get_decrypted_totp_secret() resp = requests.get( 'https://www.authenticatorApi.com/Validate.aspx', params={'Pin': pin, 'SecretCode': secret}, timeout=10 ) if resp.text.strip().lower() == 'true': request.session['2fa_verified'] = True return redirect('dashboard') else: messages.error(request, 'Invalid code. Please try again.') return render(request, 'auth/verify_2fa.html') # middleware.py — enforce 2FA on all protected views class TwoFactorMiddleware: EXEMPT_PATHS = ['/login/', '/login/verify/', '/logout/'] def __init__(self, get_response): self.get_response = get_response def __call__(self, request): if (request.user.is_authenticated and request.path not in self.EXEMPT_PATHS and user_has_2fa(request.user) and not request.session.get('2fa_verified')): return redirect('/login/verify/') return self.get_response(request)
from fastapi import FastAPI, HTTPException, Depends from fastapi.security import OAuth2PasswordBearer import httpx app = FastAPI() async def validate_totp(pin: str, secret: str) -> bool: async with httpx.AsyncClient() as client: response = await client.get( 'https://www.authenticatorApi.com/Validate.aspx', params={'Pin': pin, 'SecretCode': secret}, timeout=10.0 ) return response.text.strip().lower() == 'true' @app.post('/auth/verify-2fa') async def verify_2fa(pin: str, current_user = Depends(get_current_user)): secret = await get_user_secret(current_user.id) if not await validate_totp(pin, secret): raise HTTPException(status_code=401, detail='Invalid 2FA code') return {'status': 'verified'}
secrets.choice() not random.choice() for secret generationcryptography.fernet or Django's field encryptionrequests.RequestException and fail gracefullysession.cycle_key() in Django)SecretCode value — scrub it from any request logging