Add AuthPI login to Python backends with authpi-idp — the OIDC authorization-code flow with PKCE, token refresh, and per-organization authorization checks.
Last updated 2026-06-13
authpi-idp is the official Python SDK for authenticating users against your AuthPI issuer: it drives the OIDC authorization-code flow with PKCE, exchanges and refreshes tokens, and gives you an authenticated agent object with the user’s identity and per-organization permissions.
Requirements: Python 3.11+. All token operations are async.
pip install authpi-idp
from authpi_idp import IdpClient
idp = IdpClient(
issuer_url="https://idp.authpi.com/i_4r8w2k9m5x1p7q3e6t0y2u4i8",
client_id="cli_xxx",
client_secret="...", # omit for public clients (SPAs, native apps)
redirect_uri="https://app.example.com/callback",
)
1. Send the user to AuthPI. create_authorization_url is synchronous and generates the PKCE verifier, state, and nonce for you — store them in the user’s session, then redirect:
auth = idp.create_authorization_url(scopes=["openid", "profile", "email"])
session["oauth"] = {
"code_verifier": auth.code_verifier,
"state": auth.state,
"nonce": auth.nonce,
}
return redirect(auth.url)
2. Handle the callback. After checking that the returned state matches the one you stored, exchange the code for an authenticated agent. The exchange goes directly to the token endpoint over TLS, and PKCE binds it to the verifier from step 1:
agent = await idp.exchange_code(code, session["oauth"]["code_verifier"])
session["tokens"] = agent.tokens.model_dump()
3. Use the agent. It carries the user’s identity and organization memberships, with an authorization helper that checks scopes within an organization:
if agent.has_access_in("org_0kfz3m8q1w5e9r2t6y4u7i3o5", "write", "projects"):
... # the user can write to projects in that organization
Rebuild an agent from stored tokens on subsequent requests — create_agent refreshes expired access tokens automatically and hands you the rotated tokens through on_refresh:
agent = await idp.create_agent(
session["tokens"],
on_refresh=lambda new_tokens: session.update({"tokens": new_tokens}),
on_refresh_error=lambda error: handle_session_expired(error),
)
Refresh tokens rotate on use, so always persist what on_refresh gives you. Organization claims in refreshed tokens are recomputed at every refresh — membership changes propagate within the access-token TTL (the propagation contract).
agent.tokens