Authorization
The EDK's authorization system enforces access control through external Policy Decision Points (PDPs) using the OpenID AuthZEN specification. Rather than scattering if (user.hasRole("admin")) checks throughout your code, authorization decisions are delegated to a dedicated policy engine that evaluates every command against configurable policies.
This design separates policy from code. Your application logic doesn't contain authorization rules, it describes what it wants to do (the action, the resource, the actor), and the policy engine decides whether it's allowed. Policies can be updated, audited, and tested independently of application deployments.
Why External Policy Engines?
Hardcoded authorization has well-known problems. Role checks get scattered across controllers, services, and repositories. Changing who can do what requires code changes and redeployment. Auditing access decisions means searching through application logs. Multi-tenant authorization, where different tenants have different rules, becomes a maze of conditionals.
External policy engines solve these by centralizing the decision logic:
- Policies are data, not code: Cedar policies, OPA Rego rules, or any AuthZEN-compliant engine can express complex rules (RBAC, ABAC, tenant isolation, time-based access) without touching application code
- Decisions are auditable: every evaluation is a structured request/response pair that can be logged, replayed, and analyzed
- Tenant-specific rules: each tenant can have different policies without branching in your codebase
- Separation of concerns: security teams write policies, developers write application logic
How It Works
The EDK uses a PEP/PDP (Policy Enforcement Point / Policy Decision Point) architecture:
- Before a command executes, the
PolicyCommandExtensionintercepts it - It builds an authorization request describing who (subject), what (action), and on what (resource)
- The request is sent to the configured PDP (Cedarling, OPA, or any AuthZEN-compliant endpoint)
- The PDP evaluates its policies and returns PERMIT, DENY, or STEP_UP_REQUIRED
- If denied, the command is short-circuited with a
FORBIDDEN_ERROR, the business logic never runs
This happens transparently for every command. Developers don't write authorization checks, they write commands, and the authorization layer enforces policy automatically. See Command Extension for the full details on how commands are intercepted.
Supported Engines
The authorization system is provider-agnostic. The PolicyEngine interface abstracts over multiple PDP implementations, so switching engines is a configuration change, not a code change.
Cedarling (Cedar)
Cedarling is Gluu's implementation of the Cedar policy language, deployed as a sidecar alongside your application. Cedar policies are human-readable, formally verifiable, and designed for fine-grained authorization. The EDK communicates with Cedarling over HTTP using the AuthZEN evaluation protocol.
Cedarling supports two request modes: standard AuthZEN (forwards the request shape as-is) and sidecar-token mode (applies Janssen-specific token semantics with optional SHA-256 ID hashing). This makes it compatible with both standalone deployments and Janssen/Gluu identity platform integrations.
See Cedarling Integration for deployment, configuration, and policy examples.
Open Policy Agent (OPA)
OPA is a general-purpose policy engine using the Rego language. It's widely adopted in Kubernetes and cloud-native environments. The EDK sends authorization requests to OPA's REST API and parses the response.
OPA's response is interpreted with a fail-closed default: if the response doesn't contain an explicit allow: true field, the decision is DENY. This prevents policy misconfigurations from accidentally granting access.
See OPA Integration for configuration details.
Generic AuthZEN
Any PDP that implements the OpenID AuthZEN specification's /access/v1/evaluation endpoint works out of the box. The EDK sends a standard AuthZEN evaluation request and interprets the response. This includes commercial policy platforms, custom PDPs, and future AuthZEN-compliant engines.
The Authorization Request
Every authorization evaluation is built from four components that together describe the access attempt:
Subject: who is making the request. Extracted automatically from the JWT access token. The system supports multiple subject types simultaneously: a user principal (the end user) and a workload principal (the calling service). This matters in microservice architectures where both identities need authorization.
Action: what operation is being performed. Derived from the command ID. For example, command kms.keys.generate becomes an action with name generate, in module kms, service keys. This normalization lets policies match on specific commands, entire services, or whole modules using pattern-based rules.
Resource: what is being acted on. Extracted from the command's input arguments via pluggable resource mappers. A GetKeyCommand with input GetKeyInput(keyId = "abc") produces a resource of type Key with ID abc. Each domain can register its own mapper to extract resource information from its command arguments.
Context: ambient information that influences the decision. Includes tenant ID, session ID, timestamps, jurisdiction, and custom attributes. Context is where tenant-specific policy evaluation happens, a policy can permit an action for tenant A but deny it for tenant B based on the same principal and action.
data class PolicyRequest(
val principal: PolicyPrincipal,
val action: PolicyAction,
val resource: PolicyResource,
val context: PolicyContext
)
All engines receive the same normalized request via PolicyRequestNormalizer, which decomposes command IDs into module/service/command attributes. This ensures policy parity across engines, whether you use Cedar or OPA, the policies see identical input.
Decisions
The PDP returns one of three decisions:
PERMIT: the action is allowed. The command executes normally.
DENY: the action is forbidden. The command is short-circuited with a FORBIDDEN_ERROR and the business logic never runs. The denial reasons from the PDP are included in the error for debugging and audit.
STEP_UP_REQUIRED: the action is allowed in principle, but requires higher authentication assurance (per RFC 9470). This bridges authorization and authentication: a policy can say "this action needs MFA" without the application code knowing about authentication levels. The caller receives a challenge to re-authenticate at a higher level.
Enforcement Modes
The authorization system supports progressive rollout. You don't have to go from "no authorization" to "full enforcement" in one step.
DISABLED: no policy evaluation at all. Use during development and early testing when you don't have a PDP deployed yet.
LOG_ONLY: evaluate policies against the real PDP, but always permit regardless of the decision. The actual decision is logged, so you can review what would be denied in production without breaking anything. This is invaluable for validating new policies before enforcing them.
ENFORCED: full enforcement. DENY blocks the command. This is the production setting.
A common rollout pattern is: deploy with LOG_ONLY, review logs for unexpected denials, adjust policies, then switch to ENFORCED.
Dual-Principal Evaluation
In service-to-service architectures, a request often carries two identities: the end user (from the forwarded JWT) and the calling service (the workload). The EDK can evaluate both.
PRIMARY_ONLY evaluates only the user principal. This is the simplest mode and sufficient when services trust each other implicitly.
DUAL_AND requires both the user AND the workload to be permitted. This is the strictest mode, the policy must explicitly allow both the end user's action and the service's ability to make that type of request on behalf of users.
DUAL_OR permits if either the user or the workload is permitted. This is useful for service accounts that perform batch operations where no end user is involved.
The evaluation creates a cross-product of principals and resources, sending all combinations to the PDP as a batch evaluation. The batch combine mode (ALL_PERMIT or ANY_PERMIT) determines how individual decisions are aggregated.
Resilience
Policy evaluation must not become a reliability bottleneck. If the PDP goes down, your entire application would stop processing requests, unless the authorization layer handles failures gracefully.
Circuit breaker: after a configurable number of consecutive PDP failures (default 5), the circuit opens. While open, requests skip the PDP entirely and go straight to the fallback policy. The breaker periodically enters a half-open state, sending a single test request to check if the PDP has recovered.
Response caching: identical authorization requests are cached with a configurable TTL (default 5 minutes) and maximum size (default 10,000 entries). Cache keys include tenant, subject, action, and resource, so different users and tenants never share cached decisions. Caches can be invalidated per-subject (when a user's permissions change) or per-tenant (when tenant policies change).
Fallback policies: when the PDP is unreachable and the circuit breaker is open:
- DENY (default), fail closed. No access until the PDP recovers. This is the secure choice for production.
- ALLOW: fail open. Allow everything. Only appropriate for development environments.
- FAIL: propagate the error to the caller. The caller gets a 503 and can retry or show an appropriate error.
Configuration
sphereon:
authzen:
enabled: true
enforcement-mode: ENFORCED
pdp:
type: cedarling
base-url: http://cedarling-sidecar:5000
evaluation-path: /cedarling/evaluation
timeout-ms: 5000
fallback: deny
dual-principal-mode: primary-only
cache:
enabled: true
ttl-seconds: 300
max-entries: 10000
resilience:
circuit-breaker-enabled: true
failure-threshold: 5
reset-time-ms: 30000
include:
- "**"
exclude:
- "health.**"
- "actuator.**"
- "discovery.**"
The include and exclude patterns use glob-style matching against command IDs. By default, all commands are authorized except health checks and discovery endpoints. You can narrow inclusion to specific domains (e.g., kms.**, party.**) during rollout.
Modules
| Module | Description |
|---|---|
lib-authz-policy-api | Provider-agnostic PolicyEngine interface, request/decision models |
lib-authz-authzen-api | AuthZEN protocol models, PDP interface, configuration |
lib-authz-authzen-cedarling | Cedarling PDP client with request mapping |
lib-authz-authzen-opa | OPA policy engine with Rego input/output mapping |
lib-authz-authzen-impl | Authorization evaluator, command extension, resilience (circuit breaker, cache) |
lib-authz-authzen-spring | Spring Boot auto-configuration |