altium-auth (Python)
Altium 365 OAuth2 / OpenID Connect authentication for Python. Supports both client types across Commercial Cloud, GovCloud, and AES (on-prem):
- Public clients (desktop, native) — browser sign-in with PKCE over Altium's ActionWait long-poll:
AltiumAuthClient.sign_in. - Confidential clients (web/server backends) — the standard authorization-code redirect flow:
create_authorization_url+exchange_code. - Workspace tokens, refresh, revocation, and first-class GovCloud + AES support.
Zero runtime dependencies (stdlib only). Python 3.10+.
Documentation
The library implements the protocol described in the language-neutral guides (start here if you're new to Altium Identity):
- Authentication overview
- Register your application
- Web / server apps
- Desktop apps
- GovCloud
- AES (on-prem)
- Access token claims
Installation
pip install altium-auth
Quick start
Construct an AltiumAuthClient once with an AltiumAuthConfig. Only client_id and scopes are required — endpoints default to the Commercial Cloud.
Public apps (desktop — ActionWait sign-in)
from altium_auth import AltiumAuthClient, AltiumAuthConfig
client = AltiumAuthClient(AltiumAuthConfig(client_id="your-client-id", scopes="openid profile"))
# Opens a browser login page and waits for the callback.
tokens = client.sign_in()
# Persist `tokens` yourself — a keyring, an OS credential store, or a file.
workspace = client.sign_into_workspace(tokens.access_token, "workspace-id-here")
Provide a custom browser opener (e.g. an IDE/host bridge) via config; the library still owns PKCE, ActionWait polling, state correlation, CSRF validation, and the token exchange:
config = AltiumAuthConfig(
client_id="your-client-id",
scopes="openid profile",
open_browser=lambda url: my_host.open_external(url),
)
Confidential apps (web / server — authorization-code redirect)
from altium_auth import AltiumAuthClient, AltiumAuthConfig
client = AltiumAuthClient(
AltiumAuthConfig(
client_id="your-client-id",
client_secret="your-client-secret", # confidential client → HTTP Basic
scopes="openid profile offline_access",
)
)
redirect_uri = "https://my-service.example.com/oauth/callback"
# On your login route: build the URL, stash state + verifier, then redirect.
req = client.create_authorization_url(redirect_uri=redirect_uri)
session["oauth"] = {"state": req.state, "code_verifier": req.code_verifier}
# redirect(req.url)
# On your callback route: verify state, then exchange the code.
if request.args["state"] != session["oauth"]["state"]:
raise ValueError("state mismatch")
tokens = client.exchange_code(
request.args["code"],
code_verifier=session["oauth"]["code_verifier"],
redirect_uri=redirect_uri,
)
GovCloud
from altium_auth import AltiumAuthClient, AltiumAuthConfig, GOV_CLOUD_ENDPOINTS
client = AltiumAuthClient(
AltiumAuthConfig(
client_id="your-gov-client-id",
scopes="openid profile",
endpoints=GOV_CLOUD_ENDPOINTS,
)
)
tokens = client.sign_in() # secure=1 is added to token requests automatically
AES (on-prem)
from altium_auth import AltiumAuthClient, AltiumAuthConfig, aes_endpoints
endpoints = aes_endpoints("https://aes.server.example:9785")
scopes = AltiumAuthClient.get_client_scopes(endpoints.scope_endpoint, "your-aes-client-id")
client = AltiumAuthClient(
AltiumAuthConfig(
client_id="your-aes-client-id",
scopes=" ".join(scopes),
endpoints=endpoints,
)
)
tokens = client.sign_in()
Using from async code
The library is synchronous. From an event loop, bridge with the stdlib:
import asyncio
tokens = await asyncio.to_thread(client.refresh_token, refresh_token)
API reference
| Method | Description |
|---|---|
create_authorization_url(*, redirect_uri=None, state=None, code_verifier=None, scopes=None, select_workspace=WorkspaceSelection.NONE) |
Build a PKCE authorization URL (no I/O). Returns AuthorizationRequest(url, state, code_verifier). |
exchange_code(code, *, code_verifier=None, redirect_uri=None, timeout=30.0) |
Authorization-code grant → TokenSet. |
sign_in(*, select_workspace=WorkspaceSelection.NONE, timeout=180.0) |
Full ActionWait sign-in → TokenSet. |
sign_into_workspace(base_access_token, workspace_auth_id, *, timeout=30.0) |
RFC 8693 workspace exchange → TokenSet. |
refresh_token(refresh_token, *, timeout=30.0) |
Refresh grant (no scope resent) → TokenSet. |
revoke_refresh_token(refresh_token, *, timeout=30.0) |
RFC 7009 revocation (idempotent). |
AltiumAuthClient.get_client_scopes(scope_endpoint, client_id, *, timeout=30.0) |
Static; scope introspection → list[str]. |
Types
AltiumAuthConfig(client_id, scopes, client_secret=None, endpoints=COMMERCIAL_CLOUD_ENDPOINTS, secure=None, open_browser=None)— propertiesis_confidential,use_secure.AltiumEndpoints(authorize_endpoint, token_endpoint, action_wait_endpoint, redirect_uri, scope_endpoint=None)— constantsCOMMERCIAL_CLOUD_ENDPOINTS,GOV_CLOUD_ENDPOINTS; factoryaes_endpoints(origin).TokenSet(access_token, token_type, expires_in, expires_at, refresh_token, id_token, scope)—expires_atcomputed with a 30 s clock-skew buffer.WorkspaceSelection—NONE/STRICT/OPTIONAL.
access_tokenis a signed JWT — decode it to readiss,workspaceId,secure, and scopes. See Access token claims.
Error handling
All errors subclass AltiumAuthError:
| Class | When |
|---|---|
ConfigurationError |
Missing client_id/scopes, invalid endpoint URL, or empty required argument. |
OAuthError |
Token/revocation/scope endpoint returned a non-success status (carries .status, .error, .error_description). |
ActionWaitError |
ActionWait timed out, was cancelled (410), or returned an unusable body. |
StateMismatchError |
Returned state ≠ wait token (CSRF guard). Subclass of ActionWaitError. |
TransportError |
Network-level failure. |
Development
uv sync --extra dev
uv run pytest # unit + conformance
uv run ruff check .
uv run mypy
uv build
Live E2E sign-in
uv run python tools/signin_test.py YOUR_CLIENT_ID
uv run python tools/signin_test.py --env gov YOUR_GOV_CLIENT_ID
uv run python tools/signin_test.py --env aes --aes-origin https://aes.server.example:9785 YOUR_AES_CLIENT_ID
Security
Report vulnerabilities privately — see SECURITY.md.
License
MIT © Altium Limited
Release files for altium-auth 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| altium_auth-0.2.0.tar.gz | 11.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| altium_auth-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 26.0 kB
Release files / altium_auth-0.2.0.tar.gz
| Download URL | altium_auth-0.2.0.tar.gz |
|---|---|
| Size | 11.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9218a1ac92f8b69d60440a7e89b21ae43e1013891225b7396b20b3e410751ccd
|
|
BLAKE2b-256 checksum How to use checksums |
9f81e98a932e5910fd08d8ed6b841c0c96e25aab87463686c675afe5d60e73da
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.
Transparency logRelease files / altium_auth-0.2.0-py3-none-any.whl
| Download URL | altium_auth-0.2.0-py3-none-any.whl |
|---|---|
| Size | 14.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3923941c88f1d6d021f836f9ffd0c05c57589bf1ee5b133f564f66a8c2995654
|
|
BLAKE2b-256 checksum How to use checksums |
35558fa445e852bb29215ff8cbe219a439730153ba11299488ff6dd74aa419bd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.
Transparency log