Skip to main content
Version: 1.0

Quickstart

From credentials to a policy decision in 5 minutes — just your PBAC instance and curl.

Fastest path

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
  • curl and jq

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)
Token request failed?
  • 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"]
}
]
}
}
note

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.

This is the core loop

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

  1. Check the client exists: curl -s $PBAC_URL/admin/api/clients/quickstart-app -H "X-Admin-API-Key: $ADMIN_KEY" | jq .
  2. Check policy data is seeded: Verify urn:quickstart:data appears in your policy data (see above).
  3. Check the software statement: The client's softwareStatement must include granted_resources matching the resource_types and scope in your token request.
  4. 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 }

  1. Token expired: Tokens have a TTL. Request a fresh one (Step 4).
  2. Wrong RS credentials: Verify quickstart-rs:rs-secret is correct.
  3. 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
  1. You seeded resource types — the system-level registry of what types and scopes exist.
  2. You registered two OAuth clients with software statements — each client's entitlements are declared on the client record, not in separate policy data.
  3. OPA evaluated the policy at token issuance — deciding whether to grant the token and with what scopes.
  4. OPA evaluated the policy again at introspection — the resource server verified the token and received the policy decision.
  5. 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