Authdog

Authentication

Last updated Jul 20, 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.

Prerequisites

  • Python 3.10+
  • A clone of the `authdog/web-sdk` repository pinned to the commit you have reviewed. The Python package is currently source-only and is not published on PyPI.
  • An Authdog environment and its public key (pk_…), available in the Authdog console.
  • A frontend that signs users in against the same Authdog environment. The Python SDK validates the session that your frontend establishes — it reads the authdog-session cookie (or an Authorization: Bearer <token> header) and verifies it against the identity provider's userinfo endpoint.

Install from source

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

Choose the extra for your framework:

python -m pip install "./packages/python[flask]"
python -m pip install "./packages/python[django]"

Pin the repository commit in deployment automation. Published compatibility is not yet guaranteed, so evaluate the source and run your integration tests before production use.

Configure the public key

Set your Authdog public key as an environment variable rather than hard-coding it:

export PK_AUTHDOG="pk_..."

The key is validated and parsed once at startup — a malformed key, or one whose identity host is not on the trusted allowlist, raises immediately instead of failing on the first request.

Resolve the session

Create an Authdog instance and use authdog.session as a FastAPI dependency. It reads the token, calls userinfo, and returns a typed AuthdogContext. It never raises — a missing or invalid token simply yields is_authenticated == False.

import os
from fastapi import Depends, FastAPI, Request
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}

The context is:

@dataclass
class AuthdogContext:
    token: str | None = None
    user: Any | None = None
    is_authenticated: bool = False
    user_info: dict | None = None

Protect a route

authdog.require_auth is the real server-side enforcement point. It raises HTTPException(401) for unauthenticated requests and otherwise returns the user object, so a handler can depend on it directly:

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

Every protected route must depend on require_auth. Reading ctx.is_authenticated from session is fine for shaping a response, but it is not a security boundary on its own.

Because the resolved context is cached on request.state, combining session and require_auth on the same request makes at most one outbound userinfo call.

Add a logout handler

authdog.logout(request) returns a RedirectResponse that expires the authdog-session cookie and redirects to the redirect_uri query parameter, sanitized against open redirects:

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

The cookie is cleared with HttpOnly, SameSite=Lax, and Secure when ENV=production.

Skip the userinfo round-trip

For high-throughput services that validate the token elsewhere, disable the lookup:

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

ctx.token is populated but is_authenticated stays False — you own validation.

Next steps