engineering/auth-security/skills/authorization-patterns/SKILL.md
This skill should be used when the user asks to "implement RBAC", "add authorization", "design permissions", "restrict access by role", or "set up access control". Also trigger for "who can do what", "implement policy enforcement", "attribute-based access control", "ABAC", or when the user needs to model permissions for resources, actions, or tenants.
npx skillsauth add harsh040506/claude-code-unified-skill-plugin-library engineering/auth-security/skills/authorization-patternsInstall 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.
| Model | Use When | Avoid When | |-------|----------|-----------| | RBAC (Role-Based) | Permissions map cleanly to job functions | Fine-grained per-resource rules are needed | | ABAC (Attribute-Based) | Access depends on context (time, location, resource attributes) | Team lacks policy engine infrastructure | | ReBAC (Relationship-Based) | Hierarchical ownership (e.g., Google Docs-style) | Relationships are flat or non-existent | | Flat ACLs | Small system, few resources | Roles multiply or permission logic grows complex |
For most SaaS applications, start with RBAC and layer ABAC conditions where business rules require it.
Define roles at the coarsest granularity that still captures distinct permission boundaries:
# Bad: too many roles
ROLES = ["admin", "editor", "viewer", "commenter", "readonly_editor", "super_viewer"]
# Good: coarse roles + fine-grained permissions
ROLES = {
"admin": {"resource:*:*"}, # All resources, all actions
"editor": {"resource:own:write", "resource:*:read"},
"viewer": {"resource:*:read"},
}
Map permissions as {resource}:{scope}:{action} strings — this makes wildcard matching and auditing straightforward.
def check_permission(user_permissions: set[str], required: str) -> bool:
"""
Checks exact match OR wildcard inheritance.
required = "document:own:delete"
matches: "document:own:delete", "document:own:*", "document:*:*", "resource:*:*"
"""
parts = required.split(":")
for i in range(len(parts), 0, -1):
wildcard = ":".join(parts[:i] + ["*"] * (len(parts) - i))
if wildcard in user_permissions or required in user_permissions:
return True
return False
Scope all role assignments to a tenant — never share roles across tenant boundaries:
@dataclass
class RoleAssignment:
user_id: str
role: str
tenant_id: str # Always required
resource_id: str | None = None # Optional: resource-scoped role
# Lookup: always include tenant_id in query
def get_user_permissions(user_id: str, tenant_id: str) -> set[str]:
assignments = db.query(
"SELECT role FROM role_assignments WHERE user_id = ? AND tenant_id = ?",
[user_id, tenant_id]
)
return union(ROLES[a.role] for a in assignments)
Use Open Policy Agent (OPA) for complex attribute-based rules:
# policy.rego
package authz
default allow = false
# Editors can update documents they own
allow {
input.action == "document:update"
input.user.role == "editor"
input.resource.owner_id == input.user.id
}
# Admins can do anything within a business hours window
allow {
input.user.role == "admin"
is_business_hours(input.context.timestamp)
}
is_business_hours(ts) {
hour := time.clock([ts, "UTC"])[0]
hour >= 9
hour < 18
}
Query OPA from your application:
import httpx
async def is_authorized(user: dict, action: str, resource: dict, context: dict) -> bool:
response = await httpx.post(
"http://opa:8181/v1/data/authz/allow",
json={"input": {"user": user, "action": action, "resource": resource, "context": context}},
timeout=0.1, # Authorization MUST be fast — fail closed on timeout
)
response.raise_for_status()
return response.json().get("result", False)
Apply authorization at every layer independently — defense in depth:
1. API Gateway: Rate limiting + coarse role check (valid session, correct tenant)
2. Service layer: Action-level permission check before business logic runs
3. Database layer: Row-level security (RLS) as a final backstop
Row-Level Security (PostgreSQL):
-- Enable RLS on the table
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
-- Only the owner or admins in the same tenant can SELECT
CREATE POLICY documents_select
ON documents
FOR SELECT
USING (
owner_id = current_setting('app.user_id')::uuid
OR current_setting('app.user_role') = 'admin'
AND tenant_id = current_setting('app.tenant_id')::uuid
);
-- Application sets context on every connection
SET LOCAL app.user_id = '...';
SET LOCAL app.user_role = 'editor';
SET LOCAL app.tenant_id = '...';
Model resource hierarchies by propagating permissions from parent to children:
Organization → Workspace → Project → Document
Rule: if you can read a workspace, you can list its projects.
if you can write a project, you can create documents in it.
Resolve effective permissions by walking the hierarchy bottom-up and accumulating:
def get_effective_permissions(user_id: str, resource_id: str) -> set[str]:
lineage = db.get_resource_lineage(resource_id) # [doc_id, project_id, workspace_id, org_id]
permissions: set[str] = set()
for ancestor_id in lineage:
permissions |= db.get_direct_permissions(user_id, ancestor_id)
return permissions
Cache aggressively but invalidate on any permission change event.
Centralize enforcement so it cannot be accidentally bypassed:
from functools import wraps
def require_permission(permission: str):
def decorator(func):
@wraps(func)
async def wrapper(request: Request, *args, **kwargs):
user = request.state.user
if user is None:
raise HTTPException(status_code=401)
if not check_permission(user.permissions, permission):
raise HTTPException(status_code=403) # Do NOT leak resource existence
return await func(request, *args, **kwargs)
return wrapper
return decorator
@router.delete("/documents/{doc_id}")
@require_permission("document:own:delete")
async def delete_document(doc_id: str, request: Request): ...
Return 403, not 404, when the user is authenticated but unauthorized. Return 404 only if the resource genuinely doesn't exist AND the user has read access.
| Mistake | Why It Fails | Correct Approach | |---------|-------------|-----------------| | Client-side permission checks only | JS can be modified | Enforce on every server-side request | | Checking ownership after fetching data | Data already returned; IDOR if response leaks it | Check authorization before touching DB | | Wildcard admin endpoints unguarded | Escalation path | Require explicit admin role on admin routes | | Roles stored in JWT, never re-validated | Revoked role still trusted until token expires | Re-validate roles on sensitive operations | | Global roles without tenant scoping | Tenant A admin can access tenant B | Always include tenant_id in permission lookups | | 404 for unauthorized records | Leaks resource existence | Return 403 for unauthorized, 404 only when read access confirmed |
For schema designs, OPA bundle patterns, and ReBAC graph models, see:
references/rbac-patterns.md — Role schema, wildcard permission trees, caching strategiesreferences/policy-design.md — OPA Rego patterns, ABAC attribute schemas, audit loggingtesting
Performs quality control on single-cell RNA-seq data (.h5ad or .h5 files) using scverse best practices with MAD-based filtering and comprehensive visualizations. Use when users request QC analysis, filtering low-quality cells, assessing data quality, or following scverse/scanpy best practices for single-cell analysis.
tools
Deep learning for single-cell analysis using scvi-tools. This skill should be used when users need (1) data integration and batch correction with scVI/scANVI, (2) ATAC-seq analysis with PeakVI, (3) CITE-seq multi-modal analysis with totalVI, (4) multiome RNA+ATAC analysis with MultiVI, (5) spatial transcriptomics deconvolution with DestVI, (6) label transfer and reference mapping with scANVI/scArches, (7) RNA velocity with veloVI, or (8) any deep learning-based single-cell method. Triggers include mentions of scVI, scANVI, totalVI, PeakVI, MultiVI, DestVI, veloVI, sysVI, scArches, variational autoencoder, VAE, batch correction, data integration, multi-modal, CITE-seq, multiome, reference mapping, latent space.
testing
This skill should be used when scientists need help with research problem selection, project ideation, troubleshooting stuck projects, or strategic scientific decisions. Use this skill when users ask to pitch a new research idea, work through a project problem, evaluate project risks, plan research strategy, navigate decision trees, or get help choosing what scientific problem to work on. Typical requests include "I have an idea for a project", "I'm stuck on my research", "help me evaluate this project", "what should I work on", or "I need strategic advice about my research".
development
Run nf-core bioinformatics pipelines (rnaseq, sarek, atacseq) on sequencing data. Use when analyzing RNA-seq, WGS/WES, or ATAC-seq data—either local FASTQs or public datasets from GEO/SRA. Triggers on nf-core, Nextflow, FASTQ analysis, variant calling, gene expression, differential expression, GEO reanalysis, GSE/GSM/SRR accessions, or samplesheet creation.