Quickstart
From credentials to a policy decision in 5 minutes — just your PBAC instance and curl.
Download the seed script, then run it to create all clients and policy data in one command. Jump to Step 4: Get a Token after.
# Make sure PBAC_URL and ADMIN_KEY are exported (see Prerequisites)
bash quickstart-seed.sh
Prerequisites
- Your PBAC instance URL
- Your admin API key
curlandjq
Set these once and every command below will work:
export PBAC_URL=https://pbac.example.com # your instance URL
export ADMIN_KEY=your-admin-api-key # your admin API key
Step 1: Verify your instance
curl -s $PBAC_URL/.well-known/openid-configuration | jq .issuer
You should see your instance URL as the issuer. If this fails, check that the URL is correct and the instance is running.
Step 2: Seed resource types
Policy data tells OPA what resource types exist and what scopes they accept. This is system-level configuration — not per-client:
curl -s -X PUT "$PBAC_URL/admin/api/policy-data/by-key?dataKey=oauth" \
-H "X-Admin-API-Key: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"dataKey": "oauth",
"payload": "{\"resource\":{\"types\":{\"urn:as:introspect\":{\"scopes\":[\"uma_protection\",\"introspection\"]},\"urn:quickstart:data\":{\"scopes\":[\"read\",\"write\"]}}}}"
}' | jq .
This defines two resource types: urn:quickstart:data (with read and write scopes) and urn:as:introspect (used by resource servers to call /introspect). No per-client configuration needed — that comes from the software statement on each client.
Wait 10-15 seconds for OPA to pick up the new bundle (OPA polls every 10-20 seconds), then proceed.
Step 3: Register two clients
You need two clients: an application client (requests tokens) and a resource server client (introspects tokens to enforce access). Each client's softwareStatement tells OPA what it's allowed to do — OPA reads these claims at every token and introspect request.
Application client:
curl -s -X POST $PBAC_URL/admin/api/clients \
-H "X-Admin-API-Key: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"clientId": "quickstart-app",
"clientSecret": "app-secret",
"clientType": "confidential",
"clientName": "Quickstart App",
"softwareStatement": {
"sub": "quickstart-app",
"grant_types": ["client_credentials"],
"granted_resources": [
{"type": "urn:quickstart:data", "scopes": ["read"]}
]
}
}' | jq .
You should see the full client record — clientId, clientType, enabled, and the fields you provided.
Resource server client (needs uma_protection scope to call /introspect):
curl -s -X POST $PBAC_URL/admin/api/clients \
-H "X-Admin-API-Key: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"clientId": "quickstart-rs",
"clientSecret": "rs-secret",
"clientType": "confidential",
"clientName": "Quickstart Resource Server",
"softwareStatement": {
"sub": "quickstart-rs",
"grant_types": ["client_credentials"],
"granted_resources": [
{"type": "urn:as:introspect", "scopes": ["uma_protection"]}
]
}
}' | jq .
Confirm both return "clientId" matching what you sent.
Step 4: Get a token
Request a token as the application client:
curl -s -X POST $PBAC_URL/token \
-u quickstart-app:app-secret \
-d 'grant_type=client_credentials&scope=read&resource_types=urn:quickstart:data' \
| jq .
You should see a response like:
{
"access_token": "eyJhbG...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read"
}
The resource_types parameter tells OPA which type of resource the client is requesting. Scopes are validated against this type's scope registry.
Save the access token:
TOKEN=$(curl -s -X POST $PBAC_URL/token \
-u quickstart-app:app-secret \
-d 'grant_type=client_credentials&scope=read&resource_types=urn:quickstart:data' \
| jq -r .access_token)
- 401/403: Check your client credentials (
quickstart-app:app-secret) - access_denied: OPA hasn't picked up the policy data yet — wait 10-15 seconds and retry
- No response: Verify the AS is running (
curl $PBAC_URL/.well-known/openid-configuration)
Step 5: Introspect and see the policy decision
Now call /introspect as the resource server to validate the token and see the policy decision:
curl -s -X POST $PBAC_URL/introspect \
-u quickstart-rs:rs-secret \
-d "token=$TOKEN" \
| jq .
You should see:
{
"active": true,
"scope": "read",
"client_id": "quickstart-app",
"token_type": "Bearer",
"exp": 1234567890,
"iat": 1234564290,
"resource_types": ["urn:quickstart:data"],
"extensions": {
"granted_resource_types": ["urn:quickstart:data"],
"granted_resources": [
{
"type": "urn:quickstart:data",
"granted_actions": ["read"]
}
]
}
}
Timestamps (exp, iat) will differ. The key fields to check: active: true, client_id: "quickstart-app", and extensions showing the granted resource type and actions.
This is OPA evaluating your policy in real time. The extensions field shows what OPA granted — the resource type and allowed actions. In production, your resource server makes this call on every request and enforces access based on the response.
Bonus: See a policy denial
Add quickstart-app to the denylist using the PATCH API — one line, one concept:
curl -s -X PATCH "$PBAC_URL/admin/api/policy-data/by-key?dataKey=oauth&path=denylist.client_ids" \
-H "X-Admin-API-Key: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '["quickstart-app"]'
Wait 10-15 seconds for OPA to refresh, then request a new token:
curl -s -X POST $PBAC_URL/token \
-u quickstart-app:app-secret \
-d 'grant_type=client_credentials&scope=read&resource_types=urn:quickstart:data' \
| jq .
You should see an error — the token request is denied. That is policy in action. You changed a single JSON key. No code deployed, no application restarted. The denial is immediate.
You changed a single policy data key via PATCH. No code deployed, no application restarted. Policy denial took effect immediately across every connected application. This is what centralized authorization means in practice.
Something went wrong?
OPA hasn't picked up the bundle yet
Policy data changes take a few seconds to propagate. Check the OPA bundle status:
curl -s $PBAC_URL/admin/api/policy-data/by-key?dataKey=oauth \
-H "X-Admin-API-Key: $ADMIN_KEY" | jq .
If the response contains your resource types, OPA should have them within 5-10 seconds. If not, verify the data was saved correctly.
Token request returns access_denied
- Check the client exists:
curl -s $PBAC_URL/admin/api/clients/quickstart-app -H "X-Admin-API-Key: $ADMIN_KEY" | jq . - Check policy data is seeded: Verify
urn:quickstart:dataappears in your policy data (see above). - Check the software statement: The client's
softwareStatementmust includegranted_resourcesmatching theresource_typesandscopein your token request. - Wait and retry: OPA polls the bundle every few seconds. Wait 10 seconds and try again.
Token request returns invalid_client (401)
The client ID or secret is wrong. Verify with:
curl -s $PBAC_URL/admin/api/clients/quickstart-app \
-H "X-Admin-API-Key: $ADMIN_KEY" | jq .clientId
If no client is returned, re-run Step 3.
Introspect returns { "active": false }
- Token expired: Tokens have a TTL. Request a fresh one (Step 4).
- Wrong RS credentials: Verify
quickstart-rs:rs-secretis correct. - Token was denylisted: If you completed Step 6, the client is on the denylist. Remove it:
curl -s -X PATCH "$PBAC_URL/admin/api/policy-data/by-key?dataKey=oauth&path=denylist.client_ids" \
-H "X-Admin-API-Key: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '[]'
AS is not responding
curl -s $PBAC_URL/.well-known/openid-configuration | jq .issuer
If this fails, the AS is not running or the URL is wrong. Check your $PBAC_URL variable.
What just happened
- You seeded resource types — the system-level registry of what types and scopes exist.
- You registered two OAuth clients with software statements — each client's entitlements are declared on the client record, not in separate policy data.
- OPA evaluated the policy at token issuance — deciding whether to grant the token and with what scopes.
- OPA evaluated the policy again at introspection — the resource server verified the token and received the policy decision.
- You PATCHed a single key in policy data (added a denylist entry) and saw the denial take effect immediately.
Key concepts:
- Software statements are the primary entitlement mechanism — they live on the client record and OPA reads them directly.
- Policy data is for system config (resource type registry) and optional overrides (denylists, trust levels).
- You don't need to configure clients in two places. Register once with a software statement and you're done.
Next steps
- Your First Policy — Write custom Rego rules to extend the built-in policy
- First Integration — Connect an Identity Provider and build authorization code flows
- How it works — Understand the full access model