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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -1
  3. data/Gemfile +1 -0
  4. data/Gemfile.lock +3 -1
  5. data/README.md +39 -7
  6. data/ROADMAP.md +4 -4
  7. data/docs/Gemfile.lock +2 -2
  8. data/docs/_config.yml +4 -1
  9. data/docs/adr/ADR-002-facade-and-forwardable.md +1 -1
  10. data/docs/adr/ADR-003-protocol-vs-client-type.md +1 -1
  11. data/docs/adr/ADR-006-lazy-discovery.md +1 -1
  12. data/docs/adr/ADR-008-warn-return-false-for-compliance-validation.md +4 -3
  13. data/docs/configuration/client-setup.md +9 -7
  14. data/docs/index.md +6 -4
  15. data/docs/installation.md +8 -5
  16. data/docs/smart-on-fhir/backend-services/index.md +92 -0
  17. data/docs/smart-on-fhir/backend-services/token-request.md +207 -0
  18. data/docs/smart-on-fhir/confidential-asymmetric/authorization.md +1 -1
  19. data/docs/smart-on-fhir/confidential-asymmetric/index.md +6 -3
  20. data/docs/smart-on-fhir/confidential-asymmetric/token-exchange.md +1 -1
  21. data/docs/smart-on-fhir/confidential-symmetric/authorization.md +1 -1
  22. data/docs/smart-on-fhir/confidential-symmetric/index.md +2 -2
  23. data/docs/smart-on-fhir/confidential-symmetric/token-exchange.md +1 -1
  24. data/docs/smart-on-fhir/discovery/capability-checks.md +25 -1
  25. data/docs/smart-on-fhir/discovery/index.md +3 -3
  26. data/docs/smart-on-fhir/discovery/metadata.md +1 -1
  27. data/docs/smart-on-fhir/index.md +22 -13
  28. data/docs/smart-on-fhir/post-based-authorization.md +1 -1
  29. data/docs/smart-on-fhir/public-client/authorization.md +1 -1
  30. data/docs/smart-on-fhir/public-client/index.md +2 -2
  31. data/docs/smart-on-fhir/public-client/token-exchange.md +1 -1
  32. data/docs/troubleshooting/auth-errors.md +4 -1
  33. data/docs/troubleshooting/client-errors.md +30 -0
  34. data/docs/troubleshooting/index.md +1 -1
  35. data/docs/udap.md +4 -3
  36. data/lib/safire/client.rb +28 -19
  37. data/lib/safire/client_config.rb +5 -5
  38. data/lib/safire/jwt_assertion.rb +1 -1
  39. data/lib/safire/protocols/behaviours.rb +6 -0
  40. data/lib/safire/protocols/smart.rb +95 -20
  41. data/lib/safire/protocols/smart_metadata.rb +11 -2
  42. data/lib/safire/version.rb +1 -1
  43. data/safire.gemspec +6 -2
  44. metadata +9 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f9d11ee8ab8db74c9b225f12835caaf092616a0044372252617783b6187e3043
4
- data.tar.gz: 8d73cdb90ed9d4ca7ec1e028bfe3e68464c7284f73e2d101a084be346e9b0085
3
+ metadata.gz: b9d2b84cc65eff523a4f1a8c86c5c2fe4f2089917062c115a12fb840e52369b4
4
+ data.tar.gz: 0f9cb348d913947546dff15f2d5d7a7904ca62dfdbce01dd8117598811ce5ec9
5
5
  SHA512:
6
- metadata.gz: bf3cd169be16cb9598f2de3f572d0efeeb6c0ed98826b0690588c684e15c2a95afc6cf340d35a53c0f6d9eebc11c59dafdcdd02e71b0ddb8fbf629071644d90b
7
- data.tar.gz: 6beaec7923441fb46d8fc4cc193ffff16354bb60fb62f141a3a36131e22f275df3d4ddfa31dd5548eebb6ad3a8872cd952c4cc4ab9dc31968b9eb644b0d097be
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 on FHIR App Launch 2.2.0 support via `Safire::Protocols::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
@@ -20,6 +20,7 @@ group :development do
20
20
  end
21
21
 
22
22
  group :test do
23
+ gem 'dotenv', '~> 3.0'
23
24
  gem 'simplecov', require: false
24
25
  gem 'simplecov-cobertura', require: false
25
26
  gem 'webmock', '~> 3.18'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- safire (0.1.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
  [![Coverage](https://codecov.io/gh/vanessuniq/safire/branch/main/graph/badge.svg)](https://codecov.io/gh/vanessuniq/safire)
6
6
  [![Documentation](https://img.shields.io/badge/docs-yard-blue.svg)](https://vanessuniq.github.io/safire)
7
7
 
8
- Safire is a lean Ruby library that implements [SMART on FHIR](https://hl7.org/fhir/smart-app-launch/) and [UDAP](https://hl7.org/fhir/us/udap-security/) client protocols for healthcare applications.
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 on FHIR App Launch (v2.2.0)
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
- base_url: 'https://launch.smarthealthit.org/v/r4/sim/eyJoIjoiMSJ9/fhir',
50
- client_id: 'my_client_id',
51
- redirect_uri: 'https://myapp.example.com/callback',
52
- scopes: ['openid', 'profile', 'patient/*.read']
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 refresh. See [`examples/sinatra_app/README.md`](examples/sinatra_app/README.md) for details.
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.1
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 on FHIR App Launch (v2.2.0)
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 on FHIR
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.0.1)
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.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
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
- description: SMART on FHIR and UDAP implementation library for Ruby
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 on FHIR, UDAP) from a single public entry point. There are several ways to structure this:
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 on FHIR, UDAP) and, within SMART, multiple client authentication methods (public, confidential symmetric, confidential asymmetric). There are two ways to model this:
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 on FHIR 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:
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: "bearer"` in lowercase rather than `"Bearer"`) 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.
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
- base_url: 'https://fhir.example.com/r4',
27
- client_id: 'my_client_id',
28
- redirect_uri: 'https://myapp.com/callback',
29
- scopes: ['openid', 'profile', 'patient/*.read']
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 client type to use, see [SMART on FHIR — Choosing a Client Type]({{ site.baseurl }}/smart-on-fhir/).
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` | Always |
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 on FHIR Workflows]({{ site.baseurl }}/smart-on-fhir/) — step-by-step authorization flow guides
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
  [![Gem Version](https://badge.fury.io/rb/safire.svg)](https://badge.fury.io/rb/safire)
11
12
  [![CI](https://github.com/vanessuniq/safire/workflows/CI/badge.svg)](https://github.com/vanessuniq/safire/actions)
12
13
 
13
- A lean Ruby gem implementing **[SMART on FHIR](https://hl7.org/fhir/smart-app-launch/)** and **[UDAP](https://hl7.org/fhir/us/udap-security/)** protocols for clients.
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 on FHIR]({{ site.baseurl }}/smart-on-fhir/) | Discovery, Public clients, Confidential clients |
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 on FHIR App Launch
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 management.
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
- base_url: 'https://launch.smarthealthit.org/v/r4/sim/eyJoIjoiMSJ9/fhir',
75
- client_id: 'test',
76
- redirect_uri: 'https://example.com/callback',
77
- scopes: ['openid', 'profile', 'patient/*.read']
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 on FHIR]({{ site.baseurl }}/smart-on-fhir/) | Authorization flows for public and confidential clients |
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