{"id":22882396,"url":"https://github.com/loresoft/privileged","last_synced_at":"2026-01-16T11:54:41.629Z","repository":{"id":216239653,"uuid":"740804259","full_name":"loresoft/Privileged","owner":"loresoft","description":"Privileged is an authorization library for restricting resources by action, subject and qualifiers.","archived":false,"fork":false,"pushed_at":"2026-01-09T08:19:57.000Z","size":1339,"stargazers_count":76,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-01-14T00:57:35.476Z","etag":null,"topics":["access-control","authorization","blazor","field-level-security","permissions","policy","rule-based"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/loresoft.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"loresoft"}},"created_at":"2024-01-09T05:15:43.000Z","updated_at":"2026-01-10T15:26:34.000Z","dependencies_parsed_at":"2025-11-27T09:09:16.128Z","dependency_job_id":null,"html_url":"https://github.com/loresoft/Privileged","commit_stats":null,"previous_names":["loresoft/authorizone","loresoft/privileged"],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/loresoft/Privileged","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loresoft%2FPrivileged","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loresoft%2FPrivileged/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loresoft%2FPrivileged/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loresoft%2FPrivileged/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/loresoft","download_url":"https://codeload.github.com/loresoft/Privileged/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loresoft%2FPrivileged/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28478391,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T06:30:42.265Z","status":"ssl_error","status_checked_at":"2026-01-16T06:30:16.248Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["access-control","authorization","blazor","field-level-security","permissions","policy","rule-based"],"created_at":"2024-12-13T18:17:18.959Z","updated_at":"2026-01-16T11:54:36.612Z","avatar_url":"https://github.com/loresoft.png","language":"C#","readme":"# Privileged\n\nPrivileged is an authorization library for restricting resources by action, subject and qualifiers.\nIt's designed to be incrementally adoptable and can easily scale between a simple claim based and fully featured\nsubject and action based authorization. It makes it easy to manage and share permissions across UI components,\nAPI services, and database queries.\n\n[![Build Project](https://github.com/loresoft/Privileged/actions/workflows/dotnet.yml/badge.svg)](https://github.com/loresoft/Privileged/actions/workflows/dotnet.yml)\n[![License](https://img.shields.io/github/license/loresoft/Privileged.svg)](https://github.com/loresoft/Privileged/blob/main/LICENSE)\n[![Coverage Status](https://coveralls.io/repos/github/loresoft/Privileged/badge.svg?branch=main)](https://coveralls.io/github/loresoft/Privileged?branch=main)\n\n| Package                                                                              | Version                                                                                                                           | Description                                                          |\n| ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |\n| [Privileged](https://www.nuget.org/packages/Privileged/)                             | [![NuGet](https://img.shields.io/nuget/v/Privileged.svg)](https://www.nuget.org/packages/Privileged/)                             | Core authorization library for rule-based permissions                |\n| [Privileged.Authorization](https://www.nuget.org/packages/Privileged.Authorization/) | [![NuGet](https://img.shields.io/nuget/v/Privileged.Authorization.svg)](https://www.nuget.org/packages/Privileged.Authorization/) | ASP.NET Core authorization integration with attribute-based policies |\n| [Privileged.Components](https://www.nuget.org/packages/Privileged.Components/)       | [![NuGet](https://img.shields.io/nuget/v/Privileged.Components.svg)](https://www.nuget.org/packages/Privileged.Components/)       | Blazor components for privilege-aware UI elements                    |\n| [Privileged.Endpoint](https://www.nuget.org/packages/Privileged.Endpoint/)           | [![NuGet](https://img.shields.io/nuget/v/Privileged.Endpoint.svg)](https://www.nuget.org/packages/Privileged.Endpoint/)           | ASP.NET Core endpoint extensions for privilege requirements          |\n\n## Installation\n\nInstall the core package via NuGet:\n\n```bash\ndotnet add package Privileged\n```\n\nFor ASP.NET Core applications with attribute-based authorization, also install the authorization package:\n\n```bash\ndotnet add package Privileged.Authorization\n```\n\nFor ASP.NET Core applications using minimal APIs with privilege requirements, also install the endpoint package:\n\n```bash\ndotnet add package Privileged.Endpoint\n```\n\nFor Blazor applications, also install the components package:\n\n```bash\ndotnet add package Privileged.Components\n```\n\n## Features\n\n- **Versatile** An incrementally adoptable and can easily scale between a simple claim based and fully featured subject and attribute based authorization.\n- **Isomorphic** Can be used on front-end and back-end and complementary packages make integration with Frontend and Backend effortless\n- **Declarative** Thanks to declarative rules, you can serialize and share permissions between UI and API or microservices\n- **Rule-based** Support for both allow and forbid rules with rule precedence\n- **Aliases** Create reusable aliases for actions, subjects, and qualifiers\n- **Qualifiers** Fine-grained control with field-level permissions\n- **ASP.NET Core Integration** Seamless integration with ASP.NET Core authorization using attribute-based policies\n- **Blazor Integration** Ready-to-use components for conditional rendering and privilege-aware form inputs\n- **Performance Optimized** Efficient rule evaluation and matching algorithms\n\n## General\n\nPrivileged operates on rules for what a user can actually do in the application. A rule itself depends on the 3 parameters:\n\n1. **Action** Describes what user can actually do in the app. User action is a word (usually a verb) which depends on the business logic (e.g., `update`, `read`). Very often it will be a list of words from CRUD - `create`, `read`, `update` and `delete`.\n2. **Subject** The subject which you want to check user action on. Usually this is a business (or domain) entity name (e.g., `Subscription`, `Post`, `User`).\n3. **Qualifiers** Can be used to restrict user action only to matched subject's qualifiers (e.g., to allow moderator to update `published` field of `Post` but not update `description` or `title`)\n\n## Basic Usage\n\n### Simple Rules\n\nUsing builder to create basic allow and forbid rules:\n\n```csharp\nvar context = new PrivilegeBuilder()\n    .Allow(\"read\", \"Post\")\n    .Allow(\"write\", \"User\")\n    .Forbid(\"delete\", \"User\")\n    .Build();\n\n// Check permissions\nbool canReadPost = context.Allowed(\"read\", \"Post\");      // true\nbool canWriteUser = context.Allowed(\"write\", \"User\");    // true\nbool canDeleteUser = context.Allowed(\"delete\", \"User\");  // false\nbool canReadUser = context.Allowed(\"read\", \"User\");      // false (not explicitly allowed)\n```\n\n### Wildcard Rules\n\nUse wildcards to allow all actions on a subject or an action on all subjects:\n\n```csharp\nvar context = new PrivilegeBuilder()\n    .Allow(\"test\", PrivilegeRule.Any)     // Allow 'test' action on any subject\n    .Allow(PrivilegeRule.Any, \"Post\")     // Allow any action on 'Post'\n    .Forbid(\"publish\", \"Post\")            // Forbid overrides allow\n    .Build();\n\ncontext.Allowed(\"read\", \"Post\").Should().BeTrue();\ncontext.Allowed(\"update\", \"Post\").Should().BeTrue();\ncontext.Allowed(\"archive\", \"Post\").Should().BeTrue();\ncontext.Allowed(\"read\", \"User\").Should().BeFalse();\ncontext.Allowed(\"delete\", \"Post\").Should().BeTrue();\ncontext.Allowed(\"publish\", \"Post\").Should().BeFalse();  // Forbid takes precedence\ncontext.Allowed(\"test\", \"User\").Should().BeTrue();\ncontext.Allowed(\"test\", \"Post\").Should().BeTrue();\n```\n\n### Using Qualifiers\n\nQualifiers provide field-level or fine-grained permissions:\n\n```csharp\nvar context = new PrivilegeBuilder()\n    .Allow(\"read\", \"Post\", [\"title\", \"id\"])   // Only allow reading specific fields\n    .Allow(\"read\", \"User\")                    // Allow reading all User fields\n    .Build();\n\n// Post permissions with qualifiers\ncontext.Allowed(\"read\", \"Post\").Should().BeTrue();           // General permission\ncontext.Allowed(\"read\", \"Post\", \"id\").Should().BeTrue();     // Specific field allowed\ncontext.Allowed(\"read\", \"Post\", \"title\").Should().BeTrue();  // Specific field allowed\ncontext.Allowed(\"read\", \"Post\", \"content\").Should().BeFalse(); // Field not allowed\n\n// User permissions without qualifiers\ncontext.Allowed(\"read\", \"User\").Should().BeTrue();           // All fields allowed\ncontext.Allowed(\"read\", \"User\", \"id\").Should().BeTrue();     // Any field allowed\n```\n\n## Advanced Features\n\n### Multiple Actions and Subjects\n\nUse extension methods for bulk rule creation:\n\n```csharp\nvar context = new PrivilegeBuilder()\n    .Allow([\"read\", \"update\"], \"Post\")                    // Multiple actions, single subject\n    .Allow(\"read\", [\"Post\", \"User\"])                      // Single action, multiple subjects\n    .Allow([\"create\", \"read\"], [\"Post\", \"Comment\"])       // Multiple actions and subjects\n    .Build();\n\ncontext.Allowed(\"read\", \"Post\").Should().BeTrue();\ncontext.Allowed(\"update\", \"Post\").Should().BeTrue();\ncontext.Allowed(\"read\", \"User\").Should().BeTrue();\ncontext.Allowed(\"create\", \"Comment\").Should().BeTrue();\n```\n\n### Aliases\n\nCreate reusable aliases for common groupings:\n\n```csharp\nvar context = new PrivilegeBuilder()\n    .Alias(\"Manage\", [\"Create\", \"Update\", \"Delete\"], PrivilegeMatch.Action)\n    .Allow(\"Manage\", \"Project\")                         // Allows all actions defined in the \"Manage\" alias\n    .Allow(\"Read\", \"User\")                              // Allows reading User\n    .Allow(\"Update\", \"User\", [\"Profile\", \"Settings\"])   // Allows updating User's Profile and Settings\n    .Forbid(\"Delete\", \"User\")                           // Forbids deleting User\n    .Build();\n\nbool canCreateProject = context.Allowed(\"Create\", \"Project\");           // true\nbool canReadUser = context.Allowed(\"Read\", \"User\");                     // true\nbool canUpdateProfile = context.Allowed(\"Update\", \"User\", \"Profile\");   // true\nbool canUpdatePassword = context.Allowed(\"Update\", \"User\", \"Password\"); // false\nbool canDeleteUser = context.Allowed(\"Delete\", \"User\");                 // false\n```\n\n### Qualifier Aliases\n\nAliases can also be used for qualifiers:\n\n```csharp\nvar context = new PrivilegeBuilder()\n    .Alias(\"PublicFields\", [\"title\", \"summary\", \"author\"], PrivilegeMatch.Qualifier)\n    .Allow(\"read\", \"Post\", [\"PublicFields\"])\n    .Build();\n\ncontext.Allowed(\"read\", \"Post\", \"title\").Should().BeTrue();\ncontext.Allowed(\"read\", \"Post\", \"summary\").Should().BeTrue();\ncontext.Allowed(\"read\", \"Post\", \"content\").Should().BeFalse();\n```\n\n## Rule Evaluation\n\n### Rule Precedence\n\nRules are evaluated in the order they are defined, with more specific rules taking precedence:\n\n1. **Forbid rules** always take precedence over allow rules when both match\n2. Rules are matched based on exact string comparison\n3. Wildcard rules `PrivilegeRule.Any` match any value\n4. Alias expansion happens during rule matching\n\n### String Comparison\n\nBy default, rule matching uses `StringComparer.InvariantCultureIgnoreCase`. You can customize this:\n\n```csharp\nvar context = new PrivilegeContext(rules, aliases, StringComparer.Ordinal);\n```\n\n## API Reference\n\n### PrivilegeBuilder\n\n- `Allow(string action, string subject, IEnumerable\u003cstring\u003e? qualifiers = null)` - Add an allow rule\n- `Forbid(string action, string subject, IEnumerable\u003cstring\u003e? qualifiers = null)` - Add a forbid rule\n- `Alias(string alias, IEnumerable\u003cstring\u003e values, PrivilegeMatch type)` - Create an alias\n- `Build()` - Create the PrivilegeContext\n\n### PrivilegeContext\n\n- `Allowed(string? action, string? subject, string? qualifier = null)` - Check if action is allowed\n- `Forbidden(string? action, string? subject, string? qualifier = null)` - Check if action is forbidden\n- `MatchRules(string? action, string? subject, string? qualifier = null)` - Get matching rules\n\n### Extension Methods\n\n- `Allow(IEnumerable\u003cstring\u003e actions, string subject, ...)` - Allow multiple actions on single subject\n- `Allow(string action, IEnumerable\u003cstring\u003e subjects, ...)` - Allow single action on multiple subjects\n- `Allow(IEnumerable\u003cstring\u003e actions, IEnumerable\u003cstring\u003e subjects, ...)` - Allow multiple actions on multiple subjects\n- Similar `Forbid` overloads for forbid rules\n\n## ASP.NET Core Authorization Integration\n\nThe `Privileged.Authorization` package provides seamless integration with ASP.NET Core's authorization system through attribute-based policies.\n\n### Authorization Setup\n\nFirst, configure the authorization services in your `Program.cs`:\n\n```csharp\n// Program.cs\nusing Privileged.Authorization;\n\nvar builder = WebApplication.CreateBuilder(args);\n\n// Add authentication here (JWT/Cookies/etc.)\nbuilder.Services.AddAuthentication(...);\nbuilder.Services.AddAuthorization();\n\n// Register privilege services + your provider\nbuilder.Services.AddPrivilegeAuthorization();\nbuilder.Services.AddScoped\u003cIPrivilegeContextProvider, YourPrivilegeContextProvider\u003e();\n\nvar app = builder.Build();\n\napp.UseAuthentication();\napp.UseAuthorization();\n\nvar app = builder.Build();\n```\n\n### Using the PrivilegeAttribute\n\nUse the `[Privilege]` attribute on controllers and actions to declaratively specify authorization requirements:\n\n```csharp\n[ApiController]\n[Route(\"api/[controller]\")]\npublic class PostsController : ControllerBase\n{\n    [HttpGet]\n    [Privilege(\"read\", \"Post\")]\n    public IActionResult GetPosts()\n    {\n        // Only users with \"read\" privilege on \"Post\" can access this\n        return Ok();\n    }\n\n    [HttpPost]\n    [Privilege(\"create\", \"Post\")]\n    public IActionResult CreatePost([FromBody] CreatePostRequest request)\n    {\n        // Only users with \"create\" privilege on \"Post\" can access this\n        return Ok();\n    }\n\n    [HttpPut(\"{id}\")]\n    [Privilege(\"update\", \"Post\")]\n    public IActionResult UpdatePost(int id, [FromBody] UpdatePostRequest request)\n    {\n        // Only users with \"update\" privilege on \"Post\" can access this\n        return Ok();\n    }\n\n    [HttpDelete(\"{id}\")]\n    [Privilege(\"delete\", \"Post\")]\n    public IActionResult DeletePost(int id)\n    {\n        // Only users with \"delete\" privilege on \"Post\" can access this\n        return NoContent();\n    }\n\n    [HttpPut(\"{id}/title\")]\n    [Privilege(\"update\", \"Post\", \"title\")]\n    public IActionResult UpdatePostTitle(int id, [FromBody] string title)\n    {\n        // Only users with \"update\" privilege on \"Post\" for \"title\" field can access this\n        return Ok();\n    }\n}\n```\n\n### Manual Authorization Checks\n\nYou can also perform manual authorization checks in your controllers:\n\n```csharp\n[ApiController]\n[Route(\"api/[controller]\")]\npublic class PostsController : ControllerBase\n{\n    private readonly IPrivilegeContextProvider _contextProvider;\n\n    public PostsController(IPrivilegeContextProvider contextProvider)\n    {\n        _contextProvider = contextProvider;\n    }\n\n    [HttpGet(\"{id}\")]\n    public async Task\u003cIActionResult\u003e GetPost(int id)\n    {\n        var context = await _contextProvider.GetContextAsync();\n\n        if (!context.Allowed(\"read\", \"Post\"))\n        {\n            return Forbid();\n        }\n\n        // Additional business logic...\n        return Ok();\n    }\n\n    [HttpPut(\"{id}\")]\n    public async Task\u003cIActionResult\u003e UpdatePost(int id, [FromBody] UpdatePostRequest request)\n    {\n        var context = await _contextProvider.GetContextAsync();\n\n        // Check different permissions based on what's being updated\n        if (!string.IsNullOrEmpty(request.Title) \u0026\u0026 !context.Allowed(\"update\", \"Post\", \"title\"))\n        {\n            return Forbid(\"Cannot update post title\");\n        }\n\n        if (!string.IsNullOrEmpty(request.Content) \u0026\u0026 !context.Allowed(\"update\", \"Post\", \"content\"))\n        {\n            return Forbid(\"Cannot update post content\");\n        }\n\n        // Perform update...\n        return Ok();\n    }\n}\n```\n\n#### Minimal API Example\n\nYou can also use privilege-based authorization with ASP.NET Core Minimal APIs. The `[Privilege]` attribute works on route handler delegates, and the dynamic policies will be generated in exactly the same way.\n\nFor minimal APIs that need to use the `RequirePrivilege` extension method, also add the `Privileged.Endpoint` package.\n\n```csharp\nusing Microsoft.AspNetCore.Authorization;\nusing Privileged.Authorization;\nusing Privileged.Endpoint;\n\nvar builder = WebApplication.CreateBuilder(args);\n\n// Add authentication here (JWT/Cookies/etc.)\nbuilder.Services.AddAuthentication(...);\nbuilder.Services.AddAuthorization();\n\n// Register privilege services + your provider\nbuilder.Services.AddPrivilegeAuthorization\u003cDatabasePrivilegeContextProvider\u003e();\n\n\nvar app = builder.Build();\n\napp.UseAuthentication();\napp.UseAuthorization();\n\n// Simple collection endpoint requiring read privilege on Post\napp.MapGet(\"/api/posts\", [Privilege(\"read\", \"Post\")] () =\u003e Results.Ok(new[] { new { Id = 1, Title = \"Hello\" } }));\n\n// Create endpoint requiring create privilege\napp.MapPost(\"/api/posts\", [Privilege(\"create\", \"Post\")] (CreatePostRequest req) =\u003e\n{\n    // Business logic...\n    return Results.Created($\"/api/posts/{123}\", req);\n});\n\n// Update with qualifier (field-level) example using RequirePrivilege extension\napp.MapPut(\"/api/posts/{id}/title\", (int id, string title) =\u003e\n{\n    // Only users with update privilege on Post:title reach here\n    return Results.Ok();\n}).RequirePrivilege(\"update\", \"Post\", \"title\");\n\n// Manual check example inside a handler\napp.MapPut(\"/api/posts/{id}\", async (int id, UpdatePostRequest req, IPrivilegeContextProvider provider) =\u003e\n{\n    var context = await provider.GetContextAsync();\n\n    if (req.Title is not null \u0026\u0026 !context.Allowed(\"update\", \"Post\", \"title\"))\n        return Results.Forbid();\n\n    if (req.Content is not null \u0026\u0026 !context.Allowed(\"update\", \"Post\", \"content\"))\n        return Results.Forbid();\n\n    return Results.Ok();\n});\n\napp.Run();\n\n// Example request models\npublic record CreatePostRequest(string Title, string Content);\npublic record UpdatePostRequest(string? Title, string? Content);\n```\n\nKey points:\n\n- Apply `[Privilege]` directly to route handler delegates.\n- Dynamic policy names follow the `Privilege:action:subject[:qualifier]` format automatically.\n- For ad-hoc logic or multiple field checks, inject `IPrivilegeContextProvider` and perform manual `Allowed` calls.\n- Combine with your existing authentication middleware (JWT, cookies, etc.).\n\n### IPrivilegeContextProvider Implementation\n\nThe `IPrivilegeContextProvider` interface allows you to load privilege contexts asynchronously, which is useful for scenarios where permissions are loaded from external sources like APIs, databases, or authentication systems. This interface is used by both ASP.NET Core and Blazor applications.\n\n#### ASP.NET Core: Database-Based Provider\n\nFor ASP.NET Core applications that load permissions from a database or external service.\n\n```csharp\npublic class DatabasePrivilegeContextProvider : IPrivilegeContextProvider\n{\n    private readonly IUserPermissionService _permissionService;\n    private readonly HybridCache _cache;\n    private readonly ILogger\u003cDatabasePrivilegeContextProvider\u003e _logger;\n\n    public DatabasePrivilegeContextProvider(\n        IUserPermissionService permissionService,\n        HybridCache cache,\n        ILogger\u003cDatabasePrivilegeContextProvider\u003e logger)\n    {\n        _permissionService = permissionService;\n        _cache = cache;\n        _logger = logger;\n    }\n\n    public async ValueTask\u003cPrivilegeContext\u003e GetContextAsync(ClaimsPrincipal? claimsPrincipal = null)\n    {\n        var user = claimsPrincipal;\n\n        // Return empty context for unauthenticated users\n        if (user?.Identity?.IsAuthenticated != true)\n            return PrivilegeContext.Empty;\n\n        try\n        {\n            var userId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;\n            if (userId == null)\n            {\n                _logger.LogWarning(\"User ID not found in claims\");\n                return PrivilegeContext.Empty;\n            }\n\n            string cacheKey = $\"privileged:permissions:{userId}\";\n\n            // Cache the privilege model to avoid repeated DB/service calls per request.\n            PrivilegeModel privilegeModel = await _cache.GetOrCreateAsync(\n                cacheKey,\n                async ct =\u003e await _permissionService.GetUserPermissionsAsync(userId),\n                options =\u003e options.SetExpiration(TimeSpan.FromMinutes(5))\n            );\n\n            return new PrivilegeContext(privilegeModel);\n        }\n        catch (Exception ex)\n        {\n            _logger.LogError(ex, \"Failed to load privileges for current user\");\n            return PrivilegeContext.Empty;\n        }\n    }\n}\n```\n\n#### Blazor: API-Based Provider\n\nFor Blazor applications that load permissions from an API:\n\n```csharp\npublic class HttpPrivilegeContextProvider : IPrivilegeContextProvider\n{\n    private readonly HttpClient _httpClient;\n    private readonly ILogger\u003cHttpPrivilegeContextProvider\u003e _logger;\n\n    public HttpPrivilegeContextProvider(\n        HttpClient httpClient,\n        ILogger\u003cHttpPrivilegeContextProvider\u003e logger)\n    {\n        _httpClient = httpClient;\n        _logger = logger;\n    }\n\n    public async ValueTask\u003cPrivilegeContext\u003e GetContextAsync(ClaimsPrincipal? claimsPrincipal = null)\n    {\n        try\n        {\n            // Load privilege model from API\n            var privilegeModel = await _httpClient.GetFromJsonAsync\u003cPrivilegeModel\u003e(\"/api/user/privileges\");\n            return new PrivilegeContext(privilegeModel);\n        }\n        catch (Exception ex)\n        {\n            _logger.LogError(ex, \"Failed to load user privileges\");\n\n            // Return minimal context with basic read permissions as fallback\n            return new PrivilegeBuilder()\n                .Allow(\"read\", \"Public\")\n                .Build();\n        }\n    }\n}\n```\n\n#### Static Provider\n\nFor simpler scenarios with static permissions:\n\n```csharp\npublic class StaticPrivilegeContextProvider : IPrivilegeContextProvider\n{\n    public ValueTask\u003cPrivilegeContext\u003e GetContextAsync(ClaimsPrincipal? claimsPrincipal = null)\n    {\n        var context = new PrivilegeBuilder()\n            .Allow(\"read\", \"Post\")\n            .Allow(\"write\", \"Post\", new[] {\"title\", \"content\"})\n            .Allow(\"delete\", \"Post\")\n            .Forbid(\"publish\", \"Post\") // Override specific action\n            .Build();\n\n        return ValueTask.FromResult(context);\n    }\n}\n```\n\n#### Registration\n\nRegister your chosen provider in `Program.cs`:\n\n```csharp\n// For ASP.NET Core\nbuilder.Services.AddScoped\u003cIPrivilegeContextProvider, DatabasePrivilegeContextProvider\u003e();\n\n// For Blazor\nbuilder.Services.AddScoped\u003cIPrivilegeContextProvider, HttpPrivilegeContextProvider\u003e();\n// or\nbuilder.Services.AddScoped\u003cIPrivilegeContextProvider, StaticPrivilegeContextProvider\u003e();\n```\n\n## Blazor Integration\n\nThe `Privileged.Components` package provides components for conditional rendering based on permissions.\n\n### Blazor Setup\n\nFirst, add the privilege context as a cascading value in your app:\n\n```csharp\n// Program.cs or similar\n\n// Create privilege rules\nvar privilegeContext = new PrivilegeBuilder()\n    .Allow(\"read\", \"Post\")\n    .Allow(\"edit\", \"Post\", [\"title\", \"content\"])\n    .Allow(\"delete\", \"Post\")\n    .Build();\n\n// Make available as cascading parameter\nbuilder.Services.AddCascadingValue(_ =\u003e privilegeContext);\n```\n\n### PrivilegeContextView Component\n\nFor scenarios where you need to load the privilege context asynchronously, use the `PrivilegeContextView` component:\n\n```razor\n\u003cPrivilegeContextView\u003e\n    \u003cLoading\u003e\n        \u003cdiv class=\"spinner-border\" role=\"status\"\u003e\n            \u003cspan class=\"visually-hidden\"\u003eLoading permissions...\u003c/span\u003e\n        \u003c/div\u003e\n    \u003c/Loading\u003e\n    \u003cLoaded\u003e\n        \u003cPrivilegeView Action=\"read\" Subject=\"Post\"\u003e\n            \u003cp\u003eContent loaded with permissions!\u003c/p\u003e\n        \u003c/PrivilegeView\u003e\n    \u003c/Loaded\u003e\n\u003c/PrivilegeContextView\u003e\n```\n\nPrivilegeContextView component requires an `IPrivilegeContextProvider` service to be registered:\n\n```csharp\n// In Program.cs\nbuilder.Services.AddScoped\u003cIPrivilegeContextProvider, YourPrivilegeContextProvider\u003e();\n```\n\n#### Using PrivilegeContextView in a Layout\n\nThe most common pattern is to wrap your entire layout with `PrivilegeContextView` to ensure permissions are loaded before any page content is rendered:\n\n```razor\n@* MainLayout.razor *@\n@inherits LayoutComponentBase\n\u003cPrivilegeContextView\u003e\n    \u003cdiv class=\"page\"\u003e\n        \u003cdiv class=\"sidebar\"\u003e\n            \u003cNavMenu /\u003e\n        \u003c/div\u003e\n        \u003cmain\u003e\n            @Body\n        \u003c/main\u003e\n    \u003c/div\u003e\n\u003c/PrivilegeContextView\u003e\n```\n\nWith this approach, all pages will automatically have access to the privilege context, and users will see a loading state until permissions are loaded. Your navigation menu can also use privilege checking:\n\n### PrivilegeView Component\n\nUse the `PrivilegeView` component to conditionally render content:\n\n```razor\n@* Basic usage with ChildContent *@\n\u003cPrivilegeView Action=\"read\" Subject=\"Post\"\u003e\n    \u003cp\u003eYou can read posts!\u003c/p\u003e\n\u003c/PrivilegeView\u003e\n\n@* With both allowed and forbidden content *@\n\u003cPrivilegeView Action=\"delete\" Subject=\"Post\"\u003e\n    \u003cAllowed\u003e\n        \u003cbutton class=\"btn btn-danger\"\u003eDelete Post\u003c/button\u003e\n    \u003c/Allowed\u003e\n    \u003cForbidden\u003e\n        \u003cspan class=\"text-muted\"\u003eDelete not allowed\u003c/span\u003e\n    \u003c/Forbidden\u003e\n\u003c/PrivilegeView\u003e\n\n@* With field-level permissions *@\n\u003cPrivilegeView Action=\"edit\" Subject=\"Post\" Field=\"title\"\u003e\n    \u003cinput type=\"text\" placeholder=\"Edit title\" /\u003e\n\u003c/PrivilegeView\u003e\n```\n\n### PrivilegeLink Component\n\nThe `PrivilegeLink` component extends the standard `NavLink` component to provide privilege-aware navigation. It only renders the link when the user has the required permissions, making it perfect for building navigation menus and UI elements that should only be visible to authorized users.\n\nThe link components require a `PrivilegeContext` cascading parameter.\n\n```razor\n@* Basic navigation link that only shows if user can read posts *@\n\u003cPrivilegeLink Subject=\"Post\" Action=\"read\" href=\"/posts\"\u003e\n    View Posts\n\u003c/PrivilegeLink\u003e\n\n@* Link with custom action *@\n\u003cPrivilegeLink Subject=\"Post\" Action=\"edit\" href=\"/posts/edit\"\u003e\n    Edit Posts\n\u003c/PrivilegeLink\u003e\n\n@* Link with field-level permissions *@\n\u003cPrivilegeLink Subject=\"Post\" Action=\"update\" Qualifier=\"title\" href=\"/posts/edit-title\"\u003e\n    Edit Post Titles\n\u003c/PrivilegeLink\u003e\n\n@* Navigation menu example *@\n\u003cnav class=\"navbar\"\u003e\n    \u003cPrivilegeLink Subject=\"Post\" href=\"/posts\" class=\"nav-link\"\u003e\n        Posts\n    \u003c/PrivilegeLink\u003e\n    \u003cPrivilegeLink Subject=\"User\" Action=\"manage\" href=\"/users\" class=\"nav-link\"\u003e\n        Users\n    \u003c/PrivilegeLink\u003e\n    \u003cPrivilegeLink Subject=\"Settings\" Action=\"edit\" href=\"/settings\" class=\"nav-link\"\u003e\n        Settings\n    \u003c/PrivilegeLink\u003e\n\u003c/nav\u003e\n\n@* Using with CSS classes and additional attributes *@\n\u003cPrivilegeLink Subject=\"Post\"\n               Action=\"delete\"\n               href=\"/posts/delete\"\n               class=\"btn btn-danger\"\n               @onclick=\"ConfirmDelete\"\u003e\n    Delete Post\n\u003c/PrivilegeLink\u003e\n```\n\n### Privilege-Aware Input Components\n\nThe `Privileged.Components` package also includes privilege-aware input components that automatically handle read/write permissions:\n\nThe input components require a `PrivilegeContext` cascading parameter.\n\n```razor\n@* Text input that becomes read-only based on permissions *@\n\u003cPrivilegeInputText @bind-Value=\"@model.Title\"\n                    Subject=\"Post\"\n                    Field=\"title\"\n                    ReadAction=\"read\"\n                    UpdateAction=\"update\" /\u003e\n\n@* Number input with privilege checking *@\n\u003cPrivilegeInputNumber @bind-Value=\"@model.Views\"\n                      Subject=\"Post\"\n                      Field=\"views\" /\u003e\n\n@* Select dropdown with privilege-based enabling/disabling *@\n\u003cPrivilegeInputSelect @bind-Value=\"@model.Status\"\n                      Subject=\"Post\"\n                      Field=\"status\"\u003e\n    \u003coption value=\"draft\"\u003eDraft\u003c/option\u003e\n    \u003coption value=\"published\"\u003ePublished\u003c/option\u003e\n\u003c/PrivilegeInputSelect\u003e\n\n@* Checkbox with privilege checking *@\n\u003cPrivilegeInputCheckbox @bind-Value=\"@model.IsActive\"\n                        Subject=\"Post\"\n                        Field=\"isActive\" /\u003e\n\n@* Text area with privilege checking *@\n\u003cPrivilegeInputTextArea @bind-Value=\"@model.Content\"\n                        Subject=\"Post\"\n                        Field=\"content\"\n                        rows=\"5\" /\u003e\n```\n\nThese components automatically:\n\n- Enable/disable based on update permissions\n- Show/hide based on read permissions\n\n#### PrivilegeForm Component\n\nThe `PrivilegeForm` component extends the standard `EditForm` to provide privilege-aware form functionality. It automatically cascades privilege form state to child components, allowing you to set default privilege settings at the form level while maintaining the ability for individual components to override specific values.\n\n```razor\n@* Basic form with default privilege settings *@\n\u003cPrivilegeForm Model=\"@postModel\" Subject=\"Post\" ReadAction=\"read\" UpdateAction=\"update\"\u003e\n    \u003cDataAnnotationsValidator /\u003e\n    \u003cValidationSummary /\u003e\n    \n    @* These inputs inherit the Subject, ReadAction, and UpdateAction from the form *@\n    \u003cPrivilegeInputText @bind-Value=\"@postModel.Title\" Field=\"title\" /\u003e\n    \u003cPrivilegeInputTextArea @bind-Value=\"@postModel.Content\" Field=\"content\" /\u003e\n    \u003cPrivilegeInputSelect @bind-Value=\"@postModel.Status\" Field=\"status\"\u003e\n        \u003coption value=\"draft\"\u003eDraft\u003c/option\u003e\n        \u003coption value=\"published\"\u003ePublished\u003c/option\u003e\n    \u003c/PrivilegeInputSelect\u003e\n    \n    @* This input overrides the default Subject *@\n    \u003cPrivilegeInputText @bind-Value=\"@postModel.AuthorEmail\" \n                        Subject=\"User\" \n                        Field=\"email\" /\u003e\n    \n    \u003cbutton type=\"submit\" class=\"btn btn-primary\"\u003eSave Post\u003c/button\u003e\n\u003c/PrivilegeForm\u003e\n\n@* Form with different actions for different operations *@\n\u003cPrivilegeForm Model=\"@userModel\" Subject=\"User\" ReadAction=\"view\" UpdateAction=\"edit\"\u003e\n    \u003cPrivilegeInputText @bind-Value=\"@userModel.FirstName\" Field=\"firstName\" /\u003e\n    \u003cPrivilegeInputText @bind-Value=\"@userModel.LastName\" Field=\"lastName\" /\u003e\n    \n    @* Admin-only field with different permissions *@\n    \u003cPrivilegeInputText @bind-Value=\"@userModel.Role\" \n                        Subject=\"User\" \n                        Field=\"role\"\n                        ReadAction=\"viewRole\"\n                        UpdateAction=\"editRole\" /\u003e\n\u003c/PrivilegeForm\u003e\n```\n\nKey benefits of `PrivilegeForm`:\n\n- **Cascading Defaults**: Set privilege parameters once at the form level instead of repeating them on every input\n- **Flexible Overrides**: Individual components can override any of the cascaded values when needed\n- **Standard EditForm**: Maintains all functionality of the base `EditForm` component including validation\n- **Clean Markup**: Reduces repetitive code and makes forms easier to maintain\n\n### PrivilegeInputText HTML Output Example\n\nBelow is an example of the `PrivilegeInputText` component and its corresponding HTML output for various states based on the `PrivilegeContext` results:\n\n#### PrivilegeInputText Component\n\n```razor\n\u003cPrivilegeInputText @bind-Value=\"@model.Title\"\n                    Subject=\"Post\"\n                    Field=\"title\" /\u003e\n```\n\n#### Corresponding HTML Output\n\n```html\n\u003c!-- When the user has 'update' permission --\u003e\n\u003cinput type=\"text\" id=\"Title\" name=\"Title\" value=\"Sample Title\" /\u003e\n\n\u003c!-- When the user has only 'read' permission, make input readonly --\u003e\n\u003cinput type=\"text\" id=\"Title\" name=\"Title\" value=\"Sample Title\" readonly /\u003e\n\n\u003c!-- When the user has neither 'read' nor 'update' permission, use password type and disable --\u003e\n\u003cinput type=\"password\" id=\"Title\" name=\"Title\" disabled /\u003e\n```\n\nThis demonstrates how the `PrivilegeInputText` component dynamically adapts its output based on the user's permissions as determined by the `PrivilegeContext`.\n\n## License\n\nThis project is licensed under the MIT License.\n\n## References\n\nInspired by [CASL](https://github.com/stalniy/casl)\n","funding_links":["https://github.com/sponsors/loresoft"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Floresoft%2Fprivileged","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Floresoft%2Fprivileged","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Floresoft%2Fprivileged/lists"}