safire 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -1
- data/Gemfile +1 -0
- data/Gemfile.lock +3 -1
- data/README.md +39 -7
- data/ROADMAP.md +4 -4
- data/docs/Gemfile.lock +2 -2
- data/docs/_config.yml +4 -1
- data/docs/adr/ADR-002-facade-and-forwardable.md +1 -1
- data/docs/adr/ADR-003-protocol-vs-client-type.md +1 -1
- data/docs/adr/ADR-006-lazy-discovery.md +1 -1
- data/docs/adr/ADR-008-warn-return-false-for-compliance-validation.md +4 -3
- data/docs/configuration/client-setup.md +9 -7
- data/docs/index.md +6 -4
- data/docs/installation.md +8 -5
- data/docs/smart-on-fhir/backend-services/index.md +92 -0
- data/docs/smart-on-fhir/backend-services/token-request.md +207 -0
- data/docs/smart-on-fhir/confidential-asymmetric/authorization.md +1 -1
- data/docs/smart-on-fhir/confidential-asymmetric/index.md +6 -3
- data/docs/smart-on-fhir/confidential-asymmetric/token-exchange.md +1 -1
- data/docs/smart-on-fhir/confidential-symmetric/authorization.md +1 -1
- data/docs/smart-on-fhir/confidential-symmetric/index.md +2 -2
- data/docs/smart-on-fhir/confidential-symmetric/token-exchange.md +1 -1
- data/docs/smart-on-fhir/discovery/capability-checks.md +25 -1
- data/docs/smart-on-fhir/discovery/index.md +3 -3
- data/docs/smart-on-fhir/discovery/metadata.md +1 -1
- data/docs/smart-on-fhir/index.md +22 -13
- data/docs/smart-on-fhir/post-based-authorization.md +1 -1
- data/docs/smart-on-fhir/public-client/authorization.md +1 -1
- data/docs/smart-on-fhir/public-client/index.md +2 -2
- data/docs/smart-on-fhir/public-client/token-exchange.md +1 -1
- data/docs/troubleshooting/auth-errors.md +4 -1
- data/docs/troubleshooting/client-errors.md +30 -0
- data/docs/troubleshooting/index.md +1 -1
- data/docs/udap.md +4 -3
- data/lib/safire/client.rb +28 -19
- data/lib/safire/client_config.rb +5 -5
- data/lib/safire/jwt_assertion.rb +1 -1
- data/lib/safire/protocols/behaviours.rb +6 -0
- data/lib/safire/protocols/smart.rb +95 -20
- data/lib/safire/protocols/smart_metadata.rb +11 -2
- data/lib/safire/version.rb +1 -1
- data/safire.gemspec +6 -2
- metadata +9 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b9d2b84cc65eff523a4f1a8c86c5c2fe4f2089917062c115a12fb840e52369b4
|
|
4
|
+
data.tar.gz: 0f9cb348d913947546dff15f2d5d7a7904ca62dfdbce01dd8117598811ce5ec9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 955b05bf392bebc9202d4b4c623e7223a398a969e72a2ed399bf0d25b57436cf3b2bbc086c3cdc2902f22742c36139089ec59d0389f4708185920746575134b3
|
|
7
|
+
data.tar.gz: 146ef00d7041a656c401e580e24cd309901b28a9b0584b37074f7067a2c76fe2f6548369feaa7692940af3aafc6fd3c36d045623e835b0bfdb33c0b468350d07
|
data/CHANGELOG.md
CHANGED
|
@@ -7,13 +7,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0] - 2026-04-04
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- SMART Backend Services Authorization flow (`client_credentials` grant) via
|
|
15
|
+
`Safire::Client#request_backend_token` and `Safire::Protocols::Smart#request_backend_token`:
|
|
16
|
+
- Authenticates exclusively via a signed JWT assertion (RS384 or ES384); no redirect,
|
|
17
|
+
PKCE, or user interaction required
|
|
18
|
+
- Scope defaults to `["system/*.rs"]` when none is configured or provided
|
|
19
|
+
- `private_key` and `kid` can be overridden per call
|
|
20
|
+
- `token_response_valid?` now accepts a `flow:` keyword argument (`:app_launch` default):
|
|
21
|
+
when `flow: :backend_services`, also validates `expires_in` presence (required per
|
|
22
|
+
SMART Backend Services spec)
|
|
23
|
+
- `token_response_valid?` accepts both `"Bearer"` (SMART App Launch spec) and `"bearer"`
|
|
24
|
+
(SMART Backend Services) as valid `token_type` values; the non-compliance warning
|
|
25
|
+
now references the expected value for the active flow
|
|
26
|
+
- `SmartMetadata#supports_backend_services?` returns `true` when the server advertises the
|
|
27
|
+
`client_credentials` grant type and supports `private_key_jwt` authentication
|
|
28
|
+
(i.e. `grant_types_supported` includes `"client_credentials"` and
|
|
29
|
+
`supports_asymmetric_auth?` is `true`)
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- Corrected spec name throughout: "SMART on FHIR" → "SMART App Launch" per the
|
|
34
|
+
[SMART App Launch IG](https://hl7.org/fhir/smart-app-launch/); Backend Services is
|
|
35
|
+
presented as a feature within the spec, not a separate spec
|
|
36
|
+
- `redirect_uri` and `authorization_endpoint` are now optional in `Safire::Protocols::Smart`;
|
|
37
|
+
both are validated only when `authorization_url` is called (app launch flow)
|
|
38
|
+
- `redirect_uri` is now optional in `Safire::ClientConfig` to support backend services
|
|
39
|
+
clients that operate without a redirect URI; the field is still validated when provided
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- YARD API docs nav links broken after in-page navigation: relative hrefs from the nav
|
|
44
|
+
iframe were resolved against the parent window URL (which changes on each navigation)
|
|
45
|
+
instead of the iframe base; `bin/docs` now patches the generated `full_list.js` to
|
|
46
|
+
resolve links to absolute URLs before messaging the parent
|
|
47
|
+
|
|
10
48
|
## [0.1.0] - 2026-03-25
|
|
11
49
|
|
|
12
50
|
### Added
|
|
13
51
|
|
|
14
52
|
- `Safire::Client` facade with `protocol:` (`:smart`) and `client_type:`
|
|
15
53
|
(`:public`, `:confidential_symmetric`, `:confidential_asymmetric`) keywords
|
|
16
|
-
- SMART
|
|
54
|
+
- SMART App Launch 2.2.0 support via `Safire::Protocols::Smart`:
|
|
17
55
|
- Server metadata discovery from `/.well-known/smart-configuration`
|
|
18
56
|
- Authorization URL builder for GET and POST-based authorization
|
|
19
57
|
(`authorize-post` capability)
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
safire (0.
|
|
4
|
+
safire (0.2.0)
|
|
5
5
|
activesupport (~> 8.0.0)
|
|
6
6
|
addressable (~> 2.8)
|
|
7
7
|
faraday (~> 2.14)
|
|
@@ -47,6 +47,7 @@ GEM
|
|
|
47
47
|
reline (>= 0.3.8)
|
|
48
48
|
diff-lcs (1.6.2)
|
|
49
49
|
docile (1.4.1)
|
|
50
|
+
dotenv (3.2.0)
|
|
50
51
|
drb (2.2.3)
|
|
51
52
|
erb (5.0.3)
|
|
52
53
|
faraday (2.14.1)
|
|
@@ -167,6 +168,7 @@ PLATFORMS
|
|
|
167
168
|
DEPENDENCIES
|
|
168
169
|
bundler-audit (~> 0.9)
|
|
169
170
|
debug
|
|
171
|
+
dotenv (~> 3.0)
|
|
170
172
|
pry
|
|
171
173
|
pry-byebug
|
|
172
174
|
rspec (~> 3.12)
|
data/README.md
CHANGED
|
@@ -5,19 +5,20 @@
|
|
|
5
5
|
[](https://codecov.io/gh/vanessuniq/safire)
|
|
6
6
|
[](https://vanessuniq.github.io/safire)
|
|
7
7
|
|
|
8
|
-
Safire is a
|
|
8
|
+
Safire is a Ruby gem implementing the [SMART App Launch 2.2.0](https://hl7.org/fhir/smart-app-launch/) specification and the [UDAP Security](https://hl7.org/fhir/us/udap-security/) protocol for healthcare client applications. It handles OAuth 2.0 authorization against HL7 FHIR servers, covering PKCE, private key JWT assertions, and the Backend Services system-to-system flow, so you can focus on your application rather than protocol plumbing.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
## Features
|
|
13
13
|
|
|
14
|
-
### SMART
|
|
14
|
+
### SMART App Launch (v2.2.0)
|
|
15
15
|
|
|
16
16
|
- Discovery (`/.well-known/smart-configuration`)
|
|
17
17
|
- Public Client (PKCE)
|
|
18
18
|
- Confidential Symmetric Client (`client_secret` + HTTP Basic Auth)
|
|
19
19
|
- Confidential Asymmetric Client (`private_key_jwt` with RS384/ES384)
|
|
20
20
|
- POST-Based Authorization
|
|
21
|
+
- Backend Services (`client_credentials` grant, JWT assertion, no user interaction or PKCE; scope defaults to `system/*.rs`)
|
|
21
22
|
|
|
22
23
|
### UDAP
|
|
23
24
|
|
|
@@ -46,10 +47,12 @@ require 'safire'
|
|
|
46
47
|
|
|
47
48
|
# Step 1 — Create a client (Hash config or Safire::ClientConfig.new)
|
|
48
49
|
client = Safire::Client.new(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
{
|
|
51
|
+
base_url: 'https://launch.smarthealthit.org/v/r4/sim/eyJoIjoiMSJ9/fhir',
|
|
52
|
+
client_id: 'my_client_id',
|
|
53
|
+
redirect_uri: 'https://myapp.example.com/callback',
|
|
54
|
+
scopes: ['openid', 'profile', 'patient/*.read']
|
|
55
|
+
}
|
|
53
56
|
)
|
|
54
57
|
|
|
55
58
|
# Step 2 — Discover SMART metadata (lazy — only called when needed)
|
|
@@ -98,6 +101,35 @@ client = Safire::Client.new(
|
|
|
98
101
|
# Authorization and token exchange are identical — Safire builds the JWT assertion automatically
|
|
99
102
|
```
|
|
100
103
|
|
|
104
|
+
### Backend Services (system-to-system)
|
|
105
|
+
|
|
106
|
+
No user interaction, redirect URI, or PKCE required — the client authenticates entirely via a signed JWT assertion:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
client = Safire::Client.new(
|
|
110
|
+
{
|
|
111
|
+
base_url: 'https://fhir.example.com',
|
|
112
|
+
client_id: 'my_backend_client',
|
|
113
|
+
private_key: OpenSSL::PKey::RSA.new(File.read('private_key.pem')),
|
|
114
|
+
kid: 'my-key-id-123',
|
|
115
|
+
scopes: ['system/Patient.rs', 'system/Observation.rs']
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
token_data = client.request_backend_token
|
|
120
|
+
# token_data => { "access_token" => "...", "token_type" => "Bearer", "expires_in" => 300, ... }
|
|
121
|
+
|
|
122
|
+
# Override scope or credentials per call
|
|
123
|
+
token_data = client.request_backend_token(
|
|
124
|
+
scopes: ['system/Patient.rs'],
|
|
125
|
+
private_key: OpenSSL::PKey::RSA.new(File.read('new_key.pem')),
|
|
126
|
+
kid: 'new-key-id'
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Validate the token response (flow: :backend_services also checks expires_in)
|
|
130
|
+
client.token_response_valid?(token_data, flow: :backend_services)
|
|
131
|
+
```
|
|
132
|
+
|
|
101
133
|
---
|
|
102
134
|
|
|
103
135
|
## Configuration
|
|
@@ -122,7 +154,7 @@ bin/demo
|
|
|
122
154
|
# Visit http://localhost:4567
|
|
123
155
|
```
|
|
124
156
|
|
|
125
|
-
Demonstrates SMART discovery, all authorization flows, and token
|
|
157
|
+
Demonstrates SMART discovery, all authorization flows, token refresh, and backend services token requests. See [`examples/sinatra_app/README.md`](examples/sinatra_app/README.md) for details.
|
|
126
158
|
|
|
127
159
|
---
|
|
128
160
|
|
data/ROADMAP.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Safire Roadmap
|
|
2
2
|
|
|
3
|
-
## Current Release — v0.0
|
|
3
|
+
## Current Release — v0.1.0
|
|
4
4
|
|
|
5
5
|
Safire is in early development (pre-release). The API is functional but not yet stable — breaking changes may occur before v1.0.0. Published to [RubyGems](https://rubygems.org/gems/safire).
|
|
6
6
|
|
|
@@ -10,7 +10,7 @@ Feedback, bug reports, and pull requests are welcome via the [issue tracker](htt
|
|
|
10
10
|
|
|
11
11
|
## Implemented Features
|
|
12
12
|
|
|
13
|
-
### SMART
|
|
13
|
+
### SMART App Launch (v2.2.0)
|
|
14
14
|
|
|
15
15
|
- **Discovery** — lazy fetch of `/.well-known/smart-configuration`; metadata cached per client instance
|
|
16
16
|
- **Public Client** — PKCE-only authorization code flow (RS256/ES256)
|
|
@@ -19,14 +19,14 @@ Feedback, bug reports, and pull requests are welcome via the [issue tracker](htt
|
|
|
19
19
|
- **POST-Based Authorization** — form-encoded authorization requests
|
|
20
20
|
- **JWT Assertion Builder** — signed JWT assertions with configurable `kid` and expiry
|
|
21
21
|
- **PKCE** — automatic code verifier and challenge generation
|
|
22
|
+
- **Backend Services** — `client_credentials` grant for system-to-system flows; JWT assertion (RS384/ES384); no user interaction, redirect, or PKCE required; scope defaults to `system/*.rs` when not configured
|
|
22
23
|
|
|
23
24
|
---
|
|
24
25
|
|
|
25
26
|
## Planned Features
|
|
26
27
|
|
|
27
|
-
### SMART
|
|
28
|
+
### SMART App Launch
|
|
28
29
|
|
|
29
|
-
- **Backend Services** — `client_credentials` grant for system-to-system flows (no user interaction)
|
|
30
30
|
- **Dynamic Client Registration** — programmatic client registration per [RFC 7591](https://www.rfc-editor.org/rfc/rfc7591)
|
|
31
31
|
|
|
32
32
|
### UDAP Security
|
data/docs/Gemfile.lock
CHANGED
|
@@ -4,7 +4,7 @@ GEM
|
|
|
4
4
|
addressable (2.8.8)
|
|
5
5
|
public_suffix (>= 2.0.2, < 8.0)
|
|
6
6
|
base64 (0.3.0)
|
|
7
|
-
bigdecimal (4.
|
|
7
|
+
bigdecimal (4.1.1)
|
|
8
8
|
colorator (1.1.0)
|
|
9
9
|
concurrent-ruby (1.3.6)
|
|
10
10
|
csv (3.3.5)
|
|
@@ -140,7 +140,7 @@ DEPENDENCIES
|
|
|
140
140
|
CHECKSUMS
|
|
141
141
|
addressable (2.8.8) sha256=7c13b8f9536cf6364c03b9d417c19986019e28f7c00ac8132da4eb0fe393b057
|
|
142
142
|
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
|
143
|
-
bigdecimal (4.
|
|
143
|
+
bigdecimal (4.1.1) sha256=1c09efab961da45203c8316b0cdaec0ff391dfadb952dd459584b63ebf8054ca
|
|
144
144
|
colorator (1.1.0) sha256=e2f85daf57af47d740db2a32191d1bdfb0f6503a0dfbc8327d0c9154d5ddfc38
|
|
145
145
|
concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
|
|
146
146
|
csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f
|
data/docs/_config.yml
CHANGED
|
@@ -19,7 +19,10 @@
|
|
|
19
19
|
# in the templates via {{ site.myvariable }}.
|
|
20
20
|
|
|
21
21
|
title: Safire Documentation
|
|
22
|
-
|
|
22
|
+
tagline: Ruby gem for SMART App Launch and UDAP protocols
|
|
23
|
+
description: SMART App Launch and UDAP implementation library for Ruby
|
|
24
|
+
author: Vanessa Fotso
|
|
25
|
+
lang: en-US
|
|
23
26
|
baseurl: /safire
|
|
24
27
|
url: https://vanessuniq.github.io
|
|
25
28
|
|
|
@@ -13,7 +13,7 @@ nav_order: 2
|
|
|
13
13
|
|
|
14
14
|
## Context
|
|
15
15
|
|
|
16
|
-
Safire must support multiple authorization protocols (SMART
|
|
16
|
+
Safire must support multiple authorization protocols (SMART App Launch, UDAP) from a single public entry point. There are several ways to structure this:
|
|
17
17
|
|
|
18
18
|
**Option A — Monolithic `Client`:** implement all protocol logic directly inside `Safire::Client`. Simple at first, but grows unbounded as each protocol adds methods, and makes it impossible to test protocol logic in isolation.
|
|
19
19
|
|
|
@@ -13,7 +13,7 @@ nav_order: 3
|
|
|
13
13
|
|
|
14
14
|
## Context
|
|
15
15
|
|
|
16
|
-
`Safire::Client` needs to support multiple healthcare authorization protocols (SMART
|
|
16
|
+
`Safire::Client` needs to support multiple healthcare authorization protocols (SMART, UDAP) and, within SMART, multiple client authentication methods (public, confidential symmetric, confidential asymmetric). There are two ways to model this:
|
|
17
17
|
|
|
18
18
|
**Option A — flat enum:** a single parameter enumerating every combination.
|
|
19
19
|
|
|
@@ -13,7 +13,7 @@ nav_order: 6
|
|
|
13
13
|
|
|
14
14
|
## Context
|
|
15
15
|
|
|
16
|
-
SMART
|
|
16
|
+
SMART clients need the authorization server's endpoints (`authorization_endpoint`, `token_endpoint`) to build authorization URLs and request tokens. These are obtained by fetching `/.well-known/smart-configuration`. There are two approaches:
|
|
17
17
|
|
|
18
18
|
**Option A — eager discovery:** fetch metadata in `Smart#initialize`.
|
|
19
19
|
|
|
@@ -25,7 +25,7 @@ The question is: what should compliance checks do when they find a violation?
|
|
|
25
25
|
|
|
26
26
|
**Option B — Warn and return false:** log a warning via `Safire.logger` for each violation found, then return `false`. Never raise.
|
|
27
27
|
|
|
28
|
-
Option A treats a non-compliant server as an unrecoverable error. In practice, some production FHIR servers have minor token response non-compliance (e.g. `token_type: "
|
|
28
|
+
Option A treats a non-compliant server as an unrecoverable error. In practice, some production FHIR servers have minor token response non-compliance (e.g. returning `token_type: "BEARER"` instead of the spec-required value) but are otherwise functional. Raising an exception would prevent Safire from working with those servers entirely, with no way for callers to override the decision.
|
|
29
29
|
|
|
30
30
|
Option B lets the caller decide what to do: they can check the return value, observe the warnings in their logs, and choose to proceed or abort. This is consistent with how Ruby standard library methods (e.g. `URI.parse`, `JSON.parse` with `rescue nil`) handle validation — surface the issue, let the caller decide.
|
|
31
31
|
|
|
@@ -38,9 +38,9 @@ There is also a clear boundary: **the caller controls the config** (configuratio
|
|
|
38
38
|
Compliance validation methods use the **warn + return false** pattern:
|
|
39
39
|
|
|
40
40
|
```ruby
|
|
41
|
-
def token_response_valid?(response)
|
|
41
|
+
def token_response_valid?(response, flow: :app_launch)
|
|
42
42
|
# ...
|
|
43
|
-
Safire.logger.warn("SMART token response non-compliance: token_type is #{...}; expected 'Bearer'")
|
|
43
|
+
Safire.logger.warn("SMART token response non-compliance: token_type is #{...}; expected 'Bearer' (SMART App Launch spec)")
|
|
44
44
|
false
|
|
45
45
|
end
|
|
46
46
|
|
|
@@ -72,3 +72,4 @@ Configuration validation (`ClientConfig#validate!`, `Smart#validate!`) raises `C
|
|
|
72
72
|
**Trade-offs:**
|
|
73
73
|
- Callers who do not call `token_response_valid?` get no compliance signal at all — non-compliant responses are silently accepted; this is intentional (opt-in, not opt-out)
|
|
74
74
|
- The distinction between "warn + return false" and "raise" must be maintained consistently — new validation methods should follow the same rule: server behaviour → warn; caller configuration → raise
|
|
75
|
+
- `token_response_valid?` accepts a `flow:` keyword argument (`:app_launch` default, `:backend_services`) that adjusts which fields are required and what the warning messages say. For example, `token_type` must be `"Bearer"` (App Launch spec) or `"bearer"` (Backend Services spec), and `expires_in` is RECOMMENDED for App Launch but REQUIRED for Backend Services. Callers opt in to the stricter backend-services validation by passing `flow: :backend_services`
|
|
@@ -23,10 +23,12 @@ Pass configuration as a Hash — Safire wraps it in a `ClientConfig` automatical
|
|
|
23
23
|
|
|
24
24
|
```ruby
|
|
25
25
|
client = Safire::Client.new(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
{
|
|
27
|
+
base_url: 'https://fhir.example.com/r4',
|
|
28
|
+
client_id: 'my_client_id',
|
|
29
|
+
redirect_uri: 'https://myapp.com/callback',
|
|
30
|
+
scopes: ['openid', 'profile', 'patient/*.read']
|
|
31
|
+
}
|
|
30
32
|
)
|
|
31
33
|
```
|
|
32
34
|
|
|
@@ -105,7 +107,7 @@ metadata = client.server_metadata
|
|
|
105
107
|
client.client_type = :confidential_asymmetric if metadata.supports_asymmetric_auth?
|
|
106
108
|
```
|
|
107
109
|
|
|
108
|
-
For a decision guide on which
|
|
110
|
+
For a decision guide on which workflow to use, see [SMART App Launch — Choosing a Workflow]({{ site.baseurl }}/smart-on-fhir/).
|
|
109
111
|
|
|
110
112
|
---
|
|
111
113
|
|
|
@@ -122,7 +124,7 @@ The following attributes are validated:
|
|
|
122
124
|
| Attribute | Validated when |
|
|
123
125
|
|-----------|----------------|
|
|
124
126
|
| `base_url` | Always |
|
|
125
|
-
| `redirect_uri` |
|
|
127
|
+
| `redirect_uri` | When provided (required for App Launch; not used in Backend Services) |
|
|
126
128
|
| `issuer` | When provided (defaults to `base_url`) |
|
|
127
129
|
| `authorization_endpoint` | When provided |
|
|
128
130
|
| `token_endpoint` | When provided |
|
|
@@ -155,4 +157,4 @@ config.inspect
|
|
|
155
157
|
## Next Steps
|
|
156
158
|
|
|
157
159
|
- [Logging]({{ site.baseurl }}/configuration/logging/) — configure Safire's logger and HTTP request logging
|
|
158
|
-
- [SMART
|
|
160
|
+
- [SMART App Launch Workflows]({{ site.baseurl }}/smart-on-fhir/) — step-by-step authorization flow guides
|
data/docs/index.md
CHANGED
|
@@ -3,6 +3,7 @@ layout: default
|
|
|
3
3
|
title: Home
|
|
4
4
|
nav_order: 1
|
|
5
5
|
permalink: /
|
|
6
|
+
description: "Safire is a Ruby gem implementing SMART App Launch 2.2.0 and UDAP Security protocols for healthcare client applications, with full support for OAuth 2.0 authorization against HL7 FHIR servers."
|
|
6
7
|
---
|
|
7
8
|
|
|
8
9
|
# Safire Documentation
|
|
@@ -10,7 +11,7 @@ permalink: /
|
|
|
10
11
|
[](https://badge.fury.io/rb/safire)
|
|
11
12
|
[](https://github.com/vanessuniq/safire/actions)
|
|
12
13
|
|
|
13
|
-
A lean Ruby gem implementing **[SMART
|
|
14
|
+
A lean Ruby gem implementing **[SMART App Launch](https://hl7.org/fhir/smart-app-launch/)** and **[UDAP](https://hl7.org/fhir/us/udap-security/)** protocols for clients.
|
|
14
15
|
|
|
15
16
|
## Quick Navigation
|
|
16
17
|
|
|
@@ -18,7 +19,7 @@ A lean Ruby gem implementing **[SMART on FHIR](https://hl7.org/fhir/smart-app-la
|
|
|
18
19
|
|---------|-------------|
|
|
19
20
|
| [Getting Started]({{ site.baseurl }}/installation/) | Install Safire and quick start guide |
|
|
20
21
|
| [Configuration]({{ site.baseurl }}/configuration/) | All configuration options and parameters |
|
|
21
|
-
| [SMART
|
|
22
|
+
| [SMART]({{ site.baseurl }}/smart-on-fhir/) | App Launch (Public, Confidential Symmetric, Confidential Asymmetric) and Backend Services |
|
|
22
23
|
| [UDAP]({{ site.baseurl }}/udap/) | UDAP protocol overview and planned support |
|
|
23
24
|
| [Security Guide]({{ site.baseurl }}/security/) | HTTPS, credential protection, token storage, key rotation |
|
|
24
25
|
| [Advanced Examples]({{ site.baseurl }}/advanced/) | Caching, multi-server, token management, complete Rails integration |
|
|
@@ -27,13 +28,14 @@ A lean Ruby gem implementing **[SMART on FHIR](https://hl7.org/fhir/smart-app-la
|
|
|
27
28
|
|
|
28
29
|
## Features
|
|
29
30
|
|
|
30
|
-
### SMART
|
|
31
|
+
### SMART App Launch
|
|
31
32
|
|
|
32
33
|
- Discovery (`/.well-known/smart-configuration`)
|
|
33
34
|
- Public Client (PKCE)
|
|
34
35
|
- Confidential Symmetric Client (client_secret + Basic Auth)
|
|
35
36
|
- Confidential Asymmetric Client (private_key_jwt with RS384/ES384)
|
|
36
37
|
- POST-Based Authorization
|
|
38
|
+
- Backend Services (client_credentials grant, JWT assertion, no user interaction or PKCE)
|
|
37
39
|
|
|
38
40
|
### UDAP
|
|
39
41
|
|
|
@@ -47,7 +49,7 @@ A Sinatra-based demo app is included to help you explore Safire's features:
|
|
|
47
49
|
bin/demo
|
|
48
50
|
```
|
|
49
51
|
|
|
50
|
-
Visit http://localhost:4567 to test SMART discovery, authorization flows, and token
|
|
52
|
+
Visit http://localhost:4567 to test SMART discovery, authorization flows, token management, and backend services token requests.
|
|
51
53
|
|
|
52
54
|
See [`examples/sinatra_app/README.md`](https://github.com/vanessuniq/safire/tree/main/examples/sinatra_app) for details.
|
|
53
55
|
|
data/docs/installation.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
layout: default
|
|
3
3
|
title: Installation
|
|
4
4
|
nav_order: 2
|
|
5
|
+
description: "How to install the Safire Ruby gem and get started with SMART App Launch and UDAP authorization in your healthcare application."
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
# Installation
|
|
@@ -71,10 +72,12 @@ A quick smoke test to confirm the gem is installed and SMART discovery is workin
|
|
|
71
72
|
require 'safire'
|
|
72
73
|
|
|
73
74
|
client = Safire::Client.new(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
{
|
|
76
|
+
base_url: 'https://launch.smarthealthit.org/v/r4/sim/eyJoIjoiMSJ9/fhir',
|
|
77
|
+
client_id: 'test',
|
|
78
|
+
redirect_uri: 'https://example.com/callback',
|
|
79
|
+
scopes: ['openid', 'profile', 'patient/*.read']
|
|
80
|
+
}
|
|
78
81
|
)
|
|
79
82
|
|
|
80
83
|
metadata = client.server_metadata
|
|
@@ -91,6 +94,6 @@ If you see an authorization endpoint URL, the gem is working. For troubleshootin
|
|
|
91
94
|
| | |
|
|
92
95
|
|-|-|
|
|
93
96
|
| [Configuration]({{ site.baseurl }}/configuration/) | Client credentials, logging, and protocol selection |
|
|
94
|
-
| [SMART
|
|
97
|
+
| [SMART App Launch]({{ site.baseurl }}/smart-on-fhir/) | Authorization flows for public, confidential clients, and Backend Services |
|
|
95
98
|
| [Security Guide]({{ site.baseurl }}/security/) | HTTPS requirements, credential protection, token storage |
|
|
96
99
|
| [API Reference]({{ site.baseurl }}/api/){:target="_blank"} | Complete YARD documentation |
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Backend Services Workflow
|
|
4
|
+
parent: SMART
|
|
5
|
+
nav_order: 6
|
|
6
|
+
has_children: true
|
|
7
|
+
permalink: /smart-on-fhir/backend-services/
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Backend Services Workflow
|
|
11
|
+
|
|
12
|
+
{: .no_toc }
|
|
13
|
+
|
|
14
|
+
<div class="code-example" markdown="1">
|
|
15
|
+
System-to-system access token requests using the OAuth 2.0 `client_credentials` grant and mandatory JWT assertion authentication — no user interaction, redirect URI, or PKCE required.
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Overview
|
|
21
|
+
|
|
22
|
+
SMART Backend Services enables autonomous server-to-server FHIR access without involving a user. It is defined in the [SMART App Launch Backend Services](https://hl7.org/fhir/smart-app-launch/backend-services.html) specification.
|
|
23
|
+
|
|
24
|
+
Instead of a redirect flow, the client:
|
|
25
|
+
|
|
26
|
+
1. Registers with the authorization server following the [confidential asymmetric registration steps](https://hl7.org/fhir/smart-app-launch/client-confidential-asymmetric.html#registering-a-client-communicating-public-keys) (required by the spec)
|
|
27
|
+
2. Builds a signed JWT assertion using its registered private key
|
|
28
|
+
3. Posts the assertion to the token endpoint with `grant_type=client_credentials`
|
|
29
|
+
4. Receives an access token directly — no authorization code, no callback, no PKCE
|
|
30
|
+
|
|
31
|
+
Suitable for:
|
|
32
|
+
|
|
33
|
+
- Scheduled data pipelines and batch jobs
|
|
34
|
+
- System integrations that run without a logged-in user
|
|
35
|
+
- Clinical quality reporting and analytics platforms
|
|
36
|
+
- Any server-to-server FHIR workflow
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Key Differences from App Launch
|
|
41
|
+
|
|
42
|
+
| Aspect | App Launch | Backend Services |
|
|
43
|
+
|--------|------------|-----------------|
|
|
44
|
+
| **Grant type** | `authorization_code` | `client_credentials` |
|
|
45
|
+
| **User interaction** | Required | None |
|
|
46
|
+
| **Redirect URI** | Required | Not used |
|
|
47
|
+
| **PKCE** | Required | Not used |
|
|
48
|
+
| **Client auth** | Varies by `client_type:` | JWT assertion always |
|
|
49
|
+
| **Scopes** | `patient/`, `user/`, `openid` | `system/` |
|
|
50
|
+
| **Refresh token** | Usually issued | Not issued |
|
|
51
|
+
| **`expires_in`** | Recommended | Required |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Prerequisites: Registration, Keys, and JWKS
|
|
56
|
+
|
|
57
|
+
### Client Registration
|
|
58
|
+
|
|
59
|
+
Before making any token requests, the client **SHALL** register with the authorization server following the [confidential asymmetric client registration](https://hl7.org/fhir/smart-app-launch/client-confidential-asymmetric.html#registering-a-client-communicating-public-keys) steps defined in the SMART App Launch specification. Registration communicates the client's public key(s) to the server, either via a JWKS URI or by uploading the JWKS directly.
|
|
60
|
+
|
|
61
|
+
### Key Pair and JWKS
|
|
62
|
+
|
|
63
|
+
Backend services use the same RSA or EC key pair infrastructure as the confidential asymmetric app launch flow — key generation, JWKS publishing, and algorithm selection are identical. See the [Confidential Asymmetric Client — Prerequisites]({% link smart-on-fhir/confidential-asymmetric/index.md %}#prerequisites-keys-jwks-and-algorithm) guide for full details.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Client Setup
|
|
68
|
+
|
|
69
|
+
`redirect_uri` and `scopes` are optional for backend services clients. If `scopes` is omitted, Safire defaults to `["system/*.rs"]` when `request_backend_token` is called.
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
config = Safire::ClientConfig.new(
|
|
73
|
+
base_url: ENV['FHIR_BASE_URL'],
|
|
74
|
+
client_id: ENV['SMART_CLIENT_ID'],
|
|
75
|
+
private_key: OpenSSL::PKey::RSA.new(ENV['SMART_PRIVATE_KEY_PEM']),
|
|
76
|
+
kid: ENV['SMART_KEY_ID'],
|
|
77
|
+
scopes: ['system/Patient.rs', 'system/Observation.rs'] # optional
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
client = Safire::Client.new(config)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
{: .note }
|
|
84
|
+
> `client_type:` is not used for backend services — `request_backend_token` always authenticates via JWT assertion regardless of `client_type`.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## What's Next
|
|
89
|
+
|
|
90
|
+
- [Token Request]({% link smart-on-fhir/backend-services/token-request.md %}) — Requesting a token, scope/credential overrides, flow diagram, validation, error handling, and proactive renewal
|
|
91
|
+
- [Security Guide]({{ site.baseurl }}/security/) — Private key management and rotation
|
|
92
|
+
- [Confidential Asymmetric Client]({% link smart-on-fhir/confidential-asymmetric/index.md %}) — The app launch counterpart that shares the same key infrastructure
|