Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/oidc-device-flow-release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The SDK does **OIDC discovery** against your issuer. From `{issuer}/.well-known/
- Supply ValidMind with values that match what the **ValidMind platform** already trusts for your organization:
- **Issuer URL** — Normalized OpenID Provider issuer (example Entra v2: `https://login.microsoftonline.com/<tenant-id>/v2.0`).
- **Client ID** — The application (client) ID of the public OAuth client.
- **Scopes** — The library defaults to `openid profile email`. Your IdP may require **additional scopes** or a **resource identifier** so the access token is acceptable to the ValidMind API.
- **Scopes** — The library defaults to `openid profile email offline_access` so OIDC providers can issue refresh tokens. Your IdP may require **additional scopes** or a **resource identifier** so the access token is acceptable to the ValidMind API.
- **Audience / API identifier (often required)** — Access tokens must pass ValidMind’s JWT validation (issuer, audience, signing keys). Many setups need an explicit **audience** for API-style access tokens (e.g. Auth0 API Identifier, or Azure AD custom scope / resource). Pass this as the `audience` argument (or set env `VM_OIDC_AUDIENCE`) so the provider issues tokens ValidMind can verify.

**Operational checks**
Expand Down Expand Up @@ -56,7 +56,7 @@ vm.init(
client_id="<oauth-public-client-id>",
model="<model-cuid>",
api_host="https://.../api/v1/tracking/", # or api_url= (alias); defaults from VM_API_HOST if unset
scope="openid profile email", # optional; this is the default if omitted
scope="openid profile email offline_access", # optional; this is the default if omitted
audience="<resource-or-api-identifier>", # optional; often required for API tokens; or VM_OIDC_AUDIENCE
)
```
Expand Down Expand Up @@ -101,7 +101,7 @@ Org and model access are enforced server-side: the user must be allowed to use t
| Client ID | Public OAuth client for device flow |
| Model CUID | Identifies the inventory model (`model=` or `VM_API_MODEL`) |
| API host / URL | Tracking API base (`api_host`, `api_url`, or `VM_API_HOST`) |
| Scope | OAuth scopes (default `openid profile email`) |
| Scope | OAuth scopes (default `openid profile email offline_access`) |
| Audience | Often needed so access tokens target the ValidMind API (`audience` or `VM_OIDC_AUDIENCE`) |
| Credential file | `~/.validmind/credentials.json` (cached tokens; restrict like other secrets on shared machines) |

Expand Down
35 changes: 35 additions & 0 deletions tests/test_api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,41 @@ def test_init_oidc_passes_audience(self, mock_obtain, mock_ping):
assert ctx is not None
self.assertEqual(ctx["audience"], "https://api.example.com")

@patch("validmind.api_client._ping")
@patch("validmind.api_client._obtain_oidc_tokens")
def test_init_oidc_uses_default_scope_with_offline_access(
self, mock_obtain, mock_ping
):
mock_obtain.return_value = {
"issuer": "https://issuer/",
"client_id": "cid",
"access_token": "tok",
"expires_at": "2099-01-01T00:00:00+00:00",
"refresh_token": "refresh-token",
"id_token": None,
}

with patch.dict(os.environ, {"VM_OIDC_SCOPE": ""}):
api_client.init(
model="model-cuid",
api_host="http://localhost/track/",
api_key="",
api_secret="",
issuer="https://issuer/",
client_id="cid",
document="documentation",
)

mock_obtain.assert_called_once_with(
"https://issuer/",
"cid",
"openid profile email offline_access",
audience=None,
)
ctx = api_client._oidc_login_context
assert ctx is not None
self.assertEqual(ctx["scope"], "openid profile email offline_access")

@patch("validmind.api_client._ping")
@patch("validmind.api_client._obtain_oidc_tokens")
def test_init_oidc_uses_env_config(self, mock_obtain, mock_ping):
Expand Down
4 changes: 2 additions & 2 deletions validmind/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ def init(
Can be set via env ``VM_OIDC_ISSUER``.
client_id (str, optional): OAuth public client id for device flow. Can be
set via env ``VM_OIDC_CLIENT_ID``.
scope (str, optional): OAuth scopes (default ``openid profile email``).
scope (str, optional): OAuth scopes (default ``openid profile email offline_access``).
Can be set via env ``VM_OIDC_SCOPE``.
audience (str, optional): Resource / API identifier for the access token
(e.g. Auth0 API Identifier). Use the same value the ValidMind backend
Expand Down Expand Up @@ -392,7 +392,7 @@ def init(
_api_key = None
_api_secret = None
_api_host = resolved_host
scope_val = oidc_scope or "openid profile email"
scope_val = oidc_scope or "openid profile email offline_access"
from .credentials_store import normalize_audience

oidc_audience_val = normalize_audience(
Expand Down
Loading