Metadata-Version: 2.4
Name: endevre-id-client
Version: 0.1.1.dev20260726080123
Summary: Python client for Endevre ID authentication flows
Author-email: Endevre <dev@endevre.com>
License: MIT
Project-URL: Homepage, https://github.com/endevre/id-client-auth
Project-URL: Repository, https://github.com/endevre/id-client-auth
Keywords: endevre,authentication,sso,oauth,jwt
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: requests<3,>=2.31
Requires-Dist: pyjwt[crypto]<3,>=2.8
Provides-Extra: dev
Requires-Dist: pytest<9,>=8; extra == "dev"
Requires-Dist: pytest-httpserver>=1.0.8; extra == "dev"

# Endevre ID Python Client

A Python helper library for interacting with Endevre ID authentication services. It mirrors the core workflows of the browser client with a server-friendly API.

## Features

- Build secure login URLs for Endevre ID SSO flows.
- Exchange authorization codes for tokens (optionally requesting JWTs).
- Refresh access tokens using stored refresh tokens.
- Retrieve user information from tokens or the Endevre ID API.
- Optional JWT validation backed by the public signing key.
- Standards-based OIDC access-token validation from discovery and rotating JWKS.
- Explicit `legacy`, `dual`, and `oidc` migration modes with no silent downgrade.

## Installation

```bash
pip install endevre-id-client
```

## Quick start

```python
from endevre_id_client import EndevreClient

client = EndevreClient(
    client_id="<your client id>",
    client_secret="<your client secret>",
)

login_url = client.getLoginUrl(
    final_redirect_uri="https://app.example.com/auth/callback",
    redirect_uris=["https://app.example.com/start"],
)

# After your user completes the flow and you receive the `code`
exchange_result = client.exchange(code)
user_info = client.getUserInfo(exchange_result.token)
refreshed = client.refreshToken(exchange_result.refresh)
```

## OIDC resource-server validation

OIDC support is additive. Existing construction remains in `legacy` mode by
default. A resource server can opt into OIDC without changing the application
user identifier:

```python
from endevre_id_client import EndevreClient

client = EndevreClient(
    client_id="<your client id>",
    protocol="oidc",
    oidc_issuer="https://id.endevre.com",
)

claims = client.validateToken(bearer_token)
application_user_id = claims["sub"]
assert claims["userId"] == application_user_id
```

The OIDC `sub` is the same client-specific identifier issued by the legacy
Endevre flow. `userId` is included as a compatibility alias. Validation
requires an `at+jwt` RS256 access token and verifies its signature, issuer,
audience, expiry, `client_id`, `jti`, and required claims. Discovery is cached
for one hour and JWKS for five minutes by default; an unknown key ID or
signature change triggers one bounded refresh so signing-key rotation does not
require an application deployment.

During a staged migration, configure `protocol="dual"` and retain the client
secret:

```python
client = EndevreClient(
    client_id="<your client id>",
    client_secret="<your legacy client secret>",
    protocol="dual",
    oidc_issuer="https://id.endevre.com",
)
```

Dual mode routes tokens by their signed-token shape and exact issuer. An OIDC
validation error never falls back to legacy validation. Legacy is still the
default only for compatibility while consumers migrate.

For Worker 3 qualification, change only the explicit issuer:

```python
client = EndevreClient(
    client_id="<your development client id>",
    protocol="oidc",
    oidc_issuer="https://worker3.id.endevre.com",
)
```

`getUserInfo()` validates OIDC locally first and then calls the discovered
UserInfo endpoint. It rejects a UserInfo response whose `sub` differs from the
validated access token.

### Deliberate API boundary

This release adds resource-server validation; it does not add a second
authorization-code client implementation. In `oidc` mode, the legacy
`getLoginUrl()`, `exchange()`, and `refreshToken()` methods fail closed. Web
applications should use the Endevre JavaScript SDK or another conformant OIDC
relying-party library for the browser authorization-code flow. In `legacy` and
`dual` modes those existing methods keep their current behavior during the
migration window.

## Publishing

Automated publishing mirrors the JavaScript package strategy:

- Pushes to `main` publish the version defined in `pyproject.toml` as the stable release.
- Pushes to `beta` publish a time-stamped beta pre-release (e.g. `0.1.0b20241006123000`).
- Pushes to `dev` publish a time-stamped development build (e.g. `0.1.0.dev20241006123000`).
- Pushes to `justin*` / `ray*` branches run tests only (no publish).

All workflows live under `.github/workflows/python-ci-cd.yml` and rely on the `PYPI_API_TOKEN` secret (or PyPI Trusted Publisher configuration) for auth.

## Development

Install dev dependencies:

```bash
pip install -e .[dev]
```

Run the test suite:

```bash
pytest
```
