The problem
Authorization tends to begin as a handful of role checks and slowly spreads across controllers, services, and database queries. Aegis explores a different boundary: permissions, relationships, decisions, and explanations belong to a central authorization platform.
That boundary becomes especially important in multi-tenant software. A user may administer one tenant, observe another, and have no relationship with a third. A single IsAdmin flag cannot express that context safely. The system needs to answer not only who is this user?, but what relationship does this subject have with this resource inside this tenant?
Context and constraints
Aegis is designed as an engineering study of production authorization rather than a collection of controller attributes. Its constraints shape the architecture:
- every decision belongs to a tenant boundary;
- policy evaluation must be deterministic and testable;
- callers need a small, stable authorization contract;
- administrators need an audit trail without exposing sensitive internals;
- denial should be explainable without turning the explanation into an information leak;
- role-based and relationship-based models should be able to coexist.
These constraints deliberately move policy orchestration away from HTTP endpoints and domain services. Application code asks a question; the authorization system owns how that question is evaluated.
Architectural shape
flowchart LR
Client[Product application] --> API[Authorization API]
API --> Boundary[Tenant and store boundary]
Boundary --> Resolve[Resolve subject, action, and resource]
Resolve --> Evaluate[Policy and relationship evaluation]
Evaluate --> Decision[Explainable decision]
Decision --> Client
Decision --> Audit[(Audit record)]
Model[(Authorization models)] --> Evaluate
Tuples[(Relationship tuples)] --> Evaluate
Roles[(Roles and permissions)] --> Evaluate
The public API remains narrow even when evaluation becomes sophisticated. This protects consumers from changes in storage, graph traversal, caching, or policy composition.
The diagram is deliberately logical rather than a deployment promise. The
repository can evolve its processes and stores while preserving the evaluation
boundary expected by callers.
Decision lifecycle
An authorization check is useful when its inputs and outputs are stable enough
to inspect later. The intended lifecycle is:
sequenceDiagram
participant App as Product application
participant API as Aegis API
participant Eval as Evaluator
participant Data as Model and relationship stores
participant Audit as Audit boundary
App->>API: subject, action, resource, tenant/store
API->>Eval: validated request
Eval->>Data: load relevant model and relationships
Data-->>Eval: bounded evaluation data
Eval-->>API: allow/deny + safe reason trace
API->>Audit: decision event
API-->>App: stable decision contract
This flow separates evaluation diagnostics from the response returned to an
untrusted caller. A support-facing explanation can contain more detail than a
public denial without exposing the authorization graph.
Important decisions
Treat authorization as a decision, not a Boolean
Returning only true or false is convenient but loses operational context. A decision can carry a stable reason code, evaluated policy version, tenant, correlation identifier, and a safe explanation. Those details make support, auditing, and incident investigation possible without coupling callers to evaluator internals.
Make tenant context explicit
Tenant identity should not be guessed from ambient state deep inside the evaluation pipeline. Passing it through a validated request makes cross-tenant mistakes easier to test and harder to hide.
Separate policy definition from policy execution
Definitions describe intended access. The evaluator compiles that intent into a decision for a concrete subject and resource. Keeping those responsibilities separate supports versioning, review, caching, and future evaluation strategies.
Audit at the boundary
Audit records are produced where the final decision is known. Lower-level stores can emit diagnostics, but they should not independently claim that access was allowed or denied.
What it explores
The project models authorization stores, fine-grained relationships, runtime checks, graph queries, and human-readable allow or deny traces. It also treats administrative workflows and audit records as part of the product rather than incidental infrastructure.
The current work concentrates on the contracts between these pieces: how a request names a resource, how relationships are represented, how evaluators remain composable, and how audit events preserve enough context to be useful later.
The public repository currently includes the backend API, development seed
data, an administrative dashboard, documentation, tests, and local deployment
support. It also exposes OpenFGA-style compatibility endpoints so integration
ideas can be evaluated against a familiar authorization shape.
Evidence and boundaries
Aegis is under active development. The repository is suitable for local
development, demonstrations, and integration experiments; this case study does
not claim production traffic, external customers, or measured scale. Current
evidence is the source, tests, documentation, executable development setup, and
the decision contracts that can be inspected directly.
That boundary matters for performance language. Caching and graph traversal are
design concerns, but their correct limits must come from representative policy
graphs and measured workloads rather than invented throughput numbers.
Trade-offs
Centralization creates consistency and observability, but it also introduces a dependency on the authorization service and its data. That makes latency, caching, failure behavior, and deployment compatibility part of the security model.
Aegis therefore prefers explicit degraded behavior over optimistic fallback. If a protected operation cannot obtain a trustworthy decision, the safe default is denial. Read-heavy paths may eventually use carefully scoped caches, but cache keys must include every dimension that can change a decision — especially tenant, subject, resource, action, and policy version.
Relationship-based access can represent richer domains than flat roles, but graph traversal is harder to reason about and operate. The project treats traversal limits and cycle handling as correctness constraints, not later optimizations.
What I am learning
- Security boundaries become easier to maintain when they are visible in contracts.
- Explainability is an architectural feature, not a logging afterthought.
- Multi-tenancy changes identity, storage, caching, and observability simultaneously.
- A centralized platform should reduce policy duplication without becoming a universal business-rules engine.
- The most valuable tests describe denied paths and tenant isolation, not only successful access.
Why it matters
A correct answer is only half of an authorization decision. Operators and developers also need to understand which model, tuple, or rule produced it. Aegis is where I study that combination of security, multi-tenant boundaries, explainability, and production-oriented .NET architecture.
Current direction
The next milestones are to stabilize the authorization request and decision contracts, exercise tenant-isolation scenarios, and document the audit model. From there, performance work can be measured against realistic policy graphs rather than optimized in the abstract.