Authdog

Authentication

Last updated Sep 3, 2026
View as Markdown

Add sign-in to a Python backend with Authdog. This guide uses FastAPI, but the same three primitives, a session resolver, a require_auth gate, and a logout handler, exist for Flask, Django, Starlette, and aiohttp over the same shared core.

Install from source

git clone https://github.com/authdog/web-sdk.git
cd web-sdk
python -m pip install "./packages/python[fastapi]"

The Python package is source-only, not published on PyPI. Swap [fastapi] for [flask] or [django] for other frameworks, and pin the repository commit in deployment automation.

Configure the public key

export PK_AUTHDOG="pk_..."

Set it as an environment variable, never hard-code it. The key is validated once at startup, a malformed key or one whose identity host isn't allowlisted raises immediately instead of failing on the first request.

Resolve the session

import os
from fastapi import Depends, FastAPI
from authdog.fastapi import Authdog

app = FastAPI()
authdog = Authdog(public_key=os.environ["PK_AUTHDOG"])

@app.get("/")
async def index(ctx=Depends(authdog.session)):
    return {"authenticated": ctx.is_authenticated}

authdog.session reads the token, calls userinfo, and returns a typed AuthdogContext (token, user, is_authenticated, user_info). It never raises, a missing or invalid token just yields is_authenticated == False.

Protect a route

@app.get("/me")
async def me(user=Depends(authdog.require_auth)):
    return user

require_auth is the real enforcement point: it raises 401 for unauthenticated requests and otherwise returns the user directly. Every protected route must depend on it, reading ctx.is_authenticated from session is fine for shaping a response, but it isn't a security boundary on its own. The resolved context is cached on request.state, so combining session and require_auth on one request makes at most one userinfo call.

Add a logout handler

@app.get("/logout")
async def logout(request: Request):
    return authdog.logout(request)

authdog.logout(request) expires the authdog-session cookie (HttpOnly, SameSite=Lax, Secure in production) and redirects to a redirect_uri sanitized against open redirects.

Skip the userinfo round-trip

authdog = Authdog(public_key=os.environ["PK_AUTHDOG"], fetch_user=False)

For high-throughput services validating the token elsewhere: ctx.token is populated but is_authenticated stays False, you own validation.

Next steps