plugins/backend-toolkit/skills/authorization/SKILL.md
Design access control — RBAC for coarse function-level checks, Postgres Row Level Security (RLS) for row-level data isolation, ABAC pushed to the app/policy layer. Use when adding permissions, building multi-user data access, or when one user can see another's data. Not for establishing who the caller is (use authentication) or tenant isolation specifically (use multitenancy-audit).
npx skillsauth add jaykim88/claude-ai-engineering authorizationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Control what an authenticated caller can do and which rows they can touch — using the right mechanism at the right layer so a missing check can't leak another user's data (the #1 API vulnerability).
Universal — RBAC vs ABAC, the function-level-vs-row-level distinction, and "enforce at the data layer when possible" are authz principles; Postgres RLS is the default enforcement primitive.
Separate coarse (function) from fine (row) authorization
Use RBAC for role/permission gates
Use Postgres RLS for row-level isolation
USING / WITH CHECK)WHERE user_id = ? — defense in depthSET LOCAL app.current_user) inside the transactionPush ABAC (attribute/context rules) to the app or a policy engine
Check authorization at the right layer — never client-only
5b. Verify ownership at every level of a nested resource
/orgs/:o/teams/:t/members/:m — checking only that the caller owns :o lets them touch any :t or :m. Re-verify the chain: m ∈ t ∈ o ∈ caller-orgs5c. Mind the cost of per-request authorization
| ❌ Anti-pattern | ✅ Correct |
|---|---|
| Function-level check only (no row check) | RBAC (function) + RLS (row) together |
| Relying on app WHERE user_id = ? alone | RLS as defense-in-depth at the DB |
| Forcing ABAC rules into RLS policies | Push attribute/context rules to app/policy engine |
| Client-side permission enforcement | Server + DB enforcement |
| RLS USING (true) (effectively disabled) | Real tenant/user predicate |
| Checking ownership only at the parent of a nested resource | Verify the full chain (m ∈ t ∈ o ∈ caller-orgs) |
| Permission check hitting DB on every request | Per-request memoization or short-TTL session cache (RLS still enforces at DB) |
| Tier | Examples | Action SLA |
|---|---|---|
| Critical | BOLA — user can access another user's data by changing an ID; RLS disabled or USING (true) on a user-data table | Block release; fix immediately |
| Major | Function-level check present but row-level missing; ABAC rule enforced only client-side | Fix this sprint |
| Minor | Over-granular roles; policy duplication between layers | Schedule within 2 sprints |
USING / WITH CHECK expression — confirm the new predicate is at least as restrictive (or intentionally widened, with reasoning)feat(authz): RLS policy for <table> / fix(authz): add row-level check to <endpoint>ALTER TABLE x ENABLE ROW LEVEL SECURITY; CREATE POLICY ... USING (auth.uid() = user_id)@UseGuards(RolesGuard) for function-level; RLS handles row-levelSET LOCAL inside the request transaction (see multitenancy-audit)SET LOCALSET LOCAL per request; Casbin for policyauthentication — authz comes after authn establishes identitymultitenancy-audit — tenant isolation is RLS applied to a tenant_idbackend-security-audit — broken object-level authz (BOLA) is OWASP API #1development
Design webhooks correctly on both sides — sending (HMAC signing, retries with backoff, at-least-once) and receiving (verify signature on raw body, enqueue + 200 fast, dedupe on event id). Use when adding webhook delivery or consuming a provider's webhooks. Not for internal service-to-service events (use async-messaging) or general outbound-call retry policy (use resilience-patterns).
testing
Use transactions and isolation levels correctly — keep them short, no network calls inside, explicit isolation, retry on serialization conflicts, and choose optimistic vs pessimistic locking. Use when a write spans multiple tables, when concurrent updates corrupt data, or when designing money/inventory flows. Not for cross-service event delivery (use async-messaging Outbox) or schema-level constraints (use schema-design).
development
Backend testing pyramid — unit for pure logic, integration against a real DB (Testcontainers), and consumer-driven contract testing (Pact) for service boundaries. Use before a feature, after a bug fix, or when services break each other on deploy. Not for load testing (use performance-profiling) or security testing (use backend-security-audit).
data-ai
Design a relational schema — normalize to 3NF then denormalize with justification, choose the right Postgres index type per data shape, enforce constraints at the DB. Use when modeling a new domain, when queries are slow, or before a migration. Not for diagnosing slow queries (use query-optimization) or shipping the change without downtime (use migration-strategy).