Skip to main content

Application Integration (PEP)

The Policy Enforcement Point (PEP) is your application, gateway, or middleware. It builds an input document, asks the PDP for a decision, and enforces allow or deny before the request proceeds.

EnforceAuth governs policy lifecycle and evidence — not runtime enforcement. This guide covers the PEP side. Your PEP → PDP calls are synchronous and customer-operated; see Runtime boundary.

Request flow​

HTTP request
│
▼
┌─────────┐ input JSON ┌─────────┐
│ App PEP │───────────────►│ OPA PDP │
│ │◄───────────────│ │
└─────────┘ allow / deny └─────────┘
│
▼
Handler or 403

For deployment topology (sidecar vs centralized vs SDK), see Deployment patterns.

Agree on a decision path​

Pick one Rego entrypoint and use it everywhere in a system. A common convention:

package authz

default allow := false

allow if {
# your rules
}

Query path: authz/allow → HTTP POST /v1/data/authz/allow.

Structured decisions are also fine (authz/decision returning an object) — see Rego patterns. The PEP and PDP must agree on the path; EnforceAuth decision logs store whichever path OPA evaluated.

Standard input shape​

There is no single mandatory schema — design input for your domain. Most teams include:

FieldPurpose
user / principalIdentity, roles, attributes
actionVerb being authorized (read, delete, invoke)
resourceTarget object (id, type, owner, classification)
contextRequest metadata (IP, time, tenant)

Example input.json:

{
"user": {
"id": "user-42",
"roles": ["editor"],
"department": "finance"
},
"action": "read",
"resource": {
"type": "report",
"id": "rpt-9",
"classification": "confidential"
},
"context": {
"tenant_id": "org-acme",
"request_id": "req-abc"
}
}

Document the schema in your policy repo. The PEP and policy authors share ownership of this contract — see Policy authoring.

REST API (any language)​

With OPA listening on localhost:8181 (sidecar or daemon):

curl -s -X POST http://127.0.0.1:8181/v1/data/authz/allow \
-H "Content-Type: application/json" \
-d @input.json

Response when allow is true:

{
"result": true
}

When denied, result is false (or absent if you use default allow := false).

For object-shaped decisions at data.authz.decision:

curl -s -X POST http://127.0.0.1:8181/v1/data/authz/decision \
-H "Content-Type: application/json" \
-d '{"input": { ... }}'

Official reference: OPA REST API.

Node.js example​

async function isAllowed(input) {
const opaUrl = process.env.OPA_URL ?? 'http://127.0.0.1:8181';
const res = await fetch(`${opaUrl}/v1/data/authz/allow`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ input }),
});
if (!res.ok) {
throw new Error(`OPA returned ${res.status}`);
}
const body = await res.json();
return body.result === true;
}

Call from Express/Fastify middleware before route handlers. Pass the same input OPA will log when the decision_logs plugin is enabled.

Go example (HTTP)​

type opaResponse struct {
Result bool `json:"result"`
}

func allowed(ctx context.Context, opaURL string, input map[string]any) (bool, error) {
payload, _ := json.Marshal(map[string]any{"input": input})
req, _ := http.NewRequestWithContext(ctx, http.MethodPost,
opaURL+"/v1/data/authz/allow", bytes.NewReader(payload))
req.Header.Set("Content-Type", "application/json")

resp, err := http.DefaultClient.Do(req)
if err != nil {
return false, err
}
defer resp.Body.Close()

var out opaResponse
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return false, err
}
return out.Result, nil
}

For in-process evaluation without HTTP, use the OPA Go SDK with a bundle loaded from your EnforceAuth deployment artifact.

Where to enforce​

LayerTradeoff
MiddlewareCentralized, easy to test; must forward enough context in input
Per-handlerFine-grained; risk of inconsistent input shapes
API gatewayUniform for north-south traffic; harder to pass rich resource attributes
Service meshStrong for east-west; policy input comes from mesh metadata

Pick one primary layer per traffic direction. Duplicate checks with different input shapes confuse decision log analysis.

Fail open vs fail closed​

When the PDP is unreachable:

StrategyWhen
Fail closed (deny)Default for security-sensitive systems
Fail open (allow)Rare — only with explicit risk acceptance and monitoring

Fail closed is the right security default, but it means a PDP outage often presents as production down (403 / 5xx / timeouts) even though application hosts may still be up. That failure mode is on the customer-operated PEP → PDP path — not an EnforceAuth control-plane outage. See Runtime boundary.

Log PDP errors regardless. Prefer distinguishing policy deny from PDP unavailable in your PEP (different status / reason) so support and on-call can triage correctly. EnforceAuth ingests successful evaluations via decision_logs; PDP outages appear as gaps in coverage — monitor fleet status when using status:write.

Bundle server down is different: OPA typically keeps serving the last activated bundle — authorization continues with possibly stale policy. See Bundle polling — failure modes.

AuthZEN and future engines​

EnforceAuth's control plane is AuthZEN-oriented — PEP integration patterns stay stable as you adopt Cedar or other engines. Today, OPA PEPs use the REST shape above. See Authorization engines landscape.

Verify end to end​

  1. Deploy policy via EnforceAuth → bundle lands at bundle destination.
  2. PDP pulls bundle (PDP integration).
  3. PEP sends traffic → decisions appear in console Decisions.
  4. Policy Coverage moves from Never evaluated to Evaluated.

Use EACommerce as a working reference or follow the 15-Minute Quickstart.

Next​