Appearance
Permissions & Authorization
GrydCrud integrates with GrydAuth to provide automatic permission-based authorization for CRUD controllers. With GrydCrud.Auth, your controllers can enforce permissions without boilerplate overrides.
📦 Installation
bash
dotnet add package GrydCrud.Auth1
How It Works
GrydCrud.Auth operates in two layers:
| Layer | Purpose | Mechanism |
|---|---|---|
| Seed-time | Generate permission strings for database seeding | ICrudPermissionGenerator scans [AutoPermission] controllers |
| Runtime | Enforce permissions on CRUD endpoints | CrudPermissionConvention applies AuthorizeFilter at startup |
The [AutoPermission] attribute works as a single source of truth — it feeds both layers:
[AutoPermission("products")]
│
├── Seed-time → ICrudPermissionGenerator → "create:products", "read:products", ...
│
└── Runtime → CrudPermissionConvention → AuthorizeFilter("Permission:read:products")
└→ PermissionPolicyProvider resolves policy
└→ PermissionAuthorizationHandler checks claims1
2
3
4
5
6
7
2
3
4
5
6
7
Setup
Option 1: Via AddGrydCrudAuth (Recommended)
csharp
// Program.cs
builder.Services.AddGrydCrudAuth(autoPermissions: true);1
2
2
This registers both the ICrudPermissionGenerator and the CrudPermissionConvention.
Option 2: Via MvcOptions
If you prefer explicit control over MVC configuration:
csharp
// Program.cs
builder.Services.AddGrydCrudAuth(); // Just the permission generator
builder.Services.AddControllers(options =>
{
options.AddCrudPermissionConvention(); // Add the convention manually
});1
2
3
4
5
6
7
2
3
4
5
6
7
Option 3: Seed-Only (No runtime enforcement)
If you only want permission generation for seeding, without automatic runtime enforcement:
csharp
// Program.cs
builder.Services.AddGrydCrudAuth(); // No autoPermissions flag1
2
2
Automatic Permission Enforcement
With CrudPermissionConvention enabled, controllers decorated with [AutoPermission] automatically get authorization on all CRUD actions — including inherited ones from CrudController and CqrsCrudController.
Before (Manual Overrides)
Without the convention, you had to override every CRUD method just to apply [RequirePermission]:
csharp
[AutoPermission("departments")]
[Route("api/[controller]")]
public class DepartmentsController : CrudController<Department, CreateDepartmentDto, UpdateDepartmentDto, DepartmentDto, DeptQueryParams>
{
public DepartmentsController(ICrudService<...> service) : base(service) { }
// ❌ 25+ lines of boilerplate just for permissions
[RequirePermission("read:departments")]
public override Task<IActionResult> GetAll([FromQuery] DeptQueryParams parameters, CancellationToken ct)
=> base.GetAll(parameters, ct);
[RequirePermission("read:departments")]
public override Task<IActionResult> GetById(Guid id, CancellationToken ct)
=> base.GetById(id, ct);
[RequirePermission("create:departments")]
public override Task<IActionResult> Create([FromBody] CreateDepartmentDto dto, CancellationToken ct)
=> base.Create(dto, ct);
[RequirePermission("update:departments")]
public override Task<IActionResult> Update(Guid id, [FromBody] UpdateDepartmentDto dto, CancellationToken ct)
=> base.Update(id, dto, ct);
[RequirePermission("delete:departments")]
public override Task<IActionResult> Delete(Guid id, CancellationToken ct)
=> base.Delete(id, ct);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
After (Automatic) ✅
With the convention enabled, the same controller becomes:
csharp
[AutoPermission("departments")]
[Route("api/[controller]")]
public class DepartmentsController : CrudController<Department, CreateDepartmentDto, UpdateDepartmentDto, DepartmentDto, DeptQueryParams>
{
public DepartmentsController(ICrudService<...> service) : base(service) { }
// ✅ That's it! Permissions are applied automatically:
// GET /api/v1/departments → Permission:read:departments
// GET /api/v1/departments/{id} → Permission:read:departments
// POST /api/v1/departments → Permission:create:departments
// PUT /api/v1/departments/{id} → Permission:update:departments
// DELETE /api/v1/departments/{id} → Permission:delete:departments
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
This works with both CrudController and CqrsCrudController.
Permission Mapping
The convention maps actions to permissions using this priority:
1. Known CRUD Method Names
Methods inherited from CrudController / CqrsCrudController:
| Method | Permission Action |
|---|---|
GetAll | read |
GetById | read |
Create | create |
Update | update |
Delete | delete |
2. HTTP Method Attributes
For custom actions, the HTTP verb determines the action:
| Attribute | Permission Action |
|---|---|
[HttpGet] | read |
[HttpPost] | create |
[HttpPut] / [HttpPatch] | update |
[HttpDelete] | delete |
3. Method Name Patterns
As a fallback, the method name is analyzed:
| Method Name Starts With | Permission Action |
|---|---|
Get, List, Find, Search | read |
Create, Add, Insert | create |
Update, Edit, Modify, Patch | update |
Delete, Remove, Destroy | delete |
Customizing Permissions
Custom Action Names
Use method-level [AutoPermission] with ActionName to define custom actions:
csharp
[AutoPermission("reports")]
[Route("api/[controller]")]
public class ReportsController : CrudController<Report, ReportDto>
{
public ReportsController(ICrudService<Report, ReportDto> service) : base(service) { }
// Standard CRUD: read:reports, create:reports, etc. (automatic)
// Custom action: export:reports
[HttpGet("export")]
[AutoPermission("reports", ActionName = "export")]
public async Task<IActionResult> Export() { ... }
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
Custom Entity Names Per Method
Override the entity name for specific methods:
csharp
[AutoPermission("orders")]
[Route("api/[controller]")]
public class OrdersController : CrudController<Order, OrderDto>
{
public OrdersController(ICrudService<Order, OrderDto> service) : base(service) { }
// Standard CRUD: read:orders, create:orders, etc.
// This method uses a different entity: read:order-items
[HttpGet("{id}/items")]
[AutoPermission("order-items")]
public async Task<IActionResult> GetOrderItems(Guid id) { ... }
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
Excluding Specific Endpoints
Mark endpoints as public by excluding them from permission enforcement:
csharp
[AutoPermission("products")]
[Route("api/[controller]")]
public class ProductsController : CrudController<Product, ProductDto>
{
public ProductsController(ICrudService<Product, ProductDto> service) : base(service) { }
// Standard CRUD endpoints get permissions automatically
// This endpoint is public — no permission required
[HttpGet("featured")]
[AutoPermission("products", IncludeInPermissions = false)]
[AllowAnonymous]
public async Task<IActionResult> GetFeatured() { ... }
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
Explicit Override Takes Precedence
If a method already has [Authorize] or [RequirePermission], the convention does not override it:
csharp
[AutoPermission("documents")]
[Route("api/[controller]")]
public class DocumentsController : CrudController<Document, DocumentDto>
{
public DocumentsController(ICrudService<Document, DocumentDto> service) : base(service) { }
// This method has an explicit policy — convention skips it
[Authorize(Policy = "DocumentOwnerOrAdmin")]
public override Task<IActionResult> Delete(Guid id, CancellationToken ct)
=> base.Delete(id, ct);
// Other CRUD methods still get automatic permissions:
// GetAll → Permission:read:documents
// GetById → Permission:read:documents
// Create → Permission:create:documents
// Update → Permission:update:documents
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Permission Generation for Seeding
The ICrudPermissionGenerator scans assemblies for controllers with [AutoPermission] and generates permission strings for database seeding:
csharp
using GrydCrud.Auth.Abstractions;
public class PermissionSeeder
{
private readonly ICrudPermissionGenerator _generator;
private readonly IPermissionRepository _permissionRepository;
public async Task SeedPermissionsAsync()
{
// Scan all controllers for [AutoPermission] attributes
var permissions = _generator.GeneratePermissions(
typeof(ProductsController).Assembly,
typeof(OrdersController).Assembly
);
// Result: ["create:products", "read:products", "update:products", "delete:products",
// "create:orders", "read:orders", ...]
foreach (var permission in permissions)
{
await _permissionRepository.CreateIfNotExistsAsync(permission);
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
You can also generate standard CRUD permissions without scanning assemblies:
csharp
var permissions = _generator.GenerateStandardCrudPermissions("invoices");
// Result: ["create:invoices", "read:invoices", "update:invoices", "delete:invoices"]1
2
2
CrudActions Helper
Use CrudActions constants for type-safe permission strings:
csharp
using GrydCrud.Auth;
// Constants
CrudActions.Create // "create"
CrudActions.Read // "read"
CrudActions.Update // "update"
CrudActions.Delete // "delete"
// Helper methods
CrudActions.CreatePermission("products") // "create:products"
CrudActions.ReadPermission("products") // "read:products"
CrudActions.UpdatePermission("products") // "update:products"
CrudActions.DeletePermission("products") // "delete:products"
CrudActions.Permission("export", "reports") // "export:reports"1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
Complete Example
Here's a full example combining all features:
csharp
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add GrydAuth (authentication + authorization)
builder.Services.AddGrydAuth(builder.Configuration);
// Add GrydCrud with auth integration (enables convention)
builder.Services.AddGrydCrud(options => { options.UseSoftDelete = true; });
builder.Services.AddGrydCrudAuth(autoPermissions: true);
builder.Services.AddControllers();
var app = builder.Build();
app.UseAuthentication();
app.UseGrydAuth();
app.UseAuthorization();
app.MapControllers();
app.Run();1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
csharp
// Controllers/ProductsController.cs
using Gryd.API.Attributes;
using GrydCrud.API.Controllers;
using GrydCrud.Services.Abstractions;
[AutoPermission("products")]
[Route("api/[controller]")]
public class ProductsController : CrudController<Product, CreateProductDto, UpdateProductDto, ProductDto, ProductQueryParams>
{
public ProductsController(
ICrudService<Product, CreateProductDto, UpdateProductDto, ProductDto, ProductQueryParams> service)
: base(service)
{
}
// All 5 CRUD endpoints are protected automatically:
// GET /api/v1/products → read:products
// GET /api/v1/products/{id} → read:products
// POST /api/v1/products → create:products
// PUT /api/v1/products/{id} → update:products
// DELETE /api/v1/products/{id} → delete:products
// Custom endpoint with custom permission
[HttpGet("export")]
[AutoPermission("products", ActionName = "export")]
public async Task<IActionResult> ExportProducts(CancellationToken ct)
{
// export:products permission required
return Ok();
}
// Public endpoint — no auth required
[HttpGet("featured")]
[AutoPermission("products", IncludeInPermissions = false)]
[AllowAnonymous]
public async Task<IActionResult> GetFeatured(CancellationToken ct)
{
return Ok();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Architecture
The permission system consists of the following components:
┌──────────────────────────────────────────────────────────────────────┐
│ Application Startup │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ CrudPermissionConvention (IControllerModelConvention) │ │
│ │ → Scans controllers with [AutoPermission] │ │
│ │ → Adds AuthorizeFilter("Permission:{action}:{entity}") │ │
│ │ → Maps HTTP verbs / method names → CRUD actions │ │
│ │ → Skips methods with existing [Authorize] / [RequirePermission] │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
│
▼ (at request time)
┌──────────────────────────────────────────────────────────────────────┐
│ ASP.NET Core Authorization Pipeline │
│ │
│ AuthorizeFilter(policy: "Permission:read:products") │
│ │ │
│ ▼ │
│ PermissionPolicyProvider │
│ → Resolves "Permission:read:products" to AuthorizationPolicy │
│ → Adds PermissionRequirement("read:products") │
│ │ │
│ ▼ │
│ PermissionAuthorizationHandler │
│ → Checks User.HasClaim("permission", "read:products") │
│ → Admin role bypasses all checks │
└──────────────────────────────────────────────────────────────────────┘
│
▼ (at seed time)
┌──────────────────────────────────────────────────────────────────────┐
│ Permission Seeding │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ ICrudPermissionGenerator │ │
│ │ → Scans assemblies for [AutoPermission] controllers │ │
│ │ → Generates permission strings (incl. inherited CRUD methods)│ │
│ │ → Returns: ["create:products", "read:products", ...] │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
API Reference
Attributes
| Attribute | Target | Description |
|---|---|---|
[AutoPermission("entity")] | Class / Method | Marks controller for automatic permission generation and enforcement |
[RequirePermission("action:entity")] | Method | Explicit permission requirement (overrides convention) |
AutoPermission Properties
| Property | Type | Default | Description |
|---|---|---|---|
EntityName | string | (required) | Entity name for the permission (normalized to lowercase) |
ActionName | string? | null | Custom action name override (e.g., "export") |
IncludeInPermissions | bool | true | Whether to include in permission generation/enforcement |
Extension Methods
| Method | Description |
|---|---|
services.AddGrydCrudAuth() | Registers ICrudPermissionGenerator only |
services.AddGrydCrudAuth(autoPermissions: true) | Registers generator + CrudPermissionConvention |
options.AddCrudPermissionConvention() | Adds convention to MvcOptions directly |
ICrudPermissionGenerator
| Method | Description |
|---|---|
GeneratePermissions(params Assembly[]) | Scans assemblies for [AutoPermission] controllers and returns permission strings |
GenerateStandardCrudPermissions(string entity) | Returns 4 standard CRUD permissions for an entity |