Backend
Phone verification in Flask and Django with one API key
Raya Dev.to (EN Zone)
20 views
Most "phone verification in Python" tutorials are four years old and assume a Twilio account. Here's the version with one API key and two HTTP calls. I work at MyOTP.App, the API used below, and the full Flask and Django examples are in the brntech/myotp-agentkit repo.
The two calls
Send:
import os, re, requests
API = "https://api.myotp.app"
HEADERS = {"X-API-Key": os.environ["MYOTP_API_KEY"]}
def clean(phone: str) -> str:
# digits only, country code first, no plus, no leading zero
return re.sub(r"\D", "", phone).lstrip("0")
def generate_otp(phone: str, channel: str = "sms") -> dict:
r = requests.post(f"{API}/generate_otp", headers=HEADERS,
json={"phone_number": phone, "channel": channel, "otp_length": 6})
r.raise_for_status()
return r.json() # {"message_id": "...", "status": "accepted", "expires_at": "...", "cost": 1.0}
Verify:
def verify_otp(phone: str, message_id: str, otp: str) -> dict:
r = requests.post(f"{API}/verify_otp", headers=HEADERS,
json={"phone_number": phone, "message_id": message_id, "otp": otp})
return r.json() # {"status": "success"} or {"status": "failed", "reason": "invalid" | "expired" | "not found"}
Everything else is your framework's job: where to keep message_id between the two requests, and what to do on success.
Flask
Keep message_id in the signed session cookie. It's an opaque UUID and the cookie is tamper-proof, so that's enough for a single server.
@app.post("/send")
def send():
phone = clean(request.form["phone"])
result = generate_otp(phone, request.form.get("channel", "sms"))
session["message_id"], session["phone"] = result["message_id"], phone
return redirect(url_for("verify"))
@app.post("/verify")
def verify():
result = verify_otp(session["phone"], session["message_id"], request.form["code"])
if result["status"] != "success":
return render_template("verify.html", error=result["reason"]), 400
session.pop("message_id")
return redirect(url_for("done"))
For more than one server, use flask-session or a database row instead of the cookie.
Django
Same shape, with the session backend you already have. Put the two functions in verification/services.py and the views become:
def send(request):
phone = clean(request.POST["phone"])
result = generate_otp(phone)
request.session["otp"] = {"message_id": result["message_id"], "phone": phone}
return redirect("verify")
def verify(request):
pending = request.session.get("otp")
result = verify_otp(pending["phone"], pending["message_id"], request.POST["code"])
if result["status"] != "success":
return render(request, "verify.html", {"error": result["reason"]}, status=400)
del request.session["otp"]
request.user.profile.phone_verified_at = timezone.now()
request.user.profile.save()
return redirect("done")
The three mistakes
Calling the API from the browser. The key would ship in your JavaScript. Both examples above run server side.
Verifying by phone number instead of message_id. Two codes in flight to one number and you'll accept the wrong one.
Forgetting the IP allowlist. A key made from your laptop is allowlisted to your laptop. The first send from the server comes back 403 with "Access from this IP not allowed". Add the server IP in the dashboard. It's not the key.
Channels
channel takes sms, whatsapp or telegram. Nothing else in the code changes. If your users are somewhere SMS is slow or expensive, that one string is the difference.
Disclosure again: I work at MyOTP.App. 15 trial credits on signup, no card, and the examples are MIT.
Read original: https://dev.to/rayas/phone-verification-in-flask-and-django-with-one-api-key-3cgp
← Previous
Designing CRM Integrations as Reliable Data Pipelines
Next →
GitHub Actions for Free 24/7 AI Automation
Related
The benchmark everyone cited for "fastest backend" was archived in March, and the reason is the interesting part
Backend
56
Dev.to (EN Zone)
Finding Exoplanets in Noisy Data with Machine Learning
Backend
46
DEV Community
How to automatically find the batch size when using Accelerate with FSDP2? [D]
Backend
43
Reddit r/MachineLearning
Cross-Chain Bridge Risk Assessment: Aave V3
Backend
36
DEV Community
Comments0
No comments yet — be the first