Metadata-Version: 2.4
Name: cms-hte-ial2-reader
Version: 0.0.3
Summary: Reads IAL2 token, verifies digital signature and returns FHIR Patient resource
Author-email: Imran Qureshi <imran.qureshi@bwell.com>
Project-URL: Repository, https://github.com/icanbwell/cms-hte-ial2-reader
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: pyjwt>=2.8.0
Requires-Dist: cryptography>=43.0.0
Requires-Dist: fhirschemapy>=0.0.11
Dynamic: license-file

# cms-hte-ial2-reader

Reads IAL2 token, verifies digital signature and returns FHIR Patient resource

Accepts a signed JWT (ID Token) from a Credential Service Provider (CSP),
verifies the signature via JWKS, extracts demographics per the CSP payload
specification V7.1, and returns a FHIR R4 Patient resource.

## Usage

```python
from cmshteial2reader import IAL2Extractor, TokenVerifier

verifier = TokenVerifier(
    jwks_uri="https://idp.example.com/.well-known/jwks.json",
    audience="my-client-id",
)
extractor = IAL2Extractor(verifier=verifier)
patient = await extractor.extract(token_string)
# patient is a FHIR R4 Patient resource dict
```

To accept tokens from any issuer on an explicit allow list (rather than a
single pinned issuer), use `MultiIssuerTokenVerifier` in place of
`TokenVerifier`:

```python
from cmshteial2reader import IAL2Extractor, MultiIssuerTokenVerifier

verifier = MultiIssuerTokenVerifier.from_env(audience="my-client-id")
extractor = IAL2Extractor(verifier=verifier)
patient = await extractor.extract(token_string)
```

`MultiIssuerTokenVerifier.from_env` reads its whitelist of trusted JWKS URLs
from the `IAL2_ALLOWED_JWKS_URLS` environment variable (comma-separated).

### Assurance-level enforcement

`IAL2Extractor` requires the token's `identity_assurance_level` claim to be
at least IAL2 (accepting values like `"ial2"`, `"IAL2"`, or `"2"`) -- a
token missing the claim, or asserting a lower level (e.g. `"ial1"`), raises
`InsufficientAssuranceLevelError` (a subclass of `TokenVerificationError`,
so existing callers that only catch the latter still catch this). This
applies to both `extract()` and `extract_claims()`. To require a higher
level, pass `minimum_assurance_level` to the constructor:

```python
extractor = IAL2Extractor(verifier=verifier, minimum_assurance_level=3)
```

### CMS Blue Button `cms_smart` extension

CMS Aligned Networks convey a patient's IAL2-verified identity by nesting a
CSP-issued ID token inside the outer auth token's claims, under
`extensions.cms_smart.id_token` (see
https://bluebutton.cms.gov/cms-aligned-networks-documentation/). Callers
that receive an already-verified outer JWT (e.g. from their own auth
middleware) can pull out the nested token with `extract_cms_smart_id_token`
and pass it to `IAL2Extractor.extract`:

```python
from cmshteial2reader import IAL2Extractor, MultiIssuerTokenVerifier, extract_cms_smart_id_token

id_token = extract_cms_smart_id_token(outer_jwt_claims)
if id_token is not None:
    verifier = MultiIssuerTokenVerifier.from_env(audience="my-client-id")
    extractor = IAL2Extractor(verifier=verifier)
    patient = await extractor.extract(id_token)
```

`extract_cms_smart_id_token` only reads the `id_token` claim -- it does not
verify anything, and it does not inspect or enforce `purpose_of_use` or
`version`. The outer JWT must already be signature-verified by the caller,
the nested id_token still needs its own verification via
`IAL2Extractor`/`TokenVerifier`, and a caller that needs to gate on
purpose-of-use must check `claims["extensions"]["cms_smart"]` itself.

## Quickstart
- `make init` to set up the local dev environment.
- `make up` to start the Docker dev stack.
- `make tests` to run tests in the dev container.

## Common Tasks
- `make shell` to open a shell in the dev container.
- `make run-pre-commit` to run linting and formatting hooks.
- `make build` to build the package artifacts.

## Publishing
- Set a token: `export TWINE_PASSWORD=pypi-xxx`
- `make package` to publish to PyPI or `make testpackage` for TestPyPI.

## Project Metadata
- Repo: https://github.com/icanbwell/cms-hte-ial2-reader
- Author: Imran Qureshi <imran.qureshi@bwell.com>
