Skip to content

Python SDK

bloonio_auth_relay_client is the Auth Relay’s backend SDK. It wraps HMAC #1 signing, the two-call sudo protocol, the pairing handshake, and callbacks — a framework-agnostic core, plus thin FastAPI and Django adapters.

Fenêtre de terminal
pip install "bloonio-auth-relay-client[fastapi,redis]"
# or
pip install "bloonio-auth-relay-client[django,redis]"

Available extras: fastapi, django, redis, mongo, all.

  • RelayClient / AsyncRelayClient — an HMAC-signed HTTP wrapper around the relay. Synchronous version for Django, asynchronous for FastAPI; same surface.
  • SudoInstructionStore — a short-lived store (~180 s) that tracks “this instruction passed sudo, here’s the re-call window with X-Sudo-Instruction-Key”. Default backend: Redis.
  • PendingOpStore — a longer store (10–30 min) that keeps the original mutation for server-side replay (required for the group quorum v2 / cross-org v3).
  • @sudo_required — the decorator; same arguments in FastAPI and Django.
  • DataType + ValidationField — structured display blocks the authenticator app renders (currency, IBAN, date, entity reference, etc.).

RelaySettings reads the BLOONIO_RELAY_ prefix. The essential keys:

VariableRole
BLOONIO_RELAY_BASE_URLThe relay’s base URL, e.g. https://auth-relay.bloonio.com.
BLOONIO_RELAY_TENANT_IDThe calling tenant’s tenant_id.
BLOONIO_RELAY_TENANT_SECRETThe tenant_secret (HMAC #1 signing).
BLOONIO_RELAY_STATE_BACKENDredis (default) or memory.
BLOONIO_RELAY_STATE_BACKEND_URLRedis URL when state_backend=redis.
BLOONIO_RELAY_CALLBACK_PATHPath where the relay POSTs the ApprovalEvent (default /api/sudo-callback).
BLOONIO_RELAY_BASE_URL=https://auth-relay.bloonio.com
BLOONIO_RELAY_TENANT_ID=tnt_...
BLOONIO_RELAY_TENANT_SECRET=sk_...
BLOONIO_RELAY_STATE_BACKEND=redis
BLOONIO_RELAY_STATE_BACKEND_URL=redis://localhost:6379/0

BloonioAuthAdapter.from_env(app) builds RelaySettings from the environment, instantiates an AsyncRelayClient, and mounts the dispatch middleware, the auto-response router (/_bloonio/submit-response), and the callback router.

For named routes where you know in advance which fields to display:

from fastapi import FastAPI
from bloonio_auth_relay_client import DataType, SudoActionType, ValidationField
from bloonio_auth_relay_client.adapters.fastapi import BloonioAuthAdapter
app = FastAPI()
bloonio = BloonioAuthAdapter.from_env(app) # reads the BLOONIO_RELAY_*
@app.post(
"/transfer/execute",
dependencies=[bloonio.sudo_required(
expected_action="transfer_funds",
custom_type=SudoActionType.LOCAL_AUTH,
user_socket_hash=lambda req: req.state.user.socket_hash,
title="Confirm the transfer",
fields=lambda req: [
ValidationField(key="amount", title="Amount",
value=str(req.state.body["amount"]),
data_type=DataType.CURRENCY_USD),
ValidationField(key="to", title="Beneficiary",
value=req.state.body["recipient"],
data_type=DataType.PARTY_NAME),
],
)],
)
async def transfer_execute(...): ...

When sudo is required, the SDK creates an instruction_id, writes the Redis state, calls send_auth_challenge (FCM push), and returns 403 {error: "SUDO_INSTRUCTION_KEY_REQUIRED", instruction_id}. On approval, the state flips to validated and the re-call with X-Sudo-Instruction-Key goes through. See Sudo (step-up).

settings.py
INSTALLED_APPS = [..., "bloonio_auth_relay_client.adapters.django"]
MIDDLEWARE = [..., "bloonio_auth_relay_client.adapters.django.middleware.SudoActionMiddleware"]
BLOONIO_AUTH_RELAY = {
"BASE_URL": "https://auth-relay.bloonio.com",
"TENANT_ID": os.environ["RELAY_TENANT_ID"],
"TENANT_SECRET": os.environ["RELAY_TENANT_SECRET"],
"STATE_BACKEND": "redis",
"STATE_BACKEND_URL": os.environ["REDIS_URL"],
"CALLBACK_PATH": "/api/sudo-callback/",
}
urls.py
from bloonio_auth_relay_client.adapters.django import urls as relay_urls
urlpatterns = [..., path("", include(relay_urls))]
views.py
from bloonio_auth_relay_client.adapters.django import sudo_required
from bloonio_auth_relay_client import DataType, SudoActionType, ValidationField
@sudo_required(
expected_action="transfer_funds",
custom_type=SudoActionType.LOCAL_AUTH,
user_socket_hash=lambda req: req.user.socket_hash,
title="Confirm the transfer",
fields=lambda req: [
ValidationField(key="amount", title="Amount",
value=str(req.POST["amount"]),
data_type=DataType.CURRENCY_USD),
],
)
def transfer_execute(request):
...

Works on plain views, DRF’s @api_view and APIView/ViewSet, and async views (Django 4.1+). Same decorator, same arguments — just req.user instead of req.state.user and req.POST instead of req.state.body.

Called once per device pairing, from your QR handler after the user is authenticated:

from bloonio_auth_relay_client import AsyncRelayClient, RelaySettings
relay = AsyncRelayClient(RelaySettings())
result = await relay.prepare_pairing(
user_socket_hash=user.socket_hash,
backend_user_id=str(user.id),
user_email=user.email,
user_phone=user.phone,
first_name=user.first_name,
last_name=user.last_name,
display_name="Example",
display_logo_url="https://example.com/logo.png",
)
pairing_proof = result["pairing_proof"] # to forward to the device

See Pairing & devices for the rest of the device-side flow.