Authentication Flows#
OIDC/OAuth 2.0 client side authentication flows.
The module provides three classes arranged in an inheritance hierarchy:
BaseFlow (token lifecycle, persistence, refresh)
|
+-- DeviceFlow (OAuth 2.0 Device Authorization Grant)
+-- CodeFlow (OAuth 2.0 Authorization Code Grant with PKCE)
All flow classes share a process wide token cache via BaseFlow
so that repeated calls reuse existing tokens when possible.
Quick start#
Most users should use the top level authenticate()
function which picks the right flow automatically. The classes in this
module are useful when you need explicit control over the flow.
from py_oidc_auth_client.flows import DeviceFlow
flow = DeviceFlow("https://myapp.example.com")
code = await flow.get_device_code()
print(f"Open {code['uri']} and enter: {code['user_code']}")
token = await flow.poll(code["device_code"], code["interval"])
- class py_oidc_auth_client.flows.BaseFlow(host: str = '', config: Config | None = None, **kwargs: Any)#
Bases:
objectShared token lifecycle, caching, and persistence.
This class is not meant to be instantiated directly. Use
DeviceFloworCodeFlowinstead, or the convenience functionauthenticate().Instances are singletons per host: calling
DeviceFlow("https://a.example.com")twice returns the same object, butDeviceFlow("https://b.example.com")creates a separate instance.Tokens are persisted in a per host JSON store (
TokenStore). Expired entries are evicted automatically on every read.- Parameters:
host (str) – Base URL of the application server.
config (Config or None) – Full configuration object. When provided, host and all keyword arguments are ignored.
store (TokenStore or None) – Custom token store instance. A shared default store is used when
None.login_route (str) – Server path for the authorization code login endpoint.
token_route (str) – Server path for the token exchange endpoint.
device_route (str) – Server path for the device authorization endpoint.
redirect_ports (list of int or None) – Ports to try for the local callback server (code flow only).
timeout (int or None) – HTTP and polling timeout in seconds.
Notes
The per host singleton pattern ensures that only one instance per concrete subclass and host exists. Token state is shared between
DeviceFlowandCodeFlowfor the same host via theTokenStore.Examples
Per host singletons:
a = DeviceFlow("https://host-a.example.com") b = DeviceFlow("https://host-a.example.com") c = DeviceFlow("https://host-b.example.com") assert a is b # same host -> same instance assert a is not c # different host -> different instance
Shared token cache across flows:
device = DeviceFlow("https://myapp.example.com") code = CodeFlow("https://myapp.example.com") token = await device.authenticate() assert code.token is not None # same store, same host
- async refresh(refresh_token: str) Token#
Obtain a new access token using a refresh token.
- Parameters:
refresh_token (str) – The refresh token from a previous authentication.
- Returns:
A fresh token with updated expiry times.
- Return type:
- Raises:
AuthError – If the refresh request fails (e.g. token revoked).
Examples
flow = DeviceFlow("https://myapp.example.com") new_token = await flow.refresh(old_token["refresh_token"])
- classmethod reset_instances() None#
Clear all cached singleton instances.
Primarily useful in tests to ensure a clean state.
Examples
DeviceFlow.reset_instances()
- property session: AsyncClient#
Lazy
httpx.AsyncClientshared across all requests.
- class py_oidc_auth_client.flows.CodeFlow(host: str = '', config: Config | None = None, **kwargs: Any)#
Bases:
BaseFlowOAuth 2.0 Authorization Code Grant with PKCE.
Opens the provider’s login page in a local browser, starts a temporary HTTP server to capture the callback, and exchanges the authorization code for tokens.
Best suited for desktop and local development environments where a browser is available on the same machine.
- Parameters:
host (str) – Base URL of the application server.
config (Config or None) – Full configuration object. Overrides host and keyword arguments.
store (TokenStore or None) – Custom token store. Shared default when
None.timeout (int or None) – Maximum seconds to wait for the user to complete the browser login.
Examples
from py_oidc_auth_client.flows import CodeFlow flow = CodeFlow("https://myapp.example.com", timeout=120) token = await flow.authenticate() print(token["access_token"][:20], "...")
See also
DeviceFlowHeadless/CLI device authorization flow.
authenticateTop level convenience function.
- async authenticate(*, force: bool = False) Token#
Run the authorization code flow and return a token.
Checks cached tokens first. Falls back to a browser based login when no valid tokens are available.
- Parameters:
force (bool) – Skip the cache and force a new interactive login.
- Returns:
The authentication token.
- Return type:
- Raises:
AuthError – If the login times out or the code exchange fails.
Examples
flow = CodeFlow("https://myapp.example.com") token = await flow.authenticate()
- class py_oidc_auth_client.flows.DeviceFlow(host: str = '', config: Config | None = None, **kwargs: Any)#
Bases:
BaseFlowOAuth 2.0 Device Authorization Grant (RFC 8628).
Best suited for CLI tools, headless servers, CI jobs, and environments where opening a local browser is not possible. The user visits a URL on any device, enters a short code, and the client polls until approval.
- Parameters:
host (str) – Base URL of the application server.
config (Config or None) – Full configuration object. Overrides host and keyword arguments.
store (TokenStore or None) – Custom token store. Shared default when
None.timeout (int or None) – Maximum seconds to wait for user approval during polling.
interactive (bool or None) – Whether to show a spinner and attempt to open the browser.
Noneauto detects based on the terminal environment.
Examples
Fully automatic login (opens browser, polls, returns token):
from py_oidc_auth_client.flows import DeviceFlow flow = DeviceFlow("https://myapp.example.com", timeout=120) token = await flow.authenticate() print(token["access_token"][:20], "...")
Step by step control:
flow = DeviceFlow("https://myapp.example.com") # Step 1: get device code code = await flow.get_device_code() print(f"Open {code['uri']}") print(f"Enter code: {code['user_code']}") # Step 2: poll until user approves token = await flow.poll( device_code=code["device_code"], interval=code["interval"], )
Reusing a cached token:
flow = DeviceFlow("https://myapp.example.com") if flow.token: print("Already authenticated!") else: token = await flow.authenticate()
See also
CodeFlowBrowser based authorization code flow.
authenticateTop level convenience function.
- async authenticate(*, force: bool = False, auto_open: bool = True) Token#
Run the full device flow and return a token.
Checks cached tokens first. When force is
Falseand a valid access token exists, it is returned immediately. When only the refresh token is still valid, a refresh is attempted before falling back to a full device flow.- Parameters:
force (bool) – Skip the cache and force a new interactive login.
auto_open (bool) – Attempt to open the verification URL in the default browser.
- Returns:
The authentication token.
- Return type:
- Raises:
AuthError – If the flow fails, times out, or the user denies access.
Examples
flow = DeviceFlow("https://myapp.example.com") token = await flow.authenticate()
- async get_device_code() DeviceCode#
Request a new device code from the authorization server.
- Returns:
Contains
uri,user_code,device_code, andinterval. Displayurianduser_codeto the user, then callpoll()withdevice_codeandinterval.- Return type:
- Raises:
AuthError – If the device authorization endpoint is unavailable or returns an invalid response.
Examples
flow = DeviceFlow("https://myapp.example.com") code = await flow.get_device_code() print(f"Go to: {code['uri']}") print(f"Code: {code['user_code']}")
- async poll(device_code: str, interval: int = 5) Token#
Poll the token endpoint until the user approves or denies.
- Parameters:
device_code (str) – The
device_codeobtained fromget_device_code().interval (int) – Minimum seconds between poll requests. Use the value from
get_device_code()to respect the provider’s rate limit.
- Returns:
The authentication token after user approval.
- Return type:
- Raises:
AuthError – On timeout, denial (
access_denied), or token expiry.
Examples
code = await flow.get_device_code() # ... show code to user ... token = await flow.poll( code["device_code"], code["interval"], )