Sdks
Python SDK
Use the hellojohn Python package to verify tokens and manage users from FastAPI, Django, Flask, or any Python backend.
Python SDK
The hellojohn Python package provides token verification and full admin API access for Python backends.
Installation
pip install hellojohnSetup
import hellojohn
client = hellojohn.Client(
secret_key=os.environ["HJ_SECRET_KEY"],
# Optional: for self-hosted
api_url=os.environ.get("HJ_API_URL"),
)Token verification
from hellojohn import verify_token, InvalidTokenError
def get_current_user(authorization: str):
if not authorization.startswith("Bearer "):
raise ValueError("Invalid authorization header")
token = authorization.removeprefix("Bearer ")
try:
payload = verify_token(token, secret_key=os.environ["HJ_SECRET_KEY"])
return payload
except InvalidTokenError as e:
raise Exception(f"Unauthorized: {e}")
# payload fields:
# payload.sub — user ID
# payload.email — user email
# payload.tenant_id — tenant ID
# payload.role — "admin" | "member"
# payload.custom_claims — dict of custom JWT claimsFastAPI integration
from fastapi import FastAPI, Depends, HTTPException, Header
from hellojohn import verify_token, InvalidTokenError, TokenPayload
app = FastAPI()
async def get_current_user(authorization: str = Header(...)) -> TokenPayload:
if not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Invalid authorization header")
token = authorization.removeprefix("Bearer ")
try:
return verify_token(token, secret_key=os.environ["HJ_SECRET_KEY"])
except InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid or expired token")
def require_role(role: str):
async def dependency(user: TokenPayload = Depends(get_current_user)):
if user.role != role:
raise HTTPException(status_code=403, detail="Insufficient permissions")
return user
return dependency
# Protected route
@app.get("/api/me")
async def get_me(user: TokenPayload = Depends(get_current_user)):
return {
"user_id": user.sub,
"email": user.email,
"tenant_id": user.tenant_id,
"role": user.role,
}
# Admin-only route
@app.delete("/api/users/{user_id}")
async def delete_user(
user_id: str,
admin: TokenPayload = Depends(require_role("admin")),
hj: hellojohn.Client = Depends(lambda: hellojohn.Client(os.environ["HJ_SECRET_KEY"])),
):
await hj.users.delete(user_id)
return {"deleted": True}Django integration
# middleware.py
from hellojohn import verify_token, InvalidTokenError
class HelloJohnAuthMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
auth_header = request.META.get("HTTP_AUTHORIZATION", "")
if auth_header.startswith("Bearer "):
token = auth_header.removeprefix("Bearer ")
try:
request.hj_user = verify_token(
token,
secret_key=settings.HJ_SECRET_KEY,
)
except InvalidTokenError:
request.hj_user = None
else:
request.hj_user = None
return self.get_response(request)
# decorators.py
from functools import wraps
from django.http import JsonResponse
def require_auth(view_func):
@wraps(view_func)
def wrapper(request, *args, **kwargs):
if request.hj_user is None:
return JsonResponse({"error": "Unauthorized"}, status=401)
return view_func(request, *args, **kwargs)
return wrapper
def require_role(role):
def decorator(view_func):
@wraps(view_func)
def wrapper(request, *args, **kwargs):
if request.hj_user is None or request.hj_user.role != role:
return JsonResponse({"error": "Forbidden"}, status=403)
return view_func(request, *args, **kwargs)
return wrapper
return decorator
# views.py
@require_auth
def get_me(request):
return JsonResponse({
"user_id": request.hj_user.sub,
"email": request.hj_user.email,
})User management
import asyncio
import hellojohn
client = hellojohn.Client(secret_key=os.environ["HJ_SECRET_KEY"])
# Async API
async def main():
# List users
result = await client.users.list(tenant_id="tnt_01H...", limit=50)
for user in result.data:
print(user.email)
# Get a user
user = await client.users.get("usr_01H...")
# Create a user
new_user = await client.users.create(
email="alice@example.com",
password="strongpassword",
display_name="Alice",
tenant_id="tnt_01H...",
role="member",
)
# Update a user
updated = await client.users.update("usr_01H...", display_name="Alice Smith")
# Disable / delete
await client.users.disable("usr_01H...")
await client.users.delete("usr_01H...")
asyncio.run(main())Synchronous client
# For sync contexts (Django views, scripts)
client = hellojohn.SyncClient(secret_key=os.environ["HJ_SECRET_KEY"])
users = client.users.list(tenant_id="tnt_01H...")
user = client.users.get("usr_01H...")Webhook verification
# FastAPI webhook handler
from hellojohn import verify_webhook_signature, WebhookVerificationError
@app.post("/webhooks/hellojohn")
async def handle_webhook(
request: Request,
x_hellojohn_signature: str = Header(...),
x_hellojohn_timestamp: str = Header(...),
):
body = await request.body()
try:
verify_webhook_signature(
payload=body,
signature=x_hellojohn_signature,
timestamp=x_hellojohn_timestamp,
secret=os.environ["WEBHOOK_SECRET"],
)
except WebhookVerificationError:
raise HTTPException(status_code=400, detail="Invalid webhook signature")
event = await request.json()
if event["type"] == "user.created":
await on_user_created(event["data"])
elif event["type"] == "user.login":
await on_user_login(event["data"])
return {"received": True}Error handling
from hellojohn.exceptions import (
HelloJohnError,
InvalidTokenError,
UserNotFoundError,
TenantNotFoundError,
RateLimitError,
ValidationError,
)
try:
user = await client.users.get("usr_nonexistent")
except UserNotFoundError:
print("User not found")
except RateLimitError as e:
print(f"Rate limited. Retry after {e.retry_after} seconds")
except HelloJohnError as e:
print(f"Error {e.code}: {e.message}")Async vs sync
| Use case | Client |
|---|---|
| FastAPI, Starlette, aiohttp | hellojohn.Client (async) |
| Django (sync views) | hellojohn.SyncClient |
| Flask | hellojohn.SyncClient |
| Background scripts | Either |
The async Client uses httpx.AsyncClient internally. The sync SyncClient uses httpx.Client.