Skip to content

Authentication

OctoFHIR implements SMART on FHIR authorization based on OAuth 2.0 and OpenID Connect.

EndpointDescription
GET /.well-known/smart-configurationSMART configuration metadata
GET /.well-known/jwks.jsonServer’s public keys for JWT verification
EndpointDescription
GET /auth/authorizeAuthorization endpoint (interactive user login & consent)
POST /auth/authorizeAuthorization form submission (login/consent actions)
POST /auth/tokenToken endpoint (exchange codes, refresh tokens)
POST /auth/revokeToken revocation (RFC 7009)
POST /auth/introspectToken introspection (RFC 7662)
GET /auth/userinfoOpenID Connect UserInfo endpoint
POST /auth/logoutBrowser-based logout (revokes token, clears cookie)
  • authorization_code - Standard OAuth flow with PKCE
  • client_credentials - Machine-to-machine (system scope)
  • refresh_token - Refresh access tokens
TypeAuthenticationPKCE RequirementUse Case
PublicPKCE onlyRequired (RFC 8252)Browser apps, mobile apps
Confidential (symmetric)client_secretRecommended (RFC 9207)Server-side apps
Confidential (asymmetric)private_key_jwtRecommended (RFC 9207)Bulk Data, secure backends

OctoFHIR enforces PKCE requirements according to OAuth 2.0 security best practices:

  • Public clients (confidential: false): PKCE is REQUIRED. Authorization requests without code_challenge and code_challenge_method will be rejected.
  • Confidential clients (confidential: true): PKCE is RECOMMENDED but optional. A warning is logged if PKCE is not used.
  • Only S256 challenge method is supported (SHA-256). The plain method is forbidden per SMART on FHIR security requirements.
  • launch - EHR launch (receive context from EHR)
  • launch/patient - Standalone launch with patient picker
  • launch/encounter - Standalone launch with encounter picker

Format: {context}/{resourceType}.{permissions}

  • Context: patient, user, system
  • Permissions: c (create), r (read), u (update), d (delete), s (search)

Examples:

patient/Patient.rs # Read/search Patient in patient context
user/Observation.cruds # Full access to Observations for logged-in user
system/*.rs # Read/search all resources (backend service)
  • openid - Enable OpenID Connect (required for /userinfo)
  • fhirUser - Include FHIR resource reference in token/userinfo

All auth settings are in octofhir.toml under [auth]:

[auth]
enabled = true
issuer = "http://localhost:8888"
[auth.oauth]
access_token_lifetime = "1h"
refresh_token_lifetime = "90d"
grant_types = ["authorization_code", "client_credentials", "refresh_token"]
[auth.smart]
launch_ehr_enabled = true
launch_standalone_enabled = true
public_clients_allowed = true
openid_enabled = true
supported_scopes = ["openid", "fhirUser", "patient/*.cruds", "..."]
[auth.signing]
algorithm = "RS384" # RS256, RS384, or ES384
key_rotation_days = 90

See octofhir.toml for the full configuration reference including:

  • [auth.policy] - Access policy engine settings
  • [auth.federation] - External IdP integration
  • [auth.rate_limiting] - Request rate limits
  • [auth.audit] - Audit logging options
Terminal window
# 1. Redirect user to authorize
open "http://localhost:8888/auth/authorize?\
client_id=my-app&\
redirect_uri=http://localhost:3000/callback&\
response_type=code&\
scope=openid%20fhirUser%20patient/Patient.rs&\
state=random-state&\
code_challenge=BASE64URL_CHALLENGE&\
code_challenge_method=S256"
# 2. Exchange code for tokens
curl -X POST http://localhost:8888/auth/token \
-d "grant_type=authorization_code" \
-d "code=AUTHORIZATION_CODE" \
-d "redirect_uri=http://localhost:3000/callback" \
-d "client_id=my-app" \
-d "code_verifier=ORIGINAL_VERIFIER"
# 3. Use access token
curl -H "Authorization: Bearer ACCESS_TOKEN" \
http://localhost:8888/Patient
Terminal window
curl -X POST http://localhost:8888/auth/token \
-u "client_id:client_secret" \
-d "grant_type=client_credentials" \
-d "scope=system/Patient.rs"

The /auth/authorize endpoint provides a server-rendered user interface for authentication and consent. This enables standard OAuth 2.0 authorization code flow with a user-friendly login experience.

  1. Initial Request - Client redirects user to /auth/authorize with OAuth parameters
  2. Authentication - User sees a login form and enters credentials
  3. Consent - After successful login, user sees requested scopes and can approve/deny
  4. Authorization Code - User is redirected back to client with authorization code
  5. Token Exchange - Client exchanges code for access token at /auth/token
  • Server-rendered UI with modern glassmorphism design matching OctoFHIR’s aesthetics
  • Session management via secure HTTP-only cookies
  • Consent tracking - remembers user’s previous consent decisions
  • PKCE validation - enforced based on client type (public vs confidential)
  • Error handling - user-friendly error pages with detailed diagnostics
ParameterRequiredDescription
response_typeYesMust be code
client_idYesClient identifier
redirect_uriYesMust match registered redirect URI
scopeYesRequested scopes (space-separated)
stateYesCSRF protection (min 122 bits entropy)
code_challengeConditionalRequired for public clients
code_challenge_methodConditionalMust be S256 if provided
audYes (SMART)FHIR server base URL
launchOptionalEHR launch context identifier
nonceOptionalOpenID Connect replay protection

The authorization flow uses a session cookie (oauth_session) to track the user’s authentication state:

  • Name: oauth_session
  • Path: /auth
  • Lifetime: 10 minutes
  • Attributes: HttpOnly, SameSite=Lax, Secure (in production)

User consent decisions are stored in the database (octofhir_auth.user_consents table). When a user approves access for a client with specific scopes, subsequent authorization requests with the same scopes skip the consent screen for better UX.