Authdog
Log In

Developers guide

Python Integration

The Python bindings are source you pin and review, because authdog-fastapi is not on PyPI.

This guide installs that source and wires it into FastAPI, Django, Flask, Starlette, or aiohttp, with one rule: require_auth on anything that must not be public, and a 401 when the token is missing.

Prerequisites

  • Python 3.10 or newer.
  • An Authdog environment and its publishable public key (pk_...).
  • A commit or a reviewed snapshot of packages/python. Pin it. Do not float on main in production.
  • A client that can obtain a session for that same environment. A token from another environment will look like "every request is anonymous."

The package depends on httpx. Extras cover FastAPI 0.110, Django 4.2, Flask 3.0, Starlette 0.37, and aiohttp 3.9. There is no registry release, so there is no version solve and no provenance check from pip. The pin is the control.

Implementation

1. Build the client with the public key

Each framework module exports Authdog. Construct it with the environment's public key:

import os
from authdog.fastapi import Authdog

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

The core looks for authdog-session first, then Authorization: Bearer <token>. It calls that environment's OIDC userinfo endpoint and returns an AuthdogContext. If userinfo fails, you get an anonymous context. You do not get a half-authenticated user.

2. Use `session` to render, and `require_auth` to protect

session is for pages where anonymous and signed-in are both valid responses. It tells you which one you got:

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

Hit / with no token. is_authenticated should be false, and the handler should still run.

require_auth is the gate. It rejects an unauthenticated request with 401:

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

Hit /me with no token and expect 401. Hit it with a valid session and expect the user. A missing token, a bad token, or a failed userinfo call must not authenticate the request. If /me returns 200 for an empty header, the route is using session.

3. Call it the way that framework expects

Django takes authdog.session(request) and @authdog.require_auth. Flask takes authdog.session() and @authdog.require_auth. Starlette takes await authdog.session(request) and await authdog.require_auth(request). aiohttp takes await authdog.session(request) and @authdog.require_auth.

The logout helpers clear the local cookie and redirect the way that framework usually does. They do not revoke a bearer token, and they do not end the session at the upstream provider. After logout, call a protected route. You want 401 from the missing cookie. A bearer token you issued some other way can still be valid until it expires or you revoke it.

Security considerations

  • session describes the request. require_auth rejects it. Every protected route uses the second one.
  • After require_auth, check role, permission, or ownership. Missing authentication is 401. Authenticated and not allowed is 403.
  • fetch_user=False is not a shortcut for authentication. It leaves you holding an unverified token with is_authenticated false. Do not treat that token as a user.
  • Keep the public key in configuration. Hard-coding it is how a development key ends up in production.
  • Do not log cookies or bearer tokens.

Validation checklist

  • Python runtime is 3.10 or newer.
  • Integration source is reviewed and pinned to a commit.
  • No deployment step expects authdog-fastapi from PyPI.
  • Anonymous route returns is_authenticated == False for missing or invalid tokens.
  • Protected route returns 401 without a validated user.
  • Authorization failure returns 403 after authentication.
  • Logs redact cookies and bearer tokens.

Troubleshooting

pip cannot find authdog-fastapi. There is no PyPI release. Point the build at the pinned source snapshot. pip install authdog is a different package, the management API client, and it will not give you authdog.fastapi.

Every request is anonymous. The client is not sending authdog-session or a bearer token, or the token is for another environment, or userinfo is unreachable from this process. Fix the header and the network path before you change the dependency.

The route has a context and is still public. It depends on session. Switch that route to require_auth and repeat the unauthenticated request. You want 401.

Next steps

  • Python authentication for the install and the session gate in more detail.
  • Python access control after you confirm the claim names in your environment.
  • Backend requests for the same validation model in other stacks.
  • Watch the source repository for a real authdog-fastapi release before you switch this pin to a registry install.