Skip to main content
Version: 2.0-rc2

ADR 023: OPA backed by AuthZ database adapter as PDP

Date: 2026-01-28

Status: Accepted

Decision Makers: @cr0ssing

Context​

CIVITAS/CORE 2.0 is an urban data platform that manages DataSpaces, DataSetsDatasetA data-related element that contains processed data and makes it available for consumption. A Dataset is populated via Pipelines and carries Metadata and access permissions., and associated metadata across multiple platform services. The platform requires an authorization architecture that:

  • Complies with BSI TR-03187 (AR-12: established security standards; AR-19: centralized authorization for all platform components)
  • Enforces a Relationship-Based Access Control (ReBAC) model: Users belong to Groups, Groups are assigned Roles with Scope (TenantTenantAn isolated organizational partition that owns Data pools, Datasets, Users, Groups, and Roles. All access rules exist within their Tenant, and the Tenant is the widest Scope of a Role. Currently, one Tenant corresponds to the Platform./DataSpace/DataSetDatasetA data-related element that contains processed data and makes it available for consumption. A Dataset is populated via Pipelines and carries Metadata and access permissions.), Roles carry Permissions
  • Integrates with an existing PostgreSQL authorization schema and KeycloakKeycloakAn open-source Identity and Access Management (IAM) solution providing SSO and OAuth2/OpenID Connect flows. In CIVITAS/CORE it is used to authenticate users and issue JWTs. for authentication
  • Works with Apache APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs. as the already-selected API gateway
  • Supports multiple frontends (management portalManagement PortalThe central user interface of the Platform that provides access to all functionalities for managing data, configurations, users, and access. It serves as the main entry point for working with data-related elements., data visualization) and backend services
  • Provides fail-secure behavior and audit logging

The authorization data model is already defined in PostgreSQL and follows a multi-hop relationship chain: User > Groups > Assignments (scoped to TenantTenantAn isolated organizational partition that owns Data pools, Datasets, Users, Groups, and Roles. All access rules exist within their Tenant, and the Tenant is the widest Scope of a Role. Currently, one Tenant corresponds to the Platform., DataSpace, or DataSetDatasetA data-related element that contains processed data and makes it available for consumption. A Dataset is populated via Pipelines and carries Metadata and access permissions.) > Roles > Permissions. Binary assignments (system roles assigned to groups via group_roles) and ternary assignments (data/governance roles assigned to groups with a scope via assignments) coexist. KeycloakKeycloakAn open-source Identity and Access Management (IAM) solution providing SSO and OAuth2/OpenID Connect flows. In CIVITAS/CORE it is used to authenticate users and issue JWTs. issues JWT tokens that identify the user; all authorization data -- group memberships, role assignments, and permissions -- lives in PostgreSQL and is queried at request time. The architecture treats PostgreSQL as the single source of truth.

Frontend applications are built with Next.js and use NextAuth.js as a Backend-for-Frontend (BFFBackend-For-FrontendAn architecture pattern where a dedicated backend serves a specific frontend, ensuring that sensitive tokens never reach the client side.). NextAuth handles OAuth2 session management (token acquisition, callbacks, refresh) via direct communication with KeycloakKeycloakAn open-source Identity and Access Management (IAM) solution providing SSO and OAuth2/OpenID Connect flows. In CIVITAS/CORE it is used to authenticate users and issue JWTs., and proxies backend API calls through APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs. where authorization is enforced.

Checked Architecture Principles​

RatingPrinciple
fullModel-centric data flow
fullDistributed architecture with unified user experience
fullModular design
fullIntegration capability through defined interfaces
fullOpen source as the default
fullCloud-native architecture
fullPrefer standard solutions over custom development
fullSelf-contained deployment
fullTechnological consistency to ensure maintainability
partialMulti-tenancy
fullSecurity by design

Multi-tenancy (partial): The authorization schema supports a TENANTTenantAn isolated organizational partition that owns Data pools, Datasets, Users, Groups, and Roles. All access rules exist within their Tenant, and the Tenant is the widest Scope of a Role. Currently, one Tenant corresponds to the Platform. scope level in the hierarchy (TENANTTenantAn isolated organizational partition that owns Data pools, Datasets, Users, Groups, and Roles. All access rules exist within their Tenant, and the Tenant is the widest Scope of a Role. Currently, one Tenant corresponds to the Platform. > DATASPACE > DATASETDatasetA data-related element that contains processed data and makes it available for consumption. A Dataset is populated via Pipelines and carries Metadata and access permissions.), and RegoRegoOPA's declarative policy language, used to express authorization rules such as checking role assignments at the most specific scope first. policies implement tenantTenantAn isolated organizational partition that owns Data pools, Datasets, Users, Groups, and Roles. All access rules exist within their Tenant, and the Tenant is the widest Scope of a Role. Currently, one Tenant corresponds to the Platform.-scoped authorization checks. However, full tenantTenantAn isolated organizational partition that owns Data pools, Datasets, Users, Groups, and Roles. All access rules exist within their Tenant, and the Tenant is the widest Scope of a Role. Currently, one Tenant corresponds to the Platform. isolation (separate KeycloakKeycloakAn open-source Identity and Access Management (IAM) solution providing SSO and OAuth2/OpenID Connect flows. In CIVITAS/CORE it is used to authenticate users and issue JWTs. realms, tenantTenantAn isolated organizational partition that owns Data pools, Datasets, Users, Groups, and Roles. All access rules exist within their Tenant, and the Tenant is the widest Scope of a Role. Currently, one Tenant corresponds to the Platform.-scoped database rows, tenantTenantAn isolated organizational partition that owns Data pools, Datasets, Users, Groups, and Roles. All access rules exist within their Tenant, and the Tenant is the widest Scope of a Role. Currently, one Tenant corresponds to the Platform. routing) is not yet implemented. The architecture does not preclude multi-tenancy and the scope hierarchy was designed with it in mind.

Decision​

We adopt a centralized, gateway-enforced authorization architecture using Open Policy AgentOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization. (OPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization.) as the Policy Decision PointPolicy Decision PointThe component that decides whether a request is authorized. CIVITAS/CORE uses Open Policy Agent (OPA) as its PDP. (PDPPolicy Decision PointThe component that decides whether a request is authorized. CIVITAS/CORE uses Open Policy Agent (OPA) as its PDP.) and Apache APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs. as the Policy Enforcement PointPolicy Enforcement PointThe component that enforces the authorization decision made by the Policy Decision Point. CIVITAS/CORE uses Apache APISIX as its PEP, adopting a centralized, gateway-enforced authorization architecture. (PEPPolicy Enforcement PointThe component that enforces the authorization decision made by the Policy Decision Point. CIVITAS/CORE uses Apache APISIX as its PEP, adopting a centralized, gateway-enforced authorization architecture.), with a Spring Boot adapter service bridging OPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization. to the existing PostgreSQL authorization schema.

Architecture​

adr-002-architecture.svg

Request flow:

  1. The browser sends requests to the NextAuth.js BFFBackend-For-FrontendAn architecture pattern where a dedicated backend serves a specific frontend, ensuring that sensitive tokens never reach the client side., which proxies backend API calls through APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs..
  2. APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs. validates the JWT signature via KeycloakKeycloakAn open-source Identity and Access Management (IAM) solution providing SSO and OAuth2/OpenID Connect flows. In CIVITAS/CORE it is used to authenticate users and issue JWTs.'s JWKS endpoint (OpenID Connect plugin) and encodes the claims into an X-Userinfo header.
  3. APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs. forwards the request context (path, method, headers) to OPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization. for an authorization decision.
  4. OPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization. extracts the user identity from the JWT claims, extracts the resource type and ID from the request URI, and maps the HTTP method to an action (GET > read, POST/PUT > write, DELETE > delete). OPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization. then queries the adapter service for the user's group memberships, role assignments (both system roles via group_roles and scoped roles via assignments), and permissions. PostgreSQL is the single source of truth for all authorization data.
  5. OPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization. evaluates the RegoRegoOPA's declarative policy language, used to express authorization rules such as checking role assignments at the most specific scope first. policy: it checks assignments at the most specific scope first (DATASETDatasetA data-related element that contains processed data and makes it available for consumption. A Dataset is populated via Pipelines and carries Metadata and access permissions. if applicable), then DATASPACE, then TENANTTenantAn isolated organizational partition that owns Data pools, Datasets, Users, Groups, and Roles. All access rules exist within their Tenant, and the Tenant is the widest Scope of a Role. Currently, one Tenant corresponds to the Platform.. Permissions from all of the user's groups are combined via union (most permissive).
  6. APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs. enforces the decision: forward to the backend on allow, return 403 on deny.

Route-to-permission mapping:

APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs. sends raw HTTP context (path, method) to OPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization.. The authorization schema defines its own permission model (e.g., data.read, data.write, data.delete). The RegoRegoOPA's declarative policy language, used to express authorization rules such as checking role assignments at the most specific scope first. policy is responsible for mapping between these two:

  • Parsing the request path to extract resource type (e.g., /api/dataspaces/123 → DATASPACE) and resource ID
  • Mapping HTTP methods to actions (GET → read, POST/PUT → write, DELETE → delete)
  • Translating actions to the permission names used in the database

This mapping logic lives in RegoRegoOPA's declarative policy language, used to express authorization rules such as checking role assignments at the most specific scope first. code. If the API surface or permission model changes, the RegoRegoOPA's declarative policy language, used to express authorization rules such as checking role assignments at the most specific scope first. policy must be updated accordingly.

This approach assumes that (backend routes+HTTP verb) tuples map cleanly to authorization permissions -- a GET on a resource means "read", a DELETE means "delete", etc. This holds for straightforward CRUD APIs but may not generalize to all backends. APIs with complex operations (e.g., a POST that triggers a workflow spanning multiple resources, or a single endpoint with action parameters in the body) would require more sophisticated mapping logic or a different authorization pattern (e.g., explicit permission checks in application code, which would conflict with AR-19).

Technology Selection: Why OPA​

Selection criteria​

The PDPPolicy Decision PointThe component that decides whether a request is authorized. CIVITAS/CORE uses Open Policy Agent (OPA) as its PDP. must satisfy:

  1. AR-12 (established standards): Industry-standard, proven technology -- no custom authorization logic
  2. AR-19 (centralized authorization): Single service decides all authorization; no policy code in backends
  3. APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs. integration: Native plugin or straightforward HTTP integration
  4. PostgreSQL compatibility: Query existing schema without requiring data migration or dual-store sync
  5. Production track record: Proven at scale in comparable environments

Why Open Policy Agent​

OPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization. is a CNCF graduated project (the highest maturity level), used in production by Netflix, Pinterest, Cloudflare, and Goldman Sachs. It satisfies all selection criteria:

  • AR-12: Industry standard with active security review process
  • AR-19: Standalone service; application code contains zero authorization logic
  • APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs.: Ships with a native OPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization. plugin (opa)
  • PostgreSQL: RegoRegoOPA's declarative policy language, used to express authorization rules such as checking role assignments at the most specific scope first. policies make HTTP calls to the adapter service, which queries PostgreSQL -- no data duplication
  • Track record: Battle-tested at scale; extensive documentation and community

Additional benefits:

  • Declarative policies: RegoRegoOPA's declarative policy language, used to express authorization rules such as checking role assignments at the most specific scope first. separates policy from application code; policies are versioned and tested independently
  • Fail-secure: default allow := false ensures denial on error
  • Observability: Supports decision logging (requires configuration and a log receiver), Prometheus metrics, distributed tracing
  • Flexibility: Can evolve to ABAC, time-based access, or additional enforcement points without architectural changes

Alternatives considered​

AlternativeStrengthsWhy discarded
KeycloakKeycloakAn open-source Identity and Access Management (IAM) solution providing SSO and OAuth2/OpenID Connect flows. In CIVITAS/CORE it is used to authenticate users and issue JWTs. Authorization ServicesAlready using KeycloakKeycloakAn open-source Identity and Access Management (IAM) solution providing SSO and OAuth2/OpenID Connect flows. In CIVITAS/CORE it is used to authenticate users and issue JWTs. for authentication; built-in UI for policy management; supports resources, scopes, and policiesData model mismatch: KeycloakKeycloakAn open-source Identity and Access Management (IAM) solution providing SSO and OAuth2/OpenID Connect flows. In CIVITAS/CORE it is used to authenticate users and issue JWTs.'s authorization model is resource/scope-based, not relationship-based. The existing PostgreSQL schema implements ReBAC with multi-hop relationships (User → Group → Assignment → Role → Permission) and a scope hierarchy (TENANTTenantAn isolated organizational partition that owns Data pools, Datasets, Users, Groups, and Roles. All access rules exist within their Tenant, and the Tenant is the widest Scope of a Role. Currently, one Tenant corresponds to the Platform./DATASPACE/DATASETDatasetA data-related element that contains processed data and makes it available for consumption. A Dataset is populated via Pipelines and carries Metadata and access permissions.) that doesn't map cleanly to KeycloakKeycloakAn open-source Identity and Access Management (IAM) solution providing SSO and OAuth2/OpenID Connect flows. In CIVITAS/CORE it is used to authenticate users and issue JWTs.'s concepts. Would require either abandoning the existing schema or complex bidirectional sync. KeycloakKeycloakAn open-source Identity and Access Management (IAM) solution providing SSO and OAuth2/OpenID Connect flows. In CIVITAS/CORE it is used to authenticate users and issue JWTs. becomes a single point of failure for both authn and authz.
CerbosYAML policies (easier than RegoRegoOPA's declarative policy language, used to express authorization rules such as checking role assignments at the most specific scope first.), native PostgreSQL adapterNo APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs. plugin (requires custom integration), smaller community, less proven at scale. Custom gateway work negates YAML simplicity.
OpenFGAPurpose-built for ReBAC, graph-based queriesRequires syncing all authorization data from PostgreSQL into OpenFGA's store, introducing eventual consistency risks and dual-store operational burden. No APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs. plugin.
Ory KetoReBAC-focused, part of Ory ecosystemSame dual-store sync issues as OpenFGA. No APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs. plugin. Smaller community than OPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization..
CasbinSupports multiple models (ACL, RBAC, ABAC), PostgreSQL adapter, lightweightLibrary, not a service -- must be embedded in each backend, violating AR-19 (centralized authorization). No APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs. plugin.
Custom Spring Boot serviceDirect PostgreSQL access, familiar technologyViolates AR-12 (not an established standard). Requires custom APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs. plugin. Puts custom security code on the critical path with no built-in policy testing or audit logging.

Consequences​

Affected components​

  • APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs.: All routes must include the OPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization. plugin configuration. New services added to the platform get authorization enforcement by adding the plugin to their routes.
  • NextAuth.js BFFBackend-For-FrontendAn architecture pattern where a dedicated backend serves a specific frontend, ensuring that sensitive tokens never reach the client side.: Proxies backend API calls through APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs.. NextAuth's own /api/auth/* routes (sign-in, callback, session, sign-out) are handled locally and communicate directly with KeycloakKeycloakAn open-source Identity and Access Management (IAM) solution providing SSO and OAuth2/OpenID Connect flows. In CIVITAS/CORE it is used to authenticate users and issue JWTs..
  • Backend services (CIVITAS Portal, Protected Backend, future services): Must NOT implement authorization logic. Services rely on APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs. having enforced authorization before the request arrives. Services only configure Spring Security for JWT authentication (defense-in-depth).
  • Adapter Service: New authorization data requirements (e.g., new scope types, new entity relationships) require adding REST endpoints here and corresponding RegoRegoOPA's declarative policy language, used to express authorization rules such as checking role assignments at the most specific scope first. queries in OPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization..
  • OPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization. policies: The RegoRegoOPA's declarative policy language, used to express authorization rules such as checking role assignments at the most specific scope first. policy files are the single source of truth for all authorization rules. Changes to access control semantics (e.g., adding DENY permissions, enabling group hierarchy inheritance) are isolated to policy.
  • Operations: OPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization. and the adapter service are infrastructure components that must be monitored and kept available. OPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization. supports decision logging for audit trails (requires configuration).

Positive effects​

  • Single place to audit all authorization rules (RegoRegoOPA's declarative policy language, used to express authorization rules such as checking role assignments at the most specific scope first. policies)
  • Adding new services to the platform requires only APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs. route configuration, not authorization code
  • Policy changes can be deployed independently of application releases
  • OPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization.'s test framework provides regression safety for authorization logic
  • OPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization. supports decision logging for audit trails (requires configuration and a log receiver)
  • Production-proven technology reduces risk of security vulnerabilities

Negative effects​

  • Authorization checks add network latency (APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs. > OPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization. > Adapter > PostgreSQL); mitigated by co-locating services and caching (Caffeine in the adapter service, materialized views in PostgreSQL; Redis or OPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization. bundles for more advanced scenarios)
  • Team must learn RegoRegoOPA's declarative policy language, used to express authorization rules such as checking role assignments at the most specific scope first.; mitigated by documentation and an existing policy codebase as a reference
  • Adapter service is an additional component to maintain; mitigated by its narrow scope (read-only queries against the authorization schema)

Risks and mitigations​

RiskImpactLikelihoodMitigation
RegoRegoOPA's declarative policy language, used to express authorization rules such as checking role assignments at the most specific scope first. policy bugs cause incorrect authorization decisionsHighMediumOPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization. test framework for policy unit tests; peer review; staged rollouts
Adapter service becomes a performance bottleneckMediumMediumCaffeine caching in adapter; materialized views in PostgreSQL; monitor latency; scale horizontally
OPAOpen Policy AgentThe platform's authorization service: it evaluates 'who may do what' against the CIVITAS/CORE authorization model. OPA is the platform's central Policy Decision Point (PDP) for API authorization. policy complexity grows unmanageableMediumLowModular policy structure; documentation; policy linting
Network failures between componentsHighLowRetry logic; health checks; circuit breakers; fail-secure default
Cache invalidation issues cause stale decisionsHighMediumEvent-driven invalidation; short TTLs; monitoring

See also​