enlace_auth.auth.oauth#

OAuth2/OIDC login via Authlib.

Lazy import — authlib lives behind the enlace[oauth] extra. Providers are configured in platform.toml under [auth.oauth.{name}] with client_id_env / client_secret_env pointing at env vars (secrets never in TOML). On callback we create a local session — the upstream tokens are discarded because we use OAuth for identity only, not API access.

Two rules keep an OAuth login from being weaker than the account it opens:

  • The anti-CSRF state lives in a signed cookie scoped to /auth (_oauth_state_session()). Authlib keeps the state, nonce and PKCE verifier in request.session; the plugin installs no Starlette SessionMiddleware, so this module supplies that session itself. A callback whose state was not issued to this browser is refused.

  • An identity is bound to the provider’s stable subject (sub, or tid/oid for Microsoft, GitHub’s numeric id), recorded as oauth_links[provider] on the account. A later login must present the same subject. An existing password account, or one linked to another provider, is never taken over by an email match alone.

Residual limits, by design: the state cookie is signed but not bound to the browser, so a script that can set cookies on the platform origin (any co-hosted app) could plant its own state for a browser that has none and so log that browser in as the attacker (two cookies of the name are refused). An account created by a provider before links existed is bound to the first subject that signs in to it after the upgrade. A password reset (admin, emailed link, CLI) unlinks every external sign-in. The cookie path assumes the router is mounted at /auth with no root path.

Built-in provider presets for Google and GitHub auto-fill the well-known endpoints; other providers need explicit URLs in the config.

Functions

make_oauth_router(*, providers, ...[, ...])

Build an OAuth router or return None if no providers are configured.

enlace_auth.auth.oauth.make_oauth_router(*, providers, session_store, user_store, signing_key, cookie_name='enlace_session', session_max_age=86400, secure_cookies=True, can_register=<function <lambda>>, admin_emails=(), state_cookie_name='enlace_oauth_state', state_max_age=600)[source]#

Build an OAuth router or return None if no providers are configured.

state_cookie_name / state_max_age name and bound the signed cookie that carries Authlib’s per-login state between /auth/login/{provider} and the callback (see the module docstring). It is only used when no Starlette SessionMiddleware already provides request.session.

admin_emails may create their account by a first sign-in, whatever can_register says, but only when the provider itself verified the address (_email_verified_by_provider()). That is the one self-service way to set up an admin who never uses a password: /auth/register cannot prove ownership and refuses admin addresses (i2mint/enlace_auth#38).

Return type:

Optional[APIRouter]