Metadata-Version: 2.5
Name: mastercard-oauth2-client
Version: 1.0.0
Summary: Mastercard OAuth 2.0 client authentication for Python
Author: Mastercard
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: authentication,dpop,fapi2,jwt,mastercard,oauth2,security
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: cryptography>=45.0.1
Provides-Extra: aiohttp
Requires-Dist: aiohttp<4,>=3.13; extra == 'aiohttp'
Provides-Extra: httpx
Requires-Dist: httpx<1,>=0.28; extra == 'httpx'
Provides-Extra: requests
Requires-Dist: requests<3,>=2.33; extra == 'requests'
Description-Content-Type: text/markdown

# OAuth2 Client Python

<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://developer.mastercard.com/_/_/src/global/assets/svg/mcdev-logo-light.svg">
  <img src="https://developer.mastercard.com/_/_/src/global/assets/svg/mcdev-logo-dark.svg" alt="Mastercard Developers">
</picture>

## Overview

Authenticate requests to Mastercard APIs with OAuth 2.0, FAPI 2.0 and DPoP.
The library obtains and reuses access tokens, adds the required authentication
headers and works with supported native and OpenAPI-generated HTTP clients.

For more information, see
[Using OAuth 2.0 to Access Mastercard APIs](https://mstr.cd/43CuHBY).

## Requirements

### License

[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/Mastercard/oauth2-client-python/blob/main/LICENSE)

This project is licensed under the Apache License 2.0.

### Python

| Versions | Status |
|---|---|
| 3.11, 3.12, 3.13, 3.14 | [![Python](https://github.com/Mastercard/oauth2-client-python/actions/workflows/ci-python.yml/badge.svg)](https://github.com/Mastercard/oauth2-client-python/actions/workflows/ci-python.yml) |

Python 3.11 or newer is required.

### Dependencies

The core package requires only `cryptography`. Optional integrations are
available through extras shown with each supported client below.

## Usage

### Installation

Install the core package:

```bash
python -m pip install mastercard-oauth2-client
```

For HTTPX:

```bash
python -m pip install "mastercard-oauth2-client[httpx]"
```

For aiohttp:

```bash
python -m pip install "mastercard-oauth2-client[aiohttp]"
```

For Requests:

```bash
python -m pip install "mastercard-oauth2-client[requests]"
```

### Configuration

`OAuth2Config` contains the client credentials, token endpoint, issuer, scopes
and DPoP key configuration. Load your client signing key from PEM, DER or
PKCS#12. Load your DPoP key pair from a private JWK, or generate one for
sandbox testing:

```python
from mastercard_oauth2_client import (
    OAuth2Config,
    StaticDPoPKeyProvider,
    StaticScopeResolver,
    generate_ec_key_pair,
    load_jwk_key_pair,
    load_private_key,
)

client_key = load_private_key("path/to/client-private-key.pem")

# Generate a DPoP key pair for sandbox testing:
dpop_key_pair = generate_ec_key_pair()

# Or load an existing detached DPoP key pair:
# dpop_key_pair = load_jwk_key_pair("path/to/dpop-private-key.json")

oauth2_config = OAuth2Config(
    client_id="your-client-id",
    token_endpoint="https://sandbox.api.mastercard.com/oauth/token",
    issuer="https://sandbox.api.mastercard.com",
    client_key=client_key,
    key_id="your-signing-key-id",
    scope_resolver=StaticScopeResolver({"your-api:scope"}),
    dpop_key_provider=StaticDPoPKeyProvider(dpop_key_pair),
)
```

Replace every example value with the credentials and scopes provisioned for
your Mastercard API project. For custom scope resolution, DPoP key management
and token storage, see [Extension Points](#extension-points).

### Low-Level API

[`OAuth2Handler`](https://github.com/Mastercard/oauth2-client-python/blob/main/src/mastercard_oauth2_client/handlers/sync.py) and
[`AsyncOAuth2Handler`](https://github.com/Mastercard/oauth2-client-python/blob/main/src/mastercard_oauth2_client/handlers/async_.py) run the
OAuth2 and DPoP flow. To support another HTTP client, provide the matching
[`SyncHttpAdapter` or `AsyncHttpAdapter`](https://github.com/Mastercard/oauth2-client-python/blob/main/src/mastercard_oauth2_client/protocols.py)
and pass it to the handler. For higher-level use, choose a ready-made
integration below.

### Redirects

Token endpoint redirects are not supported and raise `OAuth2Error`. Configure
`OAuth2Config.token_endpoint` with the final token endpoint URL.

API redirects follow the HTTP client's normal behavior. Redirects within the
same origin receive a new DPoP proof. Redirects to another origin receive no
OAuth headers.

Note: Generated urllib3 clients do not follow redirects and return the original
`3xx` response to the caller.

### Direct HTTP Client Integration

For a higher-level experience, use an HTTP client integration to authenticate
requests automatically. Choose the client that best fits your application.
Each integration provides the same OAuth2 and DPoP behavior.

| HTTP client | Mode | Supported versions | Status |
|---|---|---|---|
| Requests | Sync | `>=2.33,<3` | [![Requests](https://github.com/Mastercard/oauth2-client-python/actions/workflows/ci-requests.yml/badge.svg)](https://github.com/Mastercard/oauth2-client-python/actions/workflows/ci-requests.yml) |
| HTTPX | Sync and async | `>=0.28,<1` | [![HTTPX](https://github.com/Mastercard/oauth2-client-python/actions/workflows/ci-httpx.yml/badge.svg)](https://github.com/Mastercard/oauth2-client-python/actions/workflows/ci-httpx.yml) |
| aiohttp | Async | `>=3.13,<4` | [![aiohttp](https://github.com/Mastercard/oauth2-client-python/actions/workflows/ci-aiohttp.yml/badge.svg)](https://github.com/Mastercard/oauth2-client-python/actions/workflows/ci-aiohttp.yml) |

#### Requests

Install `mastercard-oauth2-client[requests]`, then use `OAuth2Session` like a
normal `requests.Session`:

```python
from mastercard_oauth2_client.integrations.requests import OAuth2Session

with OAuth2Session(oauth2_config) as client:
    response = client.get("https://api.mastercard.com/resources", timeout=30)
    response.raise_for_status()
```

#### HTTPX

Install `mastercard-oauth2-client[httpx]`.

`OAuth2Transport` supports `httpx.Client`:

```python
import httpx

from mastercard_oauth2_client.integrations.httpx import OAuth2Transport

with httpx.Client(transport=OAuth2Transport(oauth2_config)) as client:
    response = client.get("https://api.mastercard.com/resources")
    response.raise_for_status()
```

For asynchronous applications, use `AsyncOAuth2Transport` with
`httpx.AsyncClient`:

```python
import httpx

from mastercard_oauth2_client.integrations.httpx import AsyncOAuth2Transport

async with httpx.AsyncClient(transport=AsyncOAuth2Transport(oauth2_config)) as client:
    response = await client.get("https://api.mastercard.com/resources")
    response.raise_for_status()
```

Pass a configured HTTPX transport to the wrapper for custom network settings.

#### aiohttp

Install `mastercard-oauth2-client[aiohttp]`, then create an OAuth-enabled
session:

```python
from mastercard_oauth2_client.integrations.aiohttp import create_oauth2_session

async with create_oauth2_session(oauth2_config) as client:
    response = await client.get("https://api.mastercard.com/resources")
    response.raise_for_status()
    result = await response.json()
```

Pass standard `aiohttp.ClientSession` options to `create_oauth2_session()` as
keyword arguments.

### OpenAPI Generated Clients

The library integrates with Python clients produced by OpenAPI Generator.
Attach OAuth2 before the generated client sends its first request.

| Generated library | Mode | Supported versions | Status |
|---|---|---|---|
| Default (`urllib3`) | Sync | urllib3 `>=2.1,<3` | [![OpenAPI urllib3](https://github.com/Mastercard/oauth2-client-python/actions/workflows/ci-urllib3.yml/badge.svg)](https://github.com/Mastercard/oauth2-client-python/actions/workflows/ci-urllib3.yml) |
| `httpx` | Async | HTTPX `>=0.28,<1` | [![OpenAPI HTTPX](https://github.com/Mastercard/oauth2-client-python/actions/workflows/ci-openapi-httpx.yml/badge.svg)](https://github.com/Mastercard/oauth2-client-python/actions/workflows/ci-openapi-httpx.yml) |
| `asyncio` | Async | aiohttp `>=3.13,<4` | [![OpenAPI asyncio](https://github.com/Mastercard/oauth2-client-python/actions/workflows/ci-openapi-asyncio.yml/badge.svg)](https://github.com/Mastercard/oauth2-client-python/actions/workflows/ci-openapi-asyncio.yml) |

#### OpenAPI Generator Compatibility

| Versions | Status |
|---|---|
| 7.23, 7.24 | [![OpenAPI Generator](https://github.com/Mastercard/oauth2-client-python/actions/workflows/ci-openapi.yml/badge.svg)](https://github.com/Mastercard/oauth2-client-python/actions/workflows/ci-openapi.yml) |

#### Python Generator (urllib3)

The Python generator uses urllib3 when no `--library` option is supplied.

```python
from generated_client import ApiClient, Configuration, ResourcesApi
from mastercard_oauth2_client import add_oauth2_layer

api_client = ApiClient(Configuration(host="https://api.mastercard.com"))
add_oauth2_layer(api_client, oauth2_config)

resources_api = ResourcesApi(api_client)
resources = resources_api.get_resources()
```

This adapter is specific to OpenAPI-generated clients. It is not a direct
urllib3 integration.

#### Python Generator (`httpx`)

Generate with `--library httpx` and install the `httpx` extra.

```python
from generated_client import ApiClient, Configuration, ResourcesApi
from mastercard_oauth2_client.integrations.openapi.httpx import add_oauth2_layer

api_client = ApiClient(Configuration(host="https://api.mastercard.com"))
add_oauth2_layer(api_client, oauth2_config)

try:
    resources_api = ResourcesApi(api_client)
    resources = await resources_api.get_resources()
finally:
    await api_client.close()
```

#### Python Generator (`asyncio`)

Generate with `--library asyncio` and install the `aiohttp` extra.

```python
from generated_client import ApiClient, Configuration, ResourcesApi
from mastercard_oauth2_client.integrations.openapi.asyncio import add_oauth2_layer

api_client = ApiClient(Configuration(host="https://api.mastercard.com"))
add_oauth2_layer(api_client, oauth2_config)

try:
    resources_api = ResourcesApi(api_client)
    resources = await resources_api.get_resources()
finally:
    await api_client.close()
```

## Extension Points

This library is small by design and can be extended for different application
needs.

Extension instances are scoped to one OAuth2 configuration by default. If
custom token stores, scope resolvers or DPoP key providers are shared across
client identities, the application must provide appropriate data isolation and
concurrency safety.

- [`OAuth2Config`](https://github.com/Mastercard/oauth2-client-python/blob/main/src/mastercard_oauth2_client/config.py) connects credentials,
    endpoints and extension points.
- Use [`StaticScopeResolver`](https://github.com/Mastercard/oauth2-client-python/blob/main/src/mastercard_oauth2_client/defaults.py) for
    fixed scopes, or provide a custom
    [`ScopeResolver`](https://github.com/Mastercard/oauth2-client-python/blob/main/src/mastercard_oauth2_client/protocols.py) to choose scopes
    for each request.
- Use [`StaticDPoPKeyProvider`](https://github.com/Mastercard/oauth2-client-python/blob/main/src/mastercard_oauth2_client/defaults.py) for
    one key pair, or provide a custom
    [`DPoPKeyProvider`](https://github.com/Mastercard/oauth2-client-python/blob/main/src/mastercard_oauth2_client/protocols.py) to supply or
    rotate keys.
- Use the default `InMemoryTokenStore`, or provide a custom
    [`TokenStore`](https://github.com/Mastercard/oauth2-client-python/blob/main/src/mastercard_oauth2_client/protocols.py) to control token
    storage.
- [Key utilities](https://github.com/Mastercard/oauth2-client-python/blob/main/src/mastercard_oauth2_client/keys.py): generate supported key
    pairs and load PEM, DER, PKCS#12 and private JWK key material.

## Key Support

The library supports RSA and EC keys:

- Load client private keys from PEM, DER or PKCS#12 files.
- Load DPoP key pairs from private JWKs.
- Generate RSA key pairs of at least 2048 bits with `generate_rsa_key_pair`.
- Generate P-256 key pairs for ES256 signing with `generate_ec_key_pair`.
- Use RSA keys of at least 2048 bits for PS256 signing.
- Use P-256 EC keys for ES256 signing.

## Troubleshooting

### Configuration

The token endpoint must be an absolute HTTPS URL. Configuration errors raise
`OAuth2ConfigError` with the invalid setting.

### Token endpoint errors

When the token endpoint returns an HTTP error, the library raises
`OAuth2TokenEndpointError`. The exception includes the status code and any
OAuth error details or correlation ID returned by the server.

### Requests: streaming uploads

`OAuth2Session` supports bodies prepared as bytes or strings, including normal
JSON, form and multipart requests. Direct streaming bodies such as open files,
generators and pipes are rejected before transmission.

### Logging

Use Python's standard logging configuration to enable messages from
`mastercard_oauth2_client`. Sensitive tokens, assertions, proofs, nonces, keys
and response bodies are not logged.

## Test Strategy

- [`tests/unit`](https://github.com/Mastercard/oauth2-client-python/tree/main/tests/unit) contains fast protocol, handler and adapter tests,
    with client-specific tests grouped under `direct` and `openapi`.
- [`tests/integration`](https://github.com/Mastercard/oauth2-client-python/tree/main/tests/integration) tests direct and generated clients
    against local HTTPS servers, grouped under `direct` and `openapi`.
- [`tests/live`](https://github.com/Mastercard/oauth2-client-python/tree/main/tests/live) calls the real Mastercard sandbox. These optional
    tests skip when sandbox credentials are not configured.

The OpenAPI specifications have separate purposes:

- [`fake-api.yaml`](https://github.com/Mastercard/oauth2-client-python/blob/main/tests/resources/openapi/fake-api.yaml) generates clients for
    local integration tests.
- [`petstore-api.yaml`](https://github.com/Mastercard/oauth2-client-python/blob/main/tests/resources/openapi/petstore-api.yaml) generates
    clients for real Mastercard sandbox tests.

To run all tests that do not require generated OpenAPI clients:

```bash
# Unit tests
python -m pytest -v tests/unit/test_*.py tests/unit/direct

# Integration tests
python -m pytest -v tests/integration/direct
```

To focus on one client, pass its unit and integration directories. For example,
to test Requests:

```bash
python -m pytest -v tests/unit/direct/requests tests/integration/direct/requests
```

OpenAPI tests require generated clients. Generate all clients for the complete
suite, or select one client for focused tests:

```bash
# Run the complete test suite
python scripts/generate_test_clients.py
python -m pytest -v

# Run only the OpenAPI HTTPX tests
python scripts/generate_test_clients.py --client httpx
python -m pytest -v tests/unit/openapi/httpx tests/integration/openapi/httpx
```

To run sandbox tests locally, set the variables shown in
[`.env.example`](https://github.com/Mastercard/oauth2-client-python/blob/main/.env.example). You may export them in your shell or copy that
file to `.env` as a convenience. Set `LIVE_HTTP_CLIENT` to `requests`, `httpx`,
`httpx-async`, `aiohttp`, `openapi-urllib3`, `openapi-httpx` or
`openapi-asyncio`, and set `LIVE_DPOP_ALGORITHM` to `ec` or `rsa`.

For a direct integration:

```bash
LIVE_HTTP_CLIENT=requests LIVE_DPOP_ALGORITHM=ec \
    python -m pytest tests/live/test_mastercard_petstore.py -v -s --tb=short -rs
```

For an OpenAPI integration, generate the Petstore clients first:

```bash
python scripts/generate_test_clients.py --live --client urllib3
LIVE_HTTP_CLIENT=openapi-urllib3 LIVE_DPOP_ALGORITHM=ec \
    python -m pytest tests/live/test_mastercard_petstore.py -v -s --tb=short -rs
```

### Test failures

- Start with the narrowest failing test: `python -m pytest path/to/test.py::test_name -v`.
- Sandbox tests skip when no sandbox settings are configured. Partial settings
    fail and name the missing variables.

## Development

Install the development tools and pinned default OpenAPI Generator, then run
the quality gates:

```bash
python -m pip install -e . --group dev --group generator
python scripts/generate_test_clients.py
python -m pytest -v
python -m ruff check .
python -m mypy
python -m build
```

Tested Python, HTTP client and generator combinations are defined in the
[CI workflows](https://github.com/Mastercard/oauth2-client-python/tree/main/.github/workflows).

### Code Style

[![Ruff](https://img.shields.io/badge/lint-Ruff-D7FF64.svg?logo=ruff&logoColor=black)](https://docs.astral.sh/ruff/)
[![mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://www.mypy-lang.org/)

[Ruff](https://docs.astral.sh/ruff/) checks code quality.
[Mypy](https://www.mypy-lang.org/) checks the package and test scripts in strict
mode.