py-oidc-auth-client logo

Typed client library for the authentication routes exposed by py-oidc-auth.

https://img.shields.io/badge/License-BSD-purple.svg https://readthedocs.org/projects/py-oidc-auth-client/badge/?version=latest https://codecov.io/gh/freva-org/py-oidc-auth-client/graph/badge.svg?token=9JP9UWixaf PyPI version Supported Python versions

py-oidc-auth-client is a small Python client that authenticates against the routes provided by the companion server side library py-oidc-auth. It helps applications and scripts obtain and refresh access tokens against an auth server that exposes the standard routes (login, token, device).

Key features#

  • A simple high level helper py_oidc_auth_client.authenticate()

  • Authorization code flow with a local browser callback

  • Device flow for headless sessions

  • Persistent identity-aware token storage with py_oidc_auth_client.TokenStore

  • Token caching and refresh token support

  • Fully typed public API

When to use this library#

Use py-oidc-auth-client when you need to:

  • call a service protected by bearer tokens issued by your auth server

  • perform interactive login in a local session

  • run in a headless environment (batch job, remote shell) and still obtain tokens

  • reuse cached or refreshed tokens instead of re-authenticating every time

Quick start#

High level authenticate function.

The high level helper performs the best available strategy:

  1. Use a cached token if it is still valid.

  2. Refresh an access token if a refresh token is available.

  3. Fall back to an interactive flow (browser or device) if possible.

from py_oidc_auth_client import authenticate

token = authenticate(host="https://auth.example.org")
headers = token["headers"]
Device flow

Directly use device flow logins without fall back to code flow:

import asyncio
from py_oidc_auth_client import Config, DeviceFlow

async def main():
   flow = DeviceFlow(config=Config(host="https://auth.example.org"))
   device = await flow.get_device_code()
   print(device["uri"])
   print(device["user_code"])
   token = await flow.poll(device.device_code, device.interval)
   print(token["headers"])

asyncio.run(main())
Code flow

Use code flow for IDP’s that do not support/allow device flow:

import asyncio
from py_oidc_auth_client import Config, CodeFlow

async def main():
   flow = CodeFlow(config=Config(host="https://auth.example.org"))
   token = await flow.authenticate()
   print(token["headers"])

asyncio.run(main())
Token storage with TokenStore

A single TokenStore can safely hold tokens for multiple hosts, and for several identities on the same host, because entries are keyed by an py_oidc_auth_client.AuthIdentity: the host together with the grant, client, scopes and audience that produced the token. You never build an identity yourself; each flow derives its own.

Pass app_name to keep your tokens in their own directory under the platform cache (~/.cache/my-app/tokens on Linux):

from py_oidc_auth_client import TokenStore, authenticate

store = TokenStore(app_name="my-app")
token = authenticate(
    host="https://auth.example.org",
    store=store,
)
print(token["headers"])

Pass path instead to put the store somewhere specific, for example on a shared filesystem or inside a container volume. The same store serves any number of hosts:

store = TokenStore(path="/srv/jobs/.auth/tokens")

staging = authenticate(host="https://staging.example.org", store=store)
prod = authenticate(host="https://auth.example.org", store=store)

Each entry is written to its own file, so several processes authenticating at the same time do not overwrite one another. That matters for batch jobs that start many workers at once.

Inspecting and clearing cached tokens

entries() returns what is cached, each with the identity that produced it. label is a one line summary of that identity, and is what the command line --list prints:

store = TokenStore(app_name="my-app")

for entry in store.entries():
    print(entry.identity.label, entry.usefulness())

find() looks entries up by partial identity, which is how you ask “is there a token here I could reuse?” without knowing which flow produced it:

from py_oidc_auth_client import Grant

matches = store.find(
    host="https://auth.example.org",
    grants=[Grant.DEVICE_CODE, Grant.AUTHORIZATION_CODE],
    scopes=["openid"],
)
if matches:
    token = matches[0].token

Matches come back best first: a live access token before one that only has a refresh token left. Scopes match on a superset, so a token granted more than you asked for is still a hit.

Removing a host removes every token for that server, including any obtained from it by token exchange:

store.remove("https://auth.example.org")

The same from the command line, where --list groups by host and shows the grant, client, scopes and remaining lifetime of each entry:

$ python -m py_oidc_auth_client --list
$ python -m py_oidc_auth_client --remove https://auth.example.org

Guides and reference#

Guides

API reference