Security Strategy
Intent
Provide pluggable authentication and authorization strategies for different FHIR access contexts (EHR launch, standalone apps, backend services) using the Strategy design pattern.
Forces
Structure
The Security Strategy pattern implements different authentication and authorization approaches as pluggable strategies, allowing the same system to handle multiple SMART on FHIR flows.
Key Components
AuthStrategy
Interface defining the contract for authentication and authorization strategies
EhrLaunchStrategy
Handles SMART EHR launch flow with contextual patient/encounter information
StandaloneStrategy
Manages standalone app launch where users authenticate directly
BackendServicesStrategy
Implements SMART Backend Services with client credentials and JWT assertion
ScopeEvaluator
Evaluates FHIR scopes against specific operations and resource types
SecurityGateway
Context object that selects and delegates to appropriate strategy
Behavior
Strategy Selection Logic
The SecurityGateway determines which strategy to use based on request characteristics:
Backend Services Flow
For system-to-system integration using client credentials:
# Example JWT assertion for backend services
{
"iss": "client-id-12345",
"sub": "client-id-12345",
"aud": "https://fhir.example.org/token",
"jti": "unique-request-id",
"exp": 1609459200,
"iat": 1609458900,
"nbf": 1609458900
}
# Requested scopes
scope: "system/Patient.read system/Observation.read"
EHR Launch Context
For apps launched from an EHR with clinical context:
# Access token claims
{
"iss": "https://auth.example.org",
"sub": "user-123",
"aud": "client-app-456",
"scope": "patient/Patient.read patient/Observation.read launch",
"launch": "launch-context-789",
"patient": "Patient/123",
"encounter": "Encounter/456"
}
Implementation Considerations
Strategy Configuration
Configures multiple authentication strategies (EHR launch, standalone, backend services) with pluggable validation and scope handling.
# Example strategy configuration
class SecurityConfig:
def __init__(self):
self.strategies = {
'ehr-launch': EhrLaunchStrategy(
issuer='https://auth.hospital.org',
jwks_url='https://auth.hospital.org/.well-known/jwks.json'
),
'backend': BackendServicesStrategy(
allowed_clients=['system-a', 'system-b'],
token_endpoint='https://auth.hospital.org/token'
),
'standalone': StandaloneStrategy(
issuer='https://auth.hospital.org',
introspection_endpoint='https://auth.hospital.org/introspect'
)
}
Scope Evaluation
Parses and evaluates FHIR scopes (patient/*.read, user/Observation.write) against requested operations and resource types.
class ScopeEvaluator:
def permit(self, operation: str, resource_type: str, scopes: List[str]) -> bool:
# Check for wildcard permissions
if f"user/*.{operation}" in scopes:
return True
if f"patient/*.{operation}" in scopes:
return True
if f"system/*.{operation}" in scopes:
return True
# Check resource-specific permissions
if f"user/{resource_type}.{operation}" in scopes:
return True
if f"patient/{resource_type}.{operation}" in scopes:
return True
if f"system/{resource_type}.{operation}" in scopes:
return True
return False
Token Validation
Validates JWT access tokens including signature verification, expiration checks, and issuer validation against configured authorization servers.
class BackendServicesStrategy(AuthStrategy):
def authorize(self, request: FhirRequest) -> AuthzResult:
token = self.extract_token(request)
# Validate JWT signature and claims
claims = self.validate_jwt(token)
# Verify client registration
if not self.is_registered_client(claims['iss']):
raise UnauthorizedError("Unknown client")
# Check token freshness
if claims['exp'] < time.time():
raise UnauthorizedError("Token expired")
return AuthzResult(
authorized=True,
scopes=claims.get('scope', '').split(),
context={'client_id': claims['iss']}
)
Context Propagation
Propagates security context through request chains for multi-hop scenarios, preserving user identity and authorization decisions. Security context often needs to be propagated through request chains:
class SecurityContext:
def __init__(self, user_id: str = None, patient_id: str = None,
scopes: List[str] = None, client_id: str = None):
self.user_id = user_id
self.patient_id = patient_id
self.scopes = scopes or []
self.client_id = client_id
def has_scope(self, scope: str) -> bool:
return scope in self.scopes
def can_access_patient(self, patient_id: str) -> bool:
# Backend services can access any patient
if any(s.startswith('system/') for s in self.scopes):
return True
# Patient-scoped access limited to authorized patient
return self.patient_id == patient_id
Related Patterns
- Broker: Broker delegates authentication and authorization to Security Strategy
- Privacy Enforcement: Privacy Enforcement relies on Security Strategy to establish user/system identity for consent decisions
- Audit & Provenance Chain: Security context from Security Strategy is captured in audit events
- Population Export Pipeline: Population Export uses Backend Services strategy for bulk data access
Benefits
- Flexibility: Support multiple authentication flows in same system
- Extensibility: Easy to add new authentication strategies
- Separation of Concerns: Authentication logic isolated from business logic
- Standards Compliance: Implements SMART on FHIR specifications correctly
- Testing: Easy to mock and test different authentication scenarios
Trade-offs
- Complexity: More complex than single authentication approach
- Performance: Strategy selection and validation overhead
- Configuration: Multiple strategies require more configuration management
- Token Handling: Different token formats and validation requirements
References
- SMART App Launch - EHR and standalone app launch flows
- SMART Backend Services - System-to-system authentication
- OAuth 2.0 - Authorization framework
- OpenID Connect - Identity layer on OAuth 2.0
Strategy Selection
Use request characteristics (headers, token format, claims) to automatically select the appropriate strategy rather than requiring explicit configuration per request.
Security Considerations
Always validate tokens cryptographically and check expiration times. Implement proper rate limiting and audit logging for all authentication attempts.