Troubleshooting
Common issues and solutions when integrating with Safire.
Table of contents
Error Types
Safire raises typed errors so you can handle each failure category separately:
| Error class | When raised |
|---|---|
Safire::Errors::ConfigurationError | Missing or incompatible configuration; URI and shape errors may be caught at construction, while flow-specific requirements are checked when used |
Safire::Errors::DiscoveryError | SMART or UDAP metadata discovery failed (HTTP error, invalid JSON, missing SMART token_endpoint when required, or UDAP signed_metadata validation failure) |
Safire::Errors::ValidationError | Caller-controlled input is invalid before a request is sent, such as UDAP registration metadata or certification JWT shape |
Safire::Errors::CertificateError | UDAP x5c certificate data could not be parsed or the configured UDAP client certificate chain cannot support signing |
Safire::Errors::RegistrationError | Dynamic Client Registration failed (server error, 2xx response with a missing or invalid client_id, or UDAP cancellation response without an empty grant_types array) |
Safire::Errors::TokenError | Token exchange or refresh failed (OAuth error, missing fields) |
Safire::Errors::NetworkError | Transport-level failure (connection refused, timeout, blocked redirect) |
RegistrationError, TokenError, and AuthError share a common base class Safire::Errors::OAuthError with status, error_code, and error_description attributes — you can rescue all three in one clause using OAuthError. All Safire errors inherit from Safire::Errors::Error for a single catch-all rescue.
begin
tokens = client.request_access_token(code: code, code_verifier: verifier)
rescue Safire::Errors::ConfigurationError => e
# Client misconfiguration — fix before retrying
Rails.logger.error("Configuration error: #{e.message}")
render plain: 'Server configuration error', status: :internal_server_error
rescue Safire::Errors::TokenError => e
# OAuth error — e.status, e.error_code, e.error_description are all available
Rails.logger.error("Token error: #{e.message}")
redirect_to launch_path, alert: 'Authorization failed. Please try again.'
rescue Safire::Errors::NetworkError => e
Rails.logger.error("Network error: #{e.message}")
render plain: 'Server temporarily unavailable', status: :service_unavailable
end
Discovery Errors
SMART discovery
SMART clients fetch /.well-known/smart-configuration lazily when metadata or an auth flow needs it. A DiscoveryError means the SMART metadata endpoint returned an HTTP error, did not provide a usable JSON object, or a token request needed discovery but the response did not include token_endpoint.
Discovery parses valid raw JSON-object strings even when an incorrect or missing response content type prevents Faraday from decoding the body. That tolerance does not make the server response conformant. Malformed JSON, JSON arrays or scalars, and adapter-provided Hashes with ambiguous normalized keys, cycles, or non-JSON-compatible values are treated as unusable JSON-object responses.
begin
metadata = smart_client.server_metadata
rescue Safire::Errors::DiscoveryError => e
Rails.logger.error("SMART discovery failed: #{e.message}")
end
UDAP discovery
UDAP clients fetch /.well-known/udap and validate the required signed_metadata JWT before returning metadata. In production, pass trust anchors plus CRLs or a custom revocation checker:
metadata = udap_client.server_metadata(
trusted_anchors: [ca_cert],
crls: [ca_crl]
)
Common UDAP discovery outcomes:
| Response or condition | Meaning |
|---|---|
404 Not Found | Per UDAP Security STU2, treat this as “UDAP workflows are not supported” for that server |
204 No Content | No UDAP workflows are supported for the requested community:; Safire raises DiscoveryError before parsing the body |
signed_metadata validation failed | Safire could not validate the JWT signature, certificate chain, revocation status, issuer/SAN relationship, required claims, or signed endpoint URLs |
ConfigurationError for community: | The community value was not a URI string; Safire raises before making an HTTP request |
For development and tests only, verify_chain: false skips X.509 chain and revocation validation:
metadata = udap_client.server_metadata(verify_chain: false)
Never use verify_chain: false in production.
Debugging
Enable detailed logging
Safire.configure do |config|
config.logger = Rails.logger
config.log_level = Logger::DEBUG
end
HTTP request logging is on by default. Sensitive headers (Authorization) are always filtered. Request and response bodies are never logged.
INFO: request: POST https://fhir.example.com/token
INFO: request: Authorization: [FILTERED]
INFO: response: Status 200
To disable HTTP logging:
Safire.configure { |c| c.log_http = false }
Test against the SMART reference server
# .env.development
FHIR_BASE_URL=https://launch.smarthealthit.org/v/r4/sim/eyJoIjoiMSJ9/fhir
Visit launch.smarthealthit.org to configure simulated patients and launch contexts.
Test discovery endpoints manually
curl https://fhir.example.com/.well-known/smart-configuration
curl https://fhir.example.com/.well-known/udap
curl 'https://fhir.example.com/.well-known/udap?community=https%3A%2F%2Fudap.example.org%2Fcommunity1'
SMART metadata is unsigned JSON. UDAP metadata must include signed_metadata; Safire validates that JWT before returning UdapMetadata.
Getting Help
- Check the logs first — Safire logs one line per error with all relevant context
- Test endpoints manually — check the SMART or UDAP well-known endpoint for the protocol you selected
- Open an issue — github.com/vanessuniq/safire/issues
When reporting an issue, include: Safire version (Safire::VERSION), Ruby version, the error message and backtrace, and the server type if known. Never include credentials or tokens.