Repository navigation
feat(smart): add SMART App Launch 2.0.0 and US Core Backend Services support - #1388
Open
vincentvanhees wants to merge 16 commits into
Open
vincentvanhees wants to merge 16 commits into
vincentvanhees wants to merge 16 commits into
Conversation
… 0121-0123) Add server-side support for SMART App Launch 2.0 and US Core Backend Services requirements, with minimalistic changes to the existing OAuth2 infrastructure. - Add token introspection endpoint (RFC 7662) at /oauth2/introspect (CONF-0123) - Add private_key_jwt client authentication via FencePrivateKeyJWT (CONF-0120); clients register their JWKS in client_metadata["jwks"] — no schema migration needed - Extend ClientCredentialsGrant to accept private_key_jwt authentication - Fix validate_scopes to recognise system/ scopes as SMART scopes and drop the openid requirement for Backend Services flows (CONF-0121/0122) - Add PKCE support (RFC 7636) to the authorization code grant (CONF-0117) - Advertise client-confidential-asymmetric and private_key_jwt in well-known metadata; move the SMART discovery document to gen3-fhir-proxy (FHIR layer) - Filter null values from introspection responses per RFC 7662
…iven
Fence previously hardcoded SMART on FHIR scope prefixes (patient/, user/,
system/, launch/) and literals (fhirUser, openid, etc.) in create_client(),
and hardcoded fhirUser ID-token claim generation in generate_id_token().
This couples fence to FHIR semantics it should not know about.
Replace with two generic config mechanisms:
EXTRA_ALLOWED_SCOPE_PREFIXES / EXTRA_ALLOWED_SCOPES
Lists of scope prefixes and exact literals that bypass the
CLIENT_ALLOWED_SCOPES check when registering OAuth clients. FHIR
deployments set these in their fence-config.yaml; a stock fence
deployment leaves them empty (defaults: []).
SCOPE_EXTRA_CLAIMS
Map of scope-name → {base_url_env, user_tag}. When a matching scope is
requested and the authenticated user has the named tag, the claim is
added to the ID token as "{env_var_value}/{tag_value}". FHIR deployments
configure this for the fhirUser claim; fence itself carries no FHIR
knowledge.
Also include "scope" in token responses per RFC 6749 §5.1.
Integration TestsTest summary after running integration tests
Test summary after rerunning failed integration tests
Please find the detailed integration test report here Please find the detailed integration test report after rerunning failed tests here Please find the Github Action logs here |
Failure AnalysisPlease find the detailed test analysis report here |
…t before processing/syncing any
fix(security): address various security findings and bugs
pieterlukasse
self-requested a review
October 12, 2026 13:11
validate_scopes() returned True early for any request containing a SMART scope (including openid), bypassing client.check_requested_scopes() for the entire request. This allowed arbitrary unknown scopes to pass through unchecked whenever openid was also present. Non-SMART scopes are now explicitly validated against the client's allowed scopes even when the request is identified as a SMART flow.
Coverage Report for CI Build 38256453707Coverage decreased (-0.4%) to 74.268%Details
Uncovered ChangesNo uncovered changes found. Coverage Regressions195 previously-covered lines in 6 files lost coverage.
Coverage Stats
💛 - Coveralls |
Integration Tests
Please find the detailed integration test report here Please find the Github Action logs here |
vincentvanhees
marked this pull request as ready for review
October 12, 2026 15:19
Fix resolve_client_public_key() in private_key_jwt.py for authlib 1.6.12 compatibility: list(key_set) is not supported (use key_set.keys), and find_by_kid() raises ValueError on a miss rather than returning None. Set PROMETHEUS_MULTIPROC_DIR in tests/conftest.py before fence is imported so the test suite runs without requiring the env var to be set manually. Add 74 new tests across 5 files targeting the new SMART on FHIR / US Core compliance code on this branch: - tests/oidc/test_private_key_jwt.py: create_claims_options and all resolve_client_public_key branches (no JWKS, matching kid, unknown kid, no kid, empty key set) - tests/oidc/test_endpoints.py: JWTIntrospectionToken, IntrospectionEndpoint payload building and query_token, revoke and introspect HTTP endpoints - tests/oidc/test_jwt_generator.py: generate_client_response scope handling, client_credentials dispatch, implicit scope-as-string path - tests/oidc/test_oidc_code_grant.py: PKCE S256/plain full flows and _validate_code_verifier branches, exists_nonce, validate_request_prompt (none/consent/select_account prompts), token request error paths - tests/oidc/test_oauth2_blueprint.py: auto_approve, consent denial, prompt=none error cases, unauthenticated authorize paths, revoke with invalid token, display_error endpoint
… 0121-0123) Add server-side support for SMART App Launch 2.0 and US Core Backend Services requirements, with minimalistic changes to the existing OAuth2 infrastructure. - Add token introspection endpoint (RFC 7662) at /oauth2/introspect (CONF-0123) - Add private_key_jwt client authentication via FencePrivateKeyJWT (CONF-0120); clients register their JWKS in client_metadata["jwks"] — no schema migration needed - Extend ClientCredentialsGrant to accept private_key_jwt authentication - Fix validate_scopes to recognise system/ scopes as SMART scopes and drop the openid requirement for Backend Services flows (CONF-0121/0122) - Add PKCE support (RFC 7636) to the authorization code grant (CONF-0117) - Advertise client-confidential-asymmetric and private_key_jwt in well-known metadata; move the SMART discovery document to gen3-fhir-proxy (FHIR layer) - Filter null values from introspection responses per RFC 7662
…iven
Fence previously hardcoded SMART on FHIR scope prefixes (patient/, user/,
system/, launch/) and literals (fhirUser, openid, etc.) in create_client(),
and hardcoded fhirUser ID-token claim generation in generate_id_token().
This couples fence to FHIR semantics it should not know about.
Replace with two generic config mechanisms:
EXTRA_ALLOWED_SCOPE_PREFIXES / EXTRA_ALLOWED_SCOPES
Lists of scope prefixes and exact literals that bypass the
CLIENT_ALLOWED_SCOPES check when registering OAuth clients. FHIR
deployments set these in their fence-config.yaml; a stock fence
deployment leaves them empty (defaults: []).
SCOPE_EXTRA_CLAIMS
Map of scope-name → {base_url_env, user_tag}. When a matching scope is
requested and the authenticated user has the named tag, the claim is
added to the ID token as "{env_var_value}/{tag_value}". FHIR deployments
configure this for the fhirUser claim; fence itself carries no FHIR
knowledge.
Also include "scope" in token responses per RFC 6749 §5.1.
validate_scopes() returned True early for any request containing a SMART scope (including openid), bypassing client.check_requested_scopes() for the entire request. This allowed arbitrary unknown scopes to pass through unchecked whenever openid was also present. Non-SMART scopes are now explicitly validated against the client's allowed scopes even when the request is identified as a SMART flow.
Fix resolve_client_public_key() in private_key_jwt.py for authlib 1.6.12 compatibility: list(key_set) is not supported (use key_set.keys), and find_by_kid() raises ValueError on a miss rather than returning None. Set PROMETHEUS_MULTIPROC_DIR in tests/conftest.py before fence is imported so the test suite runs without requiring the env var to be set manually. Add 74 new tests across 5 files targeting the new SMART on FHIR / US Core compliance code on this branch: - tests/oidc/test_private_key_jwt.py: create_claims_options and all resolve_client_public_key branches (no JWKS, matching kid, unknown kid, no kid, empty key set) - tests/oidc/test_endpoints.py: JWTIntrospectionToken, IntrospectionEndpoint payload building and query_token, revoke and introspect HTTP endpoints - tests/oidc/test_jwt_generator.py: generate_client_response scope handling, client_credentials dispatch, implicit scope-as-string path - tests/oidc/test_oidc_code_grant.py: PKCE S256/plain full flows and _validate_code_verifier branches, exists_nonce, validate_request_prompt (none/consent/select_account prompts), token request error paths - tests/oidc/test_oauth2_blueprint.py: auto_approve, consent denial, prompt=none error cases, unauthenticated authorize paths, revoke with invalid token, display_error endpoint
…ence into feat/uscore-fhir-ig-compliance
Integration Tests
Please find the detailed integration test report here Please find the Github Action logs here |
… OIDC discovery Add introspection_endpoint (RFC 7662), revocation_endpoint (RFC 7009), and private_key_jwt to token_endpoint_auth_methods_supported in the /.well-known/openid-configuration response. These were missing because the SMART/US Core compliance work added the underlying endpoints but did not update the discovery document. Clients that auto-discover fence capabilities via OIDC discovery (rather than via /.well-known/smart-configuration) would not know these endpoints exist. This is particularly relevant for Backend Services clients checking for private_key_jwt support (CONF-0120).
Failure AnalysisPlease find the detailed test analysis report here |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Link to JIRA ticket if there is one:
Adds server-side SMART App Launch 2.0.0 support to fence so that
gen3-fhir-proxy can pass the Inferno US Core v6.1.0 test suite. All tests
pass except 1.3.2.01 and 1.3.2.02 (TLS-only checks; will pass on QA/prod).
Existing OAuth2 flows are unaffected.
The SMART App Launch discovery document (
/.well-known/smart-configuration)is served by gen3-fhir-proxy, not fence.
New Features
POST /oauth2/introspect, RFC 7662 / CONF-0123):validates a token using fence's JWT validation and returns an RFC 7662
response including SMART patient context (
patient,username) when present.Requires HTTP Basic auth with a registered client_id + client_secret.
register their JWKS in
client_metadata["jwks"]and authenticateasymmetrically at the token endpoint — no shared secret required. Used by
SMART Backend Services. Note: JTI replay prevention is not implemented; the
expclaim limits the replay window to the token's lifetime.code_challengeandcode_challenge_methodare now persisted on the authorization code andverified on token exchange. Supports S256 and plain. Backwards compatible —
flows without PKCE continue to work.
Breaking Changes
None.
Bug Fixes
scopeis now included in token responses per RFC 6749 §5.1. SMARTclients depend on this field to confirm which scopes were granted.
Improvements
validate_scopesnow accepts SMART scopeprefixes (
patient/,user/,system/,launch/) and literals(
fhirUser,openid, etc.) without requiring them inCLIENT_ALLOWED_SCOPES. Backend Services flows (system/scopes only)no longer require
openid.Dependency updates
None.
Deployment changes
Two new optional config keys (both default to empty — no behaviour change
for non-FHIR deployments):
EXTRA_ALLOWED_SCOPE_PREFIXES/EXTRA_ALLOWED_SCOPES: allow additionalscope patterns on client registration beyond
CLIENT_ALLOWED_SCOPES.SCOPE_EXTRA_CLAIMS: maps a scope name to an ID-token claim built from auser tag and a base URL read from an environment variable. Example for the
SMART
fhirUserclaim:No new database migration is required — the PKCE columns (code_challenge,
code_challenge_method) are already added by the existing migration
9b3a5a7145d7.