Python: Django, Django REST framework and Flask
Python 3.9+, no runtime dependencies. Protect an API with the secret key, or sign users in from server-rendered pages with the publishable key; the token stays in the server-side session.
Install
tausi on PyPIRequires Python 3.9+; no dependenciespip install "tausi[django]" # Django and Django REST framework
pip install "tausi[flask]" # Flask
pip install tausi # FastAPI and anything elseSettings come from your framework config or the environment: TAUSI_URL, TAUSI_SECRET_KEY, and TAUSI_PUBLISHABLE_KEY (only for server-rendered sign-in). TAUSI_TIMEOUT sets the request timeout in seconds.
Django: protect views
TAUSI_URL = "https://tausi-auth.onrender.com"
TAUSI_SECRET_KEY = os.environ["TAUSI_SECRET_KEY"]
MIDDLEWARE = [
# ...
"tausi.django.TausiAuthMiddleware", # optional: sets request.tausi on every request
]from django.http import JsonResponse
from tausi.django import tausi_optional, tausi_required
@tausi_required # 401 JSON without a valid Bearer token; async views work too
def me(request):
return JsonResponse(request.tausi.user.to_dict())
@tausi_optional # request.tausi is an AuthResult or None
def feed(request):
...Only requests that carry a bearer token cost a call to Tausi. If Tausi can't be reached the decorators and middleware answer 503 with code AUTH_UNAVAILABLE instead of signing people out.
Django REST framework
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": ["tausi.django.rest_framework.TausiAuthentication"],
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
}class Me(APIView):
def get(self, request):
# request.user is a tausi.User (is_authenticated is True); request.auth is the AuthResult
return Response({"id": request.user.id, "email": request.user.email})Requests without a bearer token fall through to your other authentication classes.
Django: server-rendered sign-in
from django.shortcuts import redirect, render
from tausi import TausiError
from tausi.django import get_client, tausi_login_required
# settings.py: TAUSI_PUBLISHABLE_KEY = os.environ["TAUSI_PUBLISHABLE_KEY"] (needs SessionMiddleware)
def sign_in(request):
if request.method == "POST":
try:
get_client(request).sign_in(request.POST["email"], request.POST["password"])
request.session.cycle_key()
return redirect(request.GET.get("next", "/"))
except TausiError as e:
return render(request, "sign_in.html", {"error": e.message, "code": e.code})
return render(request, "sign_in.html")
def sign_up(request):
try:
get_client(request).sign_up(
request.POST["email"], request.POST["password"], request.POST["name"],
username=request.POST.get("username") or None,
phone_number=request.POST.get("phone") or None,
)
except TausiError as e:
if e.code == "EMAIL_ALREADY_REGISTERED":
... # offer sign-in / password reset; the owner has been emailed
@tausi_login_required # redirects to settings.LOGIN_URL with ?next=
def dashboard(request):
return render(request, "dashboard.html", {"user": request.tausi.user})Also on get_client(request): sign_out, update_user(first_name=…, timezone=None), change_password, forgot_password, reset_password, verify_email, list_sessions, revoke_session, start_oauth and handle_oauth_redirect.
Flask
from flask import Flask, g, redirect, render_template, request, session
from tausi import TausiError
from tausi.flask import Tausi
app = Flask(__name__)
app.config.update(TAUSI_URL="https://tausi-auth.onrender.com", TAUSI_SECRET_KEY=..., TAUSI_PUBLISHABLE_KEY=...)
tausi = Tausi(app)
@app.get("/api/me")
@tausi.required # 401 JSON without a valid Bearer token
def me():
return g.tausi.user.to_dict()
@app.post("/sign-in")
def login():
try:
tausi.client().sign_in(request.form["email"], request.form["password"]) # token kept in flask.session
except TausiError as e:
return render_template("sign_in.html", error=e.message), 400
return redirect(request.args.get("next", "/"))
@app.get("/dashboard")
@tausi.login_required(login_endpoint="login")
def dashboard():
return render_template("dashboard.html", user=g.tausi.user)
@app.get("/login/google")
def google():
start = tausi.client().start_oauth("google", redirect_to="https://app.example.com/auth/callback")
session["tausi_verifier"] = start.code_verifier
return redirect(start.url)
@app.get("/auth/callback")
def callback():
tausi.client().handle_oauth_redirect(request.url, session.pop("tausi_verifier", ""))
return redirect("/dashboard")FastAPI and other frameworks
from fastapi import Depends, FastAPI, HTTPException, Request
from starlette.concurrency import run_in_threadpool
from tausi import AuthResult, TausiBackend
tausi = TausiBackend("https://tausi-auth.onrender.com", os.environ["TAUSI_SECRET_KEY"])
app = FastAPI()
async def current_auth(request: Request) -> AuthResult:
auth = await run_in_threadpool(tausi.authenticate_request, request) # the SDK is synchronous
if auth is None:
raise HTTPException(401, "Sign in to continue.")
return auth
@app.get("/me")
async def me(auth: AuthResult = Depends(current_auth)):
return auth.user.to_dict()authenticate_request() accepts any request with .headers, a headers mapping, or the header value. To use requests or httpx, pass transport=.
Manage users
from tausi.django import get_backend # Flask: tausi.backend; elsewhere: TausiBackend(...)
backend = get_backend()
page = backend.users.list(q="asha", access="active", page_size=50)
for u in page.items:
print(u.email, u.status, u.active_sessions, u.last_sign_in_at, u.access)
detail = backend.users.get(user_id) # user, sessions (devices), signInHistory, recentAudit
backend.users.update(user_id, email_verified=True, public_metadata={"plan": "pro"}, private_metadata={"stripe": "cus_123"})
backend.users.suspend(user_id, hours=24, reason="Chargeback")
backend.users.ban(user_id, reason="Spam")
backend.users.unban(user_id)
backend.users.bulk("revoke_sessions", [id_a, id_b])
backend.users.delete(user_id)See Managing users.
Errors
| Attribute | Meaning |
|---|---|
e.code | Stable error code, e.g. INVALID_CREDENTIALS, ACCOUNT_SUSPENDED. Branch on this. |
e.status | HTTP status; 0 for NETWORK_ERROR and TIMEOUT. |
e.message | Human-readable text you can show. |
e.details | Extra data, e.g. {"issues": [{"path", "message"}]} for VALIDATION_ERROR. |
e.retry_after | Seconds to wait, for RATE_LIMITED. |
e.request_id | Quote it when reporting a problem. |