
September 10, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
Scattered if statements in controllers, Terraform modules, and CI scripts are where authorization and compliance rules go to die. Policy as Code with Open Policy Agent (OPA) moves those rules into version-controlled Rego policies that any service can query with a single HTTP call. If you ship REST APIs, Kubernetes workloads, or multi-team platforms, OPA gives you one engine for "allow or deny" decisions instead of duplicating logic in PHP, YAML, and shell. This guide covers how OPA works, how to write real policies, and where it fits next to framework-level tools like Laravel policies and gates.
What is Policy as Code with Open Policy Agent (OPA)?
OPA is a CNCF-graduated policy engine. You describe rules in Rego. OPA evaluates those rules against structured input and returns a decision. The engine is language-agnostic. Your Laravel app, a Go microservice, and a Kubernetes admission webhook can all ask the same OPA instance the same question: "Is this action permitted?"
Policy as Code is the practice of treating those rules like application code. Policies live in Git. They pass through pull requests, code review, and CI. Changes are auditable. Rollbacks are a revert, not a frantic hotfix across five repositories.
On production systems I maintain, authorization often starts inside the framework. That works until multiple services, external gateways, and infrastructure pipelines need the same rule. OPA sits at that boundary. It does not replace every @can directive in Blade. It centralizes cross-cutting rules that must hold everywhere.
The three building blocks
Every OPA integration uses the same pieces:
- Policy — Rego files that define rules and helper functions.
- Input — JSON describing the request, resource, or manifest under review.
- Query — The decision path, commonly
data.authz.allowfor authorization.
OPA returns JSON. Your caller interprets the result. That contract stays stable even when policy logic changes.
How do you write Rego policies for OPA?
Rego is a declarative language built for policy, not general programming. You define rules that are true when conditions match. There is no mutable state. You think in terms of sets, objects, and partial definitions.
Start with a minimal authorization policy. The official Rego policy language documentation is the authoritative reference for syntax and built-ins.
Example: role-based API access
Save this as policies/authz.rego:
package authz
import future.keywords.if
default allow := false
allow if {
input.method == "GET"
input.path == "/health"
}
allow if {
input.user.role == "admin"
}
allow if {
input.user.role == "editor"
input.method == "POST"
startswith(input.path, "/documents/")
}
deny_reason := "role lacks permission" if {
not allow
} Test input JSON:
{
"method": "POST",
"path": "/documents/42/share",
"user": {
"id": 17,
"role": "editor"
}
} Run OPA locally with the CLI:
opa eval --data policies/authz.rego \
--input request.json \
"data.authz.allow" Expected output when allowed:
{
"result": [
{
"expressions": [
{
"value": true,
"text": "data.authz.allow"
}
]
}
]
} Write tests before you deploy
Rego supports unit tests in _test.rego files. Treat them like any other test suite. Gate merges on passing OPA tests, similar to how teams use code coverage gates in CI.
package authz
test_editor_can_post_documents if {
allow with input as {
"method": "POST",
"path": "/documents/1",
"user": {"role": "editor"}
}
}
test_viewer_cannot_post_documents if {
not allow with input as {
"method": "POST",
"path": "/documents/1",
"user": {"role": "viewer"}
}
} Run tests:
opa test policies/ -v Validate JSON fixtures with a JSON formatter and linter before they enter your test suite. Malformed input causes false negatives that are painful to debug under load.
Where should you integrate OPA in a Laravel or API application?
Laravel 13 ships authorization through gates and policies. That layer belongs close to your domain models. OPA earns its place when rules span services or must match infrastructure policy.
On a legal-tech client portal, document sharing rules might depend on case status, user role, and jurisdiction. PHP policies handle request-level checks. OPA can enforce the same rule when a separate export service, a webhook worker, and an admin API all touch documents.
Sidecar versus embedded OPA
You have two common patterns:
- OPA sidecar — A container or local daemon on port 8181. Your app POSTs to
/v1/data/authz/allow. Best when many services share one policy bundle. - Embedded OPA — The
open-policy-agent/opaGo library inside a service. Lower latency, tighter coupling. Use when one high-throughput service owns the decision.
For PHP/Laravel stacks, the sidecar pattern is usually simpler. PHP calls OPA over HTTP with Guzzle or Laravel's HTTP client. No native Rego interpreter exists in PHP.
Laravel middleware calling OPA
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
class OpaAuthorize
{
public function handle(Request $request, Closure $next)
{
$response = Http::timeout(2)->post('http://127.0.0.1:8181/v1/data/authz/allow', [
'input' => [
'method' => $request->method(),
'path' => $request->path(),
'user' => [
'id' => $request->user()?->id,
'role' => $request->user()?->getRoleNames()->first(),
],
],
]);
if (! data_get($response->json(), 'result')) {
abort(403, 'Policy denied this action.');
}
return $next($request);
}
} Keep middleware thin. Map the request to JSON. Let Rego hold the rules. Fail closed when OPA is unreachable unless your risk model explicitly allows a degraded mode.
Projects that need deeper custom authorization logic often start with custom software development where domain rules are modeled first, then extracted into shared policy bundles as the platform grows.
OPA on Kubernetes and in CI
OPA Gatekeeper applies Rego to cluster resources at admission time. That pairs with Kubernetes pod security and network policies for defense in depth. In CI, OPA can scan Terraform plans before apply, complementing Infrastructure as Code with Terraform workflows and tools like Sentinel for Terraform policy.
How does OPA compare to Kyverno, Gatekeeper, and Sentinel?
Teams evaluating policy engines usually compare OPA with Kubernetes-native tools and cloud-vendor options. The right choice depends on where policies must run, not brand preference.
| Engine | Policy language | Best fit | Trade-off |
|---|---|---|---|
| OPA (standalone) | Rego | APIs, microservices, custom apps, multi-cloud | Rego learning curve; you operate the engine |
| OPA Gatekeeper | Rego via ConstraintTemplates | Kubernetes admission control | K8s-only; CRD overhead |
| Kyverno | YAML policies (no Rego) | Kubernetes mutate/validate/generate | Less portable outside K8s |
| HashiCorp Sentinel | Sentinel HCL-like | Terraform Cloud/Enterprise plans | Commercial; Terraform-centric |
| Laravel Policies | PHP | Single-app Eloquent authorization | Not shared across services or infra |
For a deeper Kubernetes-focused comparison, read Kyverno vs OPA Gatekeeper for policy. For org-wide governance spanning clouds and repos, see multi-cloud governance and policy as code.
OPA wins when one team must enforce the same logical rule in PHP APIs, Node workers, and cluster manifests. Kyverno wins when your team lives entirely inside Kubernetes and prefers YAML. Sentinel wins when you are already on HashiCorp Cloud Platform for Terraform.
What are common OPA deployment patterns in production?
Production OPA is more than a Rego file on disk. You need bundle delivery, health checks, observability, and a rollback path. Patterns I have seen work on enterprise application platforms and shared hosting stacks follow the same shape.
Policy bundles and hot reload
Build bundles with opa build or your CI pipeline. Serve them from an HTTP bundle server or object storage. OPA polls for updates. That decouples policy releases from application deploys.
opa run --server \
--set bundles.authz.service=local \
--set bundles.authz.resource=/bundles/authz.tar.gz \
--set bundles.authz.polling.min_delay_seconds=10 \
--set bundles.authz.polling.max_delay_seconds=20 Version bundles with Git tags. Pin each environment to a known tag. Staging can track main. Production should not.
Observability and failure modes
Log every deny with the policy path and input hash. Redact PII from logged input. Track p99 latency for OPA calls separately from app latency. A 50 ms policy check that runs on every request adds up fast.
Common failure modes:
- Timeout cascades — OPA slow or down; app threads block. Set aggressive HTTP timeouts and circuit breakers.
- Policy drift — PHP allows an action Rego denies. Align with contract tests that hit both layers.
- Over-broad input — Sending entire request bodies bloats evaluation. Send only fields Rego needs.
- Missing default deny — Without
default allow := false, undefined rules can surprise you.
Pair OPA with quality gates in CI. Tools like SonarQube for code quality and security gates catch application bugs. OPA catches policy violations before they reach production.
Security hardening checklist
- Run OPA on localhost or a private network segment, not the public internet.
- Sign bundles and verify signatures at load time.
- Limit Rego to approved built-ins; deny
http.sendin prod unless required. - Run
opa check --strictin CI on every pull request. - Document who owns policy changes — same RACI as application code.
For platforms that also expose AI endpoints, policy boundaries overlap with guardrails for autonomous AI agents. OPA can gate tool calls and data scopes before an LLM acts.
On client portals with document sharing, like work shipped for Mijar Law Associates, audit trails matter as much as the allow/deny bit. OPA's structured deny reasons make that logging straightforward.
Server-side enforcement still needs transport security. Combine OPA with headers defined in Content Security Policy for Laravel apps and standard TLS termination on your Linux production servers.
Key Takeaways
- Policy as Code with Open Policy Agent (OPA) centralizes allow/deny rules in version-controlled Rego instead of scattered conditionals.
- Start with
opa testand default-deny policies before wiring any production sidecar. - Keep Laravel policies for domain logic; use OPA when the same rule must hold across APIs, workers, Kubernetes, or CI.
- Deploy OPA as a sidecar for PHP stacks, poll signed bundles, and fail closed on timeouts.
- Log deny reasons with policy version tags for audit compliance on sensitive portals.
- Compare OPA with Kyverno and Sentinel based on deployment surface, not hype — one engine rarely covers every layer.
People Also Ask
Is Open Policy Agent only for Kubernetes?
No. Kubernetes admission control is a popular OPA use case through Gatekeeper, but OPA runs anywhere. Teams embed it in microservices, place it as a sidecar beside APIs, and run it in CI with tools like Conftest. The engine is portable by design.
Is Rego hard to learn for PHP or Laravel developers?
Rego feels unfamiliar at first because it is declarative, not imperative. Most Laravel developers become productive within a few days of writing tests alongside policies. Treat Rego like SQL or regex — specialized, but stable once you learn the evaluation model.
Can OPA replace Laravel authorization entirely?
Not usually. Laravel policies express Eloquent-centric rules close to models. OPA excels at shared, cross-service, and infrastructure rules. The practical approach is both — PHP for domain auth, Rego for platform-wide policy that must stay consistent everywhere.
How do you debug a deny decision in OPA?
Use opa eval --explain full locally with the same input JSON production sent. Add trace rules or structured deny_reason messages in Rego. Never debug production denials by guessing — reproduce the exact input in a test case and fix the policy with opa test.
Ship policy with the same discipline as application code
Policy as Code with Open Policy Agent (OPA) turns "who can do what" from tribal knowledge into tested, reviewable Rego. You gain one decision engine for APIs, clusters, and pipelines. The cost is operational — bundles, sidecars, and Rego fluency — but that cost beats untracked if statements spread across repos.
Start small. Pick one high-risk rule — document export, admin mutation, or Terraform resource type — write Rego tests, and wire a sidecar on staging. Expand only after deny logs prove the policy matches business intent.
If you want help designing authorization for a multi-service platform or a compliance-sensitive portal, contact us to talk through architecture. For ongoing enforcement after launch, testing and optimization services and support and maintenance keep policy bundles aligned with application releases.
Further reading: the official Open Policy Agent documentation and the CNCF OPA project page. For background on the author’s production work, see about me and the wider blog archive.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

