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.
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:
| Field | Purpose |
|---|---|
user / principal | Identity, roles, attributes |
action | Verb being authorized (read, delete, invoke) |
resource | Target object (id, type, owner, classification) |
context | Request 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
| Layer | Tradeoff |
|---|---|
| Middleware | Centralized, easy to test; must forward enough context in input |
| Per-handler | Fine-grained; risk of inconsistent input shapes |
| API gateway | Uniform for north-south traffic; harder to pass rich resource attributes |
| Service mesh | Strong 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:
| Strategy | When |
|---|---|
| Fail closed (deny) | Default for security-sensitive systems |
| Fail open (allow) | Rare — only with explicit risk acceptance and monitoring |
Log PDP errors regardless. 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
- Deploy policy via EnforceAuth → bundle lands at bundle destination.
- PDP pulls bundle (PDP integration).
- PEP sends traffic → decisions appear in console Decisions.
- Policy Coverage moves from Never evaluated to Evaluated.
Use EACommerce as a working reference or follow the 15-Minute Quickstart.