Authorization Runner For Advanced Scenarios
The @repo/auth-runner
package provides a simplified interface for using both role-based (RBAC) and attribute-based (ABAC) access control together in your application. It serves as an integration layer that combines the functionality of both authorization systems.
Overview
When implementing complex authorization systems, you often need to combine different authorization models. The @repo/auth-runner
package makes this easy by:
- Combining rules from both RBAC and ABAC systems
- Providing a simplified API for authorization checks
- Abstracting away the complexity of rule management
Installation
The package is included in the zopio
stack. If you need to install it separately:
pnpm add @repo/auth-runner
Usage
Using the auth-runner package is straightforward:
import { evaluateAccess } from "@repo/auth-runner";
// Check if a user has permission
const result = evaluateAccess({
context: userContext,
resource: "documents",
action: "read",
record: document
});
if (result.can) {
// User has permission
// Proceed with the operation
} else {
// Access denied
console.log(result.reason); // Optional reason for denial
}
With API Routes
import { NextResponse } from "next/server";
import { evaluateAccess } from "@repo/auth-runner";
export async function GET(request) {
const userContext = await getUserContext(request);
const document = await getDocument(request);
const result = evaluateAccess({
context: userContext,
resource: "documents",
action: "read",
record: document
});
if (!result.can) {
return NextResponse.json(
{ error: "Access denied", reason: result.reason },
{ status: 403 }
);
}
// Proceed with the operation
return NextResponse.json(document);
}
With React Components
import { useEffect, useState } from "react";
import { evaluateAccess } from "@repo/auth-runner";
function DocumentViewer({ documentId }) {
const [document, setDocument] = useState(null);
const [canView, setCanView] = useState(false);
const userContext = useUserContext(); // Your hook to get user context
useEffect(() => {
async function fetchDocument() {
const doc = await getDocument(documentId);
setDocument(doc);
const result = evaluateAccess({
context: userContext,
resource: "documents",
action: "read",
record: doc
});
setCanView(result.can);
}
fetchDocument();
}, [documentId, userContext]);
if (!document) return <div>Loading...</div>;
if (!canView) return <div>You don't have permission to view this document.</div>;
return (
<div>
<h1>{document.title}</h1>
<p>{document.content}</p>
</div>
);
}
How It Works
Under the hood, the auth-runner package:
- Imports rules from both
@repo/auth-rbac
and @repo/auth-abac
- Combines them into a single array of rules
- Uses the evaluation engine from
@repo/auth-rbac
to process these rules
// This is simplified version of what happens internally
import { rules as rbacRules } from "@repo/auth-rbac";
import { abacRules } from "@repo/auth-abac";
import { evaluateAccess as baseEvaluate } from "@repo/auth-rbac/engine/evaluate";
const combinedRules = [...rbacRules, ...abacRules];
export function evaluateAccess(params) {
return baseEvaluate({
rules: combinedRules,
...params
});
}
Benefits of Using Auth Runner
- Simplified API: No need to manually combine rules or specify them with each evaluation
- Consistent Authorization: Ensures all parts of your application use the same combined rule set
- Reduced Boilerplate: Eliminates repetitive code for rule combination
- Maintainability: Changes to rules in either system are automatically reflected
Best Practices
- Use auth-runner consistently: Replace direct usage of auth-rbac or auth-abac with auth-runner throughout your application
- Keep rule definitions separate: Continue defining RBAC rules in auth-rbac and ABAC rules in auth-abac
- Consider rule precedence: Rules are evaluated in order, so be mindful of how they’re combined
- Test thoroughly: Verify that the combined rules produce the expected authorization decisions
Example: Complete Authorization Flow
Here’s a complete example of how authorization works with auth-runner in a typical zopio
application:
- Define your RBAC rules in auth-rbac (role-based permissions)
- Define your ABAC rules in auth-abac (attribute-based conditions)
- Use auth-runner to evaluate permissions throughout your application
- The auth-runner package automatically combines and evaluates all rules
// In your API route or component
import { evaluateAccess } from "@repo/auth-runner";
// User context with both role and attribute information
const userContext = {
userId: "user-123",
role: "editor",
tenantId: "org-456",
department: "marketing",
region: "europe"
};
// Resource with relevant attributes
const document = {
id: "doc-789",
title: "Quarterly Report",
ownerId: "user-123",
department: "marketing",
confidential: false
};
// Simple permission check that evaluates both RBAC and ABAC rules
const result = evaluateAccess({
context: userContext,
resource: "documents",
action: "update",
record: document
});
if (result.can) {
// Proceed with update
}
This approach provides a clean, unified way to implement complex authorization logic that combines both role-based and attribute-based access control.