Getting Started Guides — Review Findings
Tested 2026-03-18 against a clean deploy/manager.sh create instance with default env.
Quickstart (hosted-quickstart.md)
A. Introspect response mismatch (Medium)
Step 5 shows a minimal expected response:
{"active": true, "client_id": "quickstart-app", "scope": "read", "token_type": "Bearer", "exp": 1234567890}
Actual response includes extensions (with granted_resources, granted_resource_types), iat, and resource_types. Reader sees a very different shape and may think something is wrong.
Fix: Update expected response to match actual output, or at minimum note that additional fields will be present.
B. OPA refresh timing — "wait a few seconds" is misleading (Medium)
Guide says "wait a few seconds" after seeding policy data. OPA config has min_delay_seconds: 10 / max_delay_seconds: 20. Long polling (long_polling_timeout_seconds: 30) should make propagation sub-second once OPA is in a long-poll wait, but after a fresh seed the first poll cycle can take up to 20s.
During testing, a 5-second sleep after the Step 6 PATCH appeared to be insufficient — the denylist token request still succeeded. It worked after ~10s.
Fix: Either tighten OPA polling config for faster propagation, or change docs language to "wait 10-15 seconds" with a note explaining why (OPA polling interval).
C. Seed script URL doesn't exist (Low)
The tip box references $PBAC_URL/quickstart-seed.sh — this endpoint doesn't exist on the AS.
Fix: Either implement the seed script endpoint or remove the tip.
First Policy (first-policy.md)
D. PUT examples wipe out previous policy data (CRITICAL)
Every data example uses PUT /admin/api/policy-data/by-key?dataKey=oauth with a payload containing only the new keys. PUT replaces the entire oauth key. The denylist example:
{"payload": "{\"denylist\":{\"client_ids\":[\"compromised-client-001\"]}}"}
This wipes out resource.types seeded in the quickstart, breaking all token issuance.
The subject restriction and "add resource type" examples have the same problem — each one silently overwrites the previous state.
Verified: After running the denylist PUT, curl /v1/data/oauth/resource returns null. Token requests that previously worked now fail.
Fix options:
- Use the PATCH API (which the quickstart already demonstrates in Step 6) for incremental changes
- Include the complete merged payload in each PUT example
- Add a prominent warning that PUT replaces the entire key
E. Verification section references unregistered client (High)
The verification block tests compromised-client-001:secret:
curl -s -X POST $PBAC_URL/token \
-u compromised-client-001:secret \
-d "grant_type=client_credentials&scope=read"
This client was never registered. Response is invalid_client (authentication failure), not access_denied (policy denial). The reader can't verify the denylist works.
Fix: Use quickstart-app:app-secret (from the quickstart) and add quickstart-app to the denylist instead.
F. Rego extension example breaks OPA (CRITICAL)
The example uploads evaluations_ext.rego with only a deny function:
package oauth.evaluations_ext
import future.keywords.if
deny(resource, action, subject, context) if {
data.oauth_config.custom.maintenance_mode == true
}
This replaces the base evaluations_ext.rego which defines required defaults for rar_allow, rs_obligations, and subject_obligations. Once uploaded, OPA fails bundle activation:
Bundle activation failed: 6 errors occurred:
oauth/evaluations.rego:32: rego_type_error: undefined function data.oauth.evaluations_ext.rs_obligations
oauth/evaluations.rego:31: rego_type_error: undefined function data.oauth.evaluations_ext.subject_obligations
oauth/evaluations.rego:42: rego_type_error: undefined function data.oauth.evaluations_ext.rar_allow
...
OPA stops loading any new bundles until the extension is fixed or deleted. The system is effectively bricked.
Fix: The extension example must include all required stubs:
package oauth.evaluations_ext
import future.keywords.if
default deny(_, _, _, _) := false
default rar_allow(_, _, _, _) := false
default rs_obligations(_, _, _, _) := set()
default subject_obligations(_, _, _, _) := set()
deny(resource, action, subject, context) if {
data.oauth_config.custom.maintenance_mode == true
}
First Integration (first-integration.md)
G. Client registration uses wrong pattern — no software statement (CRITICAL)
Step 1 registers my-rs-001 with grantTypes and scopes fields:
{
"clientId": "my-rs-001",
"grantTypes": ["client_credentials"],
"scopes": ["uma_protection"]
}
No softwareStatement is provided. The quickstart teaches that software statements are the primary entitlement mechanism — OPA reads them to make authorization decisions. Without one, OPA denies all token requests for this client.
Verified: Step 2 (get PAT) fails with {"error":"access_denied"}.
Fix: Use the same software statement pattern established in the quickstart:
{
"clientId": "my-rs-001",
"clientSecret": "rs-secret-abc",
"clientType": "confidential",
"clientName": "My Resource Server",
"softwareStatement": {
"sub": "my-rs-001",
"grant_types": ["client_credentials"],
"granted_resources": [
{"type": "urn:as:introspect", "scopes": ["uma_protection"]}
]
}
}
H. Step 3 uses unregistered client (High)
Step 3 references client-app-001:app-secret which was never registered in this guide or the quickstart prerequisite. Response: {"error":"invalid_client"}.
Fix: Either register client-app-001 in this guide, or use quickstart-app:app-secret from the quickstart.
I. resource vs resource_types parameter inconsistency (Medium)
- Quickstart Step 4:
resource_types=urn:quickstart:data - First Integration Step 2:
resource=urn:as:introspect - First Integration Step 4:
resource=...&resource_types=...
Both are valid token endpoint parameters but mean different things:
resource= specific resource URI (e.g.,https://api.example.com/fhir)resource_types= type classification (e.g.,urn:example:Patient)
The quickstart establishes resource_types as the pattern, then First Integration silently switches to resource without explaining the difference or when to use which.
Fix: Add a brief note explaining the two parameters, or consistently use one pattern with a callout when introducing the other.
J. Overview mentions DCR prematurely (Low)
Overview says "DCR or static" for registration but only covers static (Admin API). The DCR mention adds confusion without value in a getting-started guide.
Fix: Remove the DCR mention from the overview, keep only the existing link to the DCR guide in Step 1's closing note.
Summary
| Severity | Issue | Guide | Status |
|---|---|---|---|
| CRITICAL | F: Rego extension breaks OPA | First Policy | Fixed |
| CRITICAL | D: PUT overwrites all policy data | First Policy | Fixed |
| CRITICAL | G: No software statement = denied | First Integration | Fixed |
| High | H: Unregistered client-app-001 | First Integration | Fixed |
| High | E: Unregistered compromised-client-001 | First Policy | Fixed |
| Medium | A: Introspect response mismatch | Quickstart | Fixed |
| Medium | I: resource vs resource_types confusion | All | Fixed |
| Medium | B: OPA refresh timing misleading | Quickstart | Fixed |
| Low | C: Seed script URL missing | Quickstart | Fixed |
| Low | J: DCR mention premature | First Integration | Fixed |