Skip to main content
Version: v0.13

Authorization

The EDK provides a flexible authorization framework that integrates with external Policy Decision Points (PDPs) via the OpenID AuthZEN specification. This enables fine-grained, policy-based access control without embedding authorization logic in your application.

Architecture

The authorization system follows a Policy Enforcement Point (PEP) / Policy Decision Point (PDP) architecture:

PEP/PDP Authorization Architecture

Key Components

PolicyEngine

The generic interface for authorization decisions. Abstracts different policy engines:

interface PolicyEngine {
val id: String
val engineType: PolicyEngineType // CEDAR, OPA, AUTHZEN, CUSTOM

suspend fun isHealthy(): Boolean
suspend fun evaluate(request: PolicyRequest): IdkResult<PolicyDecision, IdkError>
}

AuthZenPdp

The OpenID AuthZEN-specific PDP interface:

interface AuthZenPdp {
val id: String

suspend fun isHealthy(): Boolean
suspend fun evaluate(request: AuthZenEvaluationRequest): IdkResult<AuthZenEvaluationResponse, IdkError>
suspend fun getConfiguration(): IdkResult<AuthZenConfiguration, IdkError>
}

ResilientAuthZenPdp

Production-ready wrapper with resilience patterns:

  • Circuit breaker - Prevents cascading failures when PDP is unavailable
  • Response caching - Reduces PDP load for repeated requests
  • Fallback handling - Configurable behavior when PDP fails

Supported Policy Engines

EngineProtocolDescription
CedarlingAuthZENCedar policy engine via Janssen sidecar
OPAREST APIOpen Policy Agent with Rego policies
GenericAuthZENAny AuthZEN-compliant PDP

Request/Response Models

PolicyRequest

The principal-action-resource model for authorization:

data class PolicyRequest(
val principal: PolicyPrincipal, // Who is requesting
val action: PolicyAction, // What operation
val resource: PolicyResource, // What target
val context: PolicyContext // Additional context
)

// Principal (the entity making the request)
data class PolicyPrincipal(
val type: String, // "User", "Workload", "Role"
val id: String,
val attributes: Map<String, JsonElement> = emptyMap()
)

// Action (the operation being requested)
data class PolicyAction(
val name: String, // Command ID like "party.create"
val attributes: Map<String, JsonElement> = emptyMap()
)

// Resource (the target of the request)
data class PolicyResource(
val type: String,
val id: String? = null,
val attributes: Map<String, JsonElement> = emptyMap()
)

// Context (environmental information)
data class PolicyContext(
val tenantId: String? = null,
val sessionId: String? = null,
val timestamp: Long? = null,
val attributes: Map<String, JsonElement> = emptyMap()
)

PolicyDecision

The result of policy evaluation:

data class PolicyDecision(
val decision: Decision, // PERMIT or DENY
val reasons: List<String> = emptyList(),
val diagnostics: Map<String, JsonElement> = emptyMap()
) {
val isPermitted: Boolean get() = decision == Decision.PERMIT
val isDenied: Boolean get() = decision == Decision.DENY
}

enum class Decision {
PERMIT,
DENY
}

AuthZEN Models

The OpenID AuthZEN protocol uses slightly different terminology:

// AuthZEN Subject (principal)
data class AuthZenSubject(
val type: String, // "user", "workload", "role"
val id: String,
val properties: Map<String, JsonElement> = emptyMap()
) {
companion object {
fun user(id: String) = AuthZenSubject("user", id)
fun workload(id: String) = AuthZenSubject("workload", id)
fun role(id: String) = AuthZenSubject("role", id)
}
}

// AuthZEN Evaluation Request
data class AuthZenEvaluationRequest(
val subject: AuthZenSubject,
val action: AuthZenAction,
val resource: AuthZenResource,
val context: AuthZenContext
)

// AuthZEN Evaluation Response
data class AuthZenEvaluationResponse(
val decision: Boolean, // true = permit, false = deny
val context: Map<String, JsonElement>? = null
)

Basic Usage

Evaluating Authorization

import com.sphereon.authz.policy.*
import jakarta.inject.Inject
import jakarta.inject.Singleton

@Singleton
class PartyService @Inject constructor(
private val policyEngine: PolicyEngine
) {
suspend fun createParty(userId: String, tenantId: String, input: PartyInput): Party {
// Build authorization request
val request = PolicyRequest(
principal = PolicyPrincipal(type = "User", id = userId),
action = PolicyAction(name = "party.create"),
resource = PolicyResource(type = "Party"),
context = PolicyContext(tenantId = tenantId)
)

// Evaluate policy
val decision = policyEngine.evaluate(request)
.getOrThrow()

if (decision.isDenied) {
throw AccessDeniedException("User $userId not authorized to create parties")
}

// Proceed with operation
return createPartyInternal(input)
}
}

Using AuthZEN Directly

import com.sphereon.authz.authzen.pdp.AuthZenPdp
import com.sphereon.authz.authzen.model.*

@Singleton
class AuthorizationService @Inject constructor(
private val pdp: AuthZenPdp
) {
suspend fun checkAccess(
userId: String,
action: String,
resourceType: String,
resourceId: String?,
tenantId: String,
sessionId: String
): Boolean {
val request = AuthZenEvaluationRequest(
subject = AuthZenSubject.user(userId),
action = AuthZenAction(name = action),
resource = AuthZenResource(type = resourceType, id = resourceId),
context = AuthZenContext(tenantId = tenantId, sessionId = sessionId)
)

val result = pdp.evaluate(request)
return result.getOrNull()?.decision ?: false
}
}

Configuration

AuthZenConfig

Configure the authorization subsystem:

data class AuthZenConfig(
val enabled: Boolean = false,
val pdp: AuthZenPdpConfig? = null,
val fallbackPolicy: FallbackPolicy = FallbackPolicy.DENY,
val cacheEnabled: Boolean = true,
val cacheTtlSeconds: Long = 300,
val excludePatterns: List<String> = listOf(
"health.**",
"discovery.**",
"actuator.**"
),
val includePatterns: List<String> = listOf("**"),
val resilience: ResilienceConfig = ResilienceConfig()
)

PDP Configuration

Configure the connection to your PDP:

// Cedarling (Cedar via Janssen)
val cedarlingConfig = AuthZenPdpConfig.Cedarling(
baseUrl = "http://cedarling-sidecar:8080",
evaluationPath = "/cedarling/evaluation",
healthPath = "/health",
timeoutMs = 5000
)

// Open Policy Agent
val opaConfig = AuthZenPdpConfig.Opa(
baseUrl = "http://opa:8181",
policyPath = "/v1/data/authz/allow",
timeoutMs = 5000
)

// Generic AuthZEN-compliant PDP
val genericConfig = AuthZenPdpConfig.Generic(
baseUrl = "https://pdp.example.com",
evaluationPath = "/access/v1/evaluation",
wellKnownPath = "/.well-known/authzen-configuration",
timeoutMs = 5000
)

Fallback Policies

Configure behavior when the PDP is unavailable:

PolicyBehaviorUse Case
DENYDeny all requests (fail closed)Production - secure default
ALLOWAllow all requests (fail open)Development/testing only
FAILPropagate error to callerWhen explicit error handling needed

Resilience Configuration

data class ResilienceConfig(
val circuitBreakerEnabled: Boolean = true,
val failureThreshold: Int = 5, // Failures before opening
val resetTimeMs: Long = 30000, // 30 seconds before retry
val retryEnabled: Boolean = false,
val maxRetries: Int = 2,
val retryDelayMs: Long = 100
)

Circuit Breaker

The circuit breaker prevents cascading failures when the PDP is unavailable:

Circuit Breaker State Diagram

Monitoring Circuit State

val resilientPdp: ResilientAuthZenPdp = // injected

// Check circuit breaker state
val state = resilientPdp.getCircuitBreakerState()
// State.CLOSED, State.OPEN, or State.HALF_OPEN

// Get cache statistics
val stats = resilientPdp.getCacheStats()

// Manual operations
resilientPdp.invalidateCache()
resilientPdp.resetCircuitBreaker()

Properties Configuration

Configure via application properties:

sphereon:
authz:
enabled: true

pdp:
type: cedarling
base-url: http://cedarling-sidecar:8080
timeout-ms: 5000

fallback-policy: deny
cache-enabled: true
cache-ttl-seconds: 300

exclude-patterns:
- "health.**"
- "actuator.**"

resilience:
circuit-breaker-enabled: true
failure-threshold: 5
reset-time-ms: 30000

Integration with Spring Boot

When using @EnableSphereonRestApi, authorization can be integrated via filters or interceptors:

import org.springframework.web.filter.OncePerRequestFilter
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse

class AuthorizationFilter(
private val policyEngine: PolicyEngine,
private val config: AuthZenConfig
) : OncePerRequestFilter() {

override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
if (!config.enabled) {
filterChain.doFilter(request, response)
return
}

// Build policy request from HTTP request
val policyRequest = buildPolicyRequest(request)

// Evaluate
runBlocking {
val decision = policyEngine.evaluate(policyRequest).getOrThrow()
if (decision.isDenied) {
response.sendError(403, "Access denied")
return@runBlocking
}
}

filterChain.doFilter(request, response)
}
}

Best Practices

Use fail-closed in production. Configure fallbackPolicy: DENY to ensure security when the PDP is unavailable.

Enable caching for performance. Authorization decisions for the same request can be cached to reduce PDP load.

Monitor circuit breaker state. Expose metrics for OPEN/HALF_OPEN states to detect PDP issues early.

Exclude health endpoints. Don't require authorization for health checks and actuator endpoints.

Use command IDs as actions. Map your service operations to hierarchical command IDs for consistent policy authoring.