OAuth Integration#

Overview#

Adds OAuth 2.0 support to bfabricPy. The library can now authenticate via PKCE, device code, client credentials, URL tokens, and personal access tokens — in addition to the existing password-based SOAP auth. All OAuth flows are transparent to downstream code: the SOAP engine receives BfabricAuth(login="__oauth__", password=<jwt>) automatically.


New: bfabric._oauth module#

Private module under bfabric/src/bfabric/_oauth/ implementing all OAuth primitives.

File

Purpose

_constants.py

DEFAULT_CLIENT_ID = "bfabric-cli", DEFAULT_OAUTH_SCOPE = "api:read api:write"

credential_provider.py

OAuthCredentialProvider — thread-safe token management with automatic refresh and disk caching. Supports both client_credentials and refresh_token grant types.

pkce.py

pkce_login() — browser-based PKCE flow. Starts a local HTTP server, opens the browser, exchanges the authorization code for tokens.

device_code.py

device_code_login() — RFC 8628 device authorization flow for headless environments.

registration.py

register_client() — RFC 7591 dynamic client registration via HTTP POST.

token_cache.py

TokenCache — JSON file cache at ~/.bfabric/tokens/{hash}.json with 0o600 permissions. compute_token_cache_path() derives a unique path from (base_url, client_id, env_name).

url_token.py

UrlTokenContext + parse_url_token() — extracts entity context (entity_id, application_id, etc.) from B-Fabric URL token JWTs.

webapp_client.py

WebappClient — dual-identity client bundling a user (from URL token) and service (from client credentials) Bfabric instance.


New: Factory methods on Bfabric#

Method

Grant type

Use case

Bfabric.connect()

auto-detect

Unchanged API. Now auto-detects auth_method: "oauth" in config and loads cached tokens.

Bfabric.connect_oauth(client_id, client_secret, base_url)

client_credentials

Service accounts / background jobs.

Bfabric.connect_pkce(base_url, client_id)

authorization_code + PKCE

Interactive browser login (programmatic).

Bfabric.connect_device_code(base_url, client_id)

device_code

Headless interactive login (programmatic).

Bfabric.from_url_token(base_url, jwt, refresh_token)

URL token

Webapps launched from B-Fabric. Returns (Bfabric, UrlTokenContext).

WebappClient.create(base_url, jwt, ..., client_id, client_secret)

URL token + client_credentials

Dual-identity webapp client.


New: CLI commands (bfabric-cli auth)#

All commands registered under bfabric-cli auth via cyclopts.

Command

File

Description

auth pkce <base_url>

cli/login/pkce.py

Browser-based OAuth login. Caches tokens + writes config.

auth device-code <base_url>

cli/login/device_code.py

Headless OAuth login.

auth pat <base_url>

cli/login/pat.py

Personal Access Token login.

auth register <client_name> <redirect_uri>

cli/login/register.py

RFC 7591 dynamic client registration. Outputs JSON.

auth status

cli/login/status.py

Show current auth status for an environment.

auth logout

cli/login/logout.py

Clear cached OAuth tokens.

All auth commands use --config-env (consistent with API commands via @use_client).

auth register enhancements#

  • --config-env reuses a cached OAuth token from an existing environment (no manual bearer token needed)

  • --config-file specifies the config file path

  • base_url is optional when --config-env is provided (inferred from config)

  • Token resolution: explicit --token > cached OAuth token via --config-env > interactive prompt


Config file changes#

New fields in environment config#

PRODUCTION:
  base_url: "https://bfabric.example.com/bfabric"
  auth_method: "oauth"        # NEW — triggers OAuth flow in Bfabric.connect()
  client_id: "bfabric-cli"    # NEW — optional, defaults to "bfabric-cli"

New: config_writer.py#

write_environment_to_config() — creates or updates a YAML environment section with atomic writes (0o600 permissions). Used by all login commands.

ConfigData / EnvironmentConfig#

Both models gained auth_method and client_id fields. Bfabric.connect() checks auth_method == "oauth" to route to _connect_oauth_from_config().


CLI error handling improvements#

  • @use_client decorator now catches ValueError / RuntimeError from Bfabric.connect() and from the wrapped function, printing clean error messages to stderr and exiting with code 1.

  • Removed @logger.catch(reraise=True) from all API commands (create, read, update, delete, inspect) — error handling is now centralized in @use_client.

  • ResultContainer.assert_success() now formats errors inline ("Query was not successful: Insufficient scope...") instead of passing a tuple.


Dependencies#

  • authlib — new dependency (added to bfabric/pyproject.toml)

  • httpx — used by registration.py for RFC 7591 HTTP calls


Test coverage#

~1800 lines of new tests across 15 test files covering:

  • All OAuth flows (PKCE, device code, client credentials, URL token)

  • Credential provider (token refresh, thread safety, disk caching, cache priority)

  • Token cache (save/load/clear, path computation, permissions)

  • Client registration

  • Webapp client

  • All 6 CLI auth commands

  • Config file parsing with new OAuth fields

  • Config writer


Scope enforcement (server-side)#

B-Fabric now enforces OAuth scopes at the API level:

  • api:read required for SOAP read operations

  • api:write required for SOAP write operations

  • Additional scopes (e.g. tus, download) can be requested and are enforced by their respective endpoints

The bfabric-cli default client is pre-registered with: openid, profile, email, api:read, api:write, tus, download, offline_access.


How to install from this branch#

# As a library dependency
pip install "bfabric @ git+https://github.com/fgcz/bfabricPy.git@oauth-integration#subdirectory=bfabric"

# As a CLI tool (both packages from branch)
uv tool install \
  "bfabric-scripts @ git+https://github.com/fgcz/bfabricPy.git@oauth-integration#subdirectory=bfabric_scripts" \
  --with "bfabric @ git+https://github.com/fgcz/bfabricPy.git@oauth-integration#subdirectory=bfabric" \
  --reinstall