Python ยท Authentication

Basic authentication

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.

app.py
from authdog.fastapi import Authdog
ย 
authdog = Authdog(
public_key=os.environ["PK_AUTHDOG"]
)
ย 
@app.get("/me")
async def me(user):
return {"id": user.sub}

Prerequisites

  • Python 3.10+
  • 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

pip install authdog-fastapi

Installing an extra pulls in the binding for your framework:

pip install "authdog-fastapi[flask]"    # Flask
pip install "authdog-fastapi[django]"   # Django

fastapi and httpx are the only runtime dependencies.

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