Python
A Flask route using requests. The same call works in Django or FastAPI.
Browser
<script src="https://nolgard.com/v1/client.js" data-site-key="SITE_KEY" defer></script>
<script>
document.querySelector('#signup').addEventListener('submit', async (event) => {
event.preventDefault();
await Nolgard.ready;
const token = await Nolgard.execute({ action: 'signup' });
const form = event.target;
const response = await fetch('/signup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token, email: form.elements.email.value })
});
// Show your own success or error message here.
});
</script>Flask
# app.py
import os
import requests
from flask import Flask, jsonify, request
app = Flask(__name__)
def nolgard_verify(token, action, account=None):
payload = {
"secret": os.environ["NOLGARD_SECRET"],
"token": token,
"remoteIp": request.remote_addr,
"action": action,
}
if account:
payload["account"] = account
response = requests.post("https://nolgard.com/v1/verify", json=payload, timeout=3)
if response.status_code >= 500:
raise requests.HTTPError(response=response)
return response.json()
@app.post("/signup")
def signup():
data = request.get_json(silent=True) or {}
email = data.get("email", "")
try:
result = nolgard_verify(data.get("token", ""), "signup", {"email": email})
except (requests.RequestException, ValueError):
result = {"success": True, "decision": "allow"} # timeout or 5xx: fail open
if not result.get("success"):
if result.get("error") == "invalid-secret":
return jsonify(error="try-again"), 503 # config error: fail closed
if result.get("error") != "over-quota":
return jsonify(error="rejected"), 403
result = {"success": True, "decision": "allow"} # over quota: fail open
decision = result["decision"]
if decision == "allow":
create_account(email)
elif decision == "limit":
create_account(email, trial_days=3)
elif decision == "shadow":
pass # same response, nothing created
elif decision == "challenge":
return jsonify(error="confirm-email"), 403
else:
return jsonify(error="rejected"), 403
return jsonify(ok=True)Supply your own create_account function. If you control the proxy in front of your app, wrap the app in Werkzeug's ProxyFix to set remote_addr to the visitor's address.
Django
# views.py
import json
import os
import requests
from django.http import JsonResponse
from django.views.decorators.http import require_POST
@require_POST
def signup(request):
data = json.loads(request.body or b"{}")
email = data.get("email", "")
try:
response = requests.post(
"https://nolgard.com/v1/verify",
json={
"secret": os.environ["NOLGARD_SECRET"],
"token": data.get("token", ""),
"remoteIp": request.META.get("REMOTE_ADDR"),
"action": "signup",
"account": {"email": email},
},
timeout=3,
)
if response.status_code >= 500:
raise requests.HTTPError(response=response)
result = response.json()
except (requests.RequestException, ValueError):
result = {"success": True, "decision": "allow"} # timeout or 5xx: fail open
if not result.get("success"):
if result.get("error") == "invalid-secret":
return JsonResponse({"error": "try-again"}, status=503) # config error: fail closed
if result.get("error") != "over-quota":
return JsonResponse({"error": "rejected"}, status=403)
result = {"success": True, "decision": "allow"} # over quota: fail open
decision = result["decision"]
if decision == "allow":
create_account(email)
elif decision == "limit":
create_account(email, trial_days=3)
elif decision == "shadow":
pass
elif decision == "challenge":
return JsonResponse({"error": "confirm-email"}, status=403)
else:
return JsonResponse({"error": "rejected"}, status=403)
return JsonResponse({"ok": True})Keep Django's CSRF protection enabled and send its CSRF token with your form. Supply your own create_account function. Behind a proxy, use the address set by your trusted proxy. Do not trust unchecked forwarding headers.