Safire
Safire is a Ruby gem implementing the SMART App Launch 2.2.0 specification and the 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.
Features
SMART App Launch (v2.2.0)
- Dynamic Client Registration (RFC 7591): obtain a
client_idat runtime by POSTing client metadata to the server's registration endpoint - Discovery (
/.well-known/smart-configuration) - Public Client (PKCE)
- Confidential Symmetric Client (
client_secret+ HTTP Basic Auth) - Confidential Asymmetric Client (
private_key_jwtwith RS384/ES384) - POST-Based Authorization
- Backend Services (
client_credentialsgrant, JWT assertion, no user interaction or PKCE; scope defaults tosystem/*.rs)
UDAP
Planned. See ROADMAP.md for details.
Installation
Requires Ruby ≥ 4.0.2.
gem 'safire'
bundle install
Quick Start
require 'safire'
# Step 1 — Create a client (Hash config or Safire::ClientConfig.new)
client = Safire::Client.new(
{
base_url: 'https://launch.smarthealthit.org/v/r4/sim/eyJoIjoiMSJ9/fhir',
client_id: 'my_client_id',
redirect_uri: 'https://myapp.example.com/callback',
scopes: ['openid', 'profile', 'patient/*.read']
}
)
# Step 2 — Discover SMART metadata (lazy — only called when needed)
= client.
puts .
puts .capabilities.join(', ')
# Step 3 — Build the authorization URL (Safire generates state + PKCE automatically)
auth_data = client.
# auth_data => { auth_url:, state:, code_verifier: }
# Store state and code_verifier server-side, redirect the user to auth_data[:auth_url]
# Step 4 — Exchange the authorization code for tokens (on callback)
token_data = client.request_access_token(
code: params[:code],
code_verifier: session[:code_verifier]
)
# token_data => { "access_token" => "...", "token_type" => "Bearer", ... }
# Step 5 — Refresh when the access token expires
new_tokens = client.refresh_token(refresh_token: token_data['refresh_token'])
Supported SMART Client Types
client_type: |
Authentication | When to use |
|---|---|---|
:public (default) |
PKCE only | Browser/mobile apps that cannot store a secret |
:confidential_symmetric |
HTTP Basic Auth (client_secret) |
Server-side apps with a securely stored secret |
:confidential_asymmetric |
JWT assertion (private_key_jwt, RS384/ES384) |
Server-side apps using a registered key pair |
For a confidential asymmetric client, provide a private key and key ID:
client = Safire::Client.new(
{
base_url: 'https://fhir.example.com',
client_id: 'my_client_id',
redirect_uri: 'https://myapp.example.com/callback',
scopes: ['openid', 'profile', 'patient/*.read'],
private_key: OpenSSL::PKey::RSA.new(File.read('private_key.pem')),
kid: 'my-key-id-123'
},
client_type: :confidential_asymmetric
)
# Authorization and token exchange are identical — Safire builds the JWT assertion automatically
Backend Services (system-to-system)
No user interaction, redirect URI, or PKCE required — the client authenticates entirely via a signed JWT assertion:
client = Safire::Client.new(
{
base_url: 'https://fhir.example.com',
client_id: 'my_backend_client',
private_key: OpenSSL::PKey::RSA.new(File.read('private_key.pem')),
kid: 'my-key-id-123',
scopes: ['system/Patient.rs', 'system/Observation.rs']
}
)
token_data = client.request_backend_token
# token_data => { "access_token" => "...", "token_type" => "Bearer", "expires_in" => 300, ... }
# Override scope or credentials per call
token_data = client.request_backend_token(
scopes: ['system/Patient.rs'],
private_key: OpenSSL::PKey::RSA.new(File.read('new_key.pem')),
kid: 'new-key-id'
)
# Validate the token response (flow: :backend_services also checks expires_in)
client.token_response_valid?(token_data, flow: :backend_services)
Configuration
Safire.configure do |config|
config.logger = Rails.logger # Default: $stdout
config.log_http = true # Log HTTP requests (sensitive headers always filtered)
end
See the Configuration Guide for all options including user_agent, log_level, and SSL settings.
Demo Application
A Sinatra-based demo is included in examples/sinatra_app/:
bin/demo
# Visit http://localhost:4567
Demonstrates Dynamic Client Registration, SMART discovery, all authorization flows, token refresh, and backend services token requests. See examples/sinatra_app/README.md for details.
Development
bin/setup # Install dependencies
bundle exec rspec # Run tests
bin/console # Interactive prompt
To serve the docs locally:
bin/docs
cd docs && bundle install && bundle exec jekyll serve
# Visit http://localhost:4000/safire/
Contributing
Bug reports and pull requests are welcome. Please read CONTRIBUTION.md before opening a PR — it covers branch naming, commit message style, and the sign-off requirement.
License
Available as open source under the Apache 2.0 License.
Parts of this project were developed with AI assistance (Claude Code) and reviewed by maintainers.