Appearance
Authorization
GrydAuth provides a flexible authorization system combining Role-Based Access Control (RBAC) and Permission-Based Access Control (PBAC) with support for custom policies.
Authorization Models
Role-Based Access Control (RBAC)
Roles group users with similar access requirements:
csharp
[Authorize(Roles = "Admin")]
public IActionResult AdminOnly() { ... }
[Authorize(Roles = "Admin,Manager")]
public IActionResult AdminOrManager() { ... }1
2
3
4
5
2
3
4
5
Permission-Based Access Control (PBAC)
Fine-grained permissions for specific actions:
csharp
[RequirePermission("create:products")]
public IActionResult CreateProduct() { ... }
[RequirePermission("read:products", "update:products")]
public IActionResult ViewOrUpdateProducts() { ... } // OR (default)
[RequirePermission(PermissionMatchMode.All, "read:products", "update:products")]
public IActionResult ManageProducts() { ... } // AND1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Permission Format
GrydAuth uses the convention {action}:{entity}:
| Permission | Description |
|---|---|
create:products | Can create products |
read:products | Can view products |
update:products | Can update products |
delete:products | Can delete products |
manage:users | Can manage users |
admin:system | System administration |
Role Management
Built-in Roles
GrydAuth comes with predefined roles:
| Role | Description |
|---|---|
SuperAdmin | Full system access |
Admin | Administrative access |
User | Standard user access |
Guest | Limited read-only access |
Creating Roles
http
POST /api/v1/roles
Authorization: Bearer {admin-token}
Content-Type: application/json
{
"name": "Manager",
"description": "Department manager with elevated permissions",
"permissions": [
"read:products",
"create:products",
"update:products",
"read:users",
"create:reports"
]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Assigning Roles to Users
http
POST /api/v1/users/{userId}/roles
Authorization: Bearer {admin-token}
Content-Type: application/json
{
"roleNames": ["Manager", "User"]
}1
2
3
4
5
6
7
2
3
4
5
6
7
Programmatic Role Management
csharp
public class RoleService
{
private readonly IRoleRepository _roleRepository;
private readonly IUserRepository _userRepository;
public async Task AssignRoleAsync(Guid userId, string roleName)
{
var user = await _userRepository.GetByIdAsync(userId);
var role = await _roleRepository.GetByNameAsync(roleName);
user.AssignRole(role);
await _userRepository.UpdateAsync(user);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Permission Management
Creating Permissions
http
POST /api/v1/permissions
Authorization: Bearer {admin-token}
Content-Type: application/json
{
"code": "export:reports",
"description": "Can export reports to PDF/Excel",
"category": "Reports"
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Auto-Generated Permissions
Use [AutoPermission] attribute to automatically generate and enforce permissions for your controllers:
csharp
using Gryd.API.Attributes;
[ApiController]
[Route("api/[controller]")]
[AutoPermission("products")] // Generates AND enforces: create:products, read:products, etc.
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult GetAll() { ... } // read:products
[HttpPost]
public IActionResult Create() { ... } // create:products
[HttpPut("{id}")]
public IActionResult Update() { ... } // update:products
[HttpDelete("{id}")]
public IActionResult Delete() { ... } // delete:products
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Automatic Enforcement with GrydCrud
When using GrydCrud.Auth with autoPermissions: true, controllers that inherit from CrudController or CqrsCrudController get permissions enforced automatically — no method overrides needed:
csharp
// Program.cs
builder.Services.AddGrydCrudAuth(autoPermissions: true);1
2
2
csharp
// Controller — zero boilerplate
[AutoPermission("departments")]
[Route("api/[controller]")]
public class DepartmentsController : CrudController<Department, CreateDeptDto, UpdateDeptDto, DeptDto, DeptQueryParams>
{
public DepartmentsController(ICrudService<...> service) : base(service) { }
// ✅ All inherited CRUD endpoints are protected automatically:
// GetAll → Permission:read:departments
// GetById → Permission:read:departments
// Create → Permission:create:departments
// Update → Permission:update:departments
// Delete → Permission:delete:departments
}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
The CrudPermissionConvention respects existing [Authorize] / [RequirePermission] attributes and will not override them. See the GrydCrud Permissions documentation for full details.
Permission Generation Service
Generate all permissions for seeding:
csharp
using GrydCrud.Auth.Abstractions;
public class PermissionSeeder
{
private readonly ICrudPermissionGenerator _permissionGenerator;
private readonly IPermissionRepository _permissionRepository;
public async Task SeedPermissionsAsync()
{
// Scan all controllers for [AutoPermission] attributes
// Includes inherited CRUD methods from base controllers
var permissions = _permissionGenerator.GeneratePermissions(
typeof(ProductsController).Assembly,
typeof(UsersController).Assembly
);
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Authorization Attributes
[Authorize]
Standard ASP.NET Core authorization:
csharp
[Authorize] // Any authenticated user
public class SecureController : ControllerBase { }
[Authorize(Roles = "Admin")]
public class AdminController : ControllerBase { }1
2
3
4
5
2
3
4
5
[RequirePermission]
GrydAuth permission-based authorization:
csharp
using GrydAuth.Application.Authorization;
// Single permission
[RequirePermission("read:products")]
public IActionResult GetProducts() { ... }
// Multiple permissions (OR - default)
[RequirePermission("read:products", "export:products")]
public IActionResult ReadOrExportProducts() { ... }
// Multiple permissions (AND)
[RequirePermission(PermissionMatchMode.All, "read:products", "export:products")]
public IActionResult ExportProducts() { ... }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
Behavior summary:
- 1 permission: same behavior as previous versions.
- 2+ permissions without
PermissionMatchMode:OR(Any). - 2+ permissions with
PermissionMatchMode.All:AND(All). - Internal policy format is versioned (
Permission:v2:*) and backward-compatible with legacyPermission:<code>.
Stale claims and token revocation
Permission claims are authorization snapshots. GrydAuth bounds their lifetime with access tokens that expire after 10 minutes by default (configurable from 1 to 15 minutes), while refresh tokens preserve session continuity.
Every change to an existing user's effective permissions increments User.TokenVersion. The token processor compares the JWT token_version claim with the current version on every request and rejects obsolete tokens globally. This includes direct permissions, user roles, permissions attached to roles, tenant access changes and user deactivation. User deletion performs complete session invalidation. Role permission changes fan out the version increment to every user assigned to that role.
Authorization remains declarative through [RequirePermission]; endpoints do not perform live permission queries. This keeps database/cache I/O out of the authorization hot path and avoids endpoint-specific revocation behavior.
[AllowAnonymous]
Skip authorization:
csharp
[AllowAnonymous]
public IActionResult PublicEndpoint() { ... }1
2
2
Policy-Based Authorization
Defining Policies
csharp
builder.Services.AddAuthorization(options =>
{
// Simple permission policy
options.AddPolicy("CanReadProducts", policy =>
policy.RequirePermission("read:products"));
// Multiple permissions (ALL required)
options.AddPolicy("CanManageProducts", policy =>
policy.RequirePermission("create:products", "update:products", "delete:products"));
// Role-based policy
options.AddPolicy("AdminOnly", policy =>
policy.RequireRole("Admin", "SuperAdmin"));
// Combined policy
options.AddPolicy("ProductManager", policy =>
policy.RequireRole("Manager")
.RequirePermission("read:products", "update:products"));
// Custom requirement
options.AddPolicy("MinimumAge", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(18)));
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Using Policies
csharp
[Authorize(Policy = "CanManageProducts")]
public IActionResult ManageProducts() { ... }1
2
2
Custom Authorization Handlers
csharp
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public int MinimumAge { get; }
public MinimumAgeRequirement(int minimumAge) => MinimumAge = minimumAge;
}
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
var dateOfBirthClaim = context.User.FindFirst(c => c.Type == "date_of_birth");
if (dateOfBirthClaim != null)
{
var dateOfBirth = DateTime.Parse(dateOfBirthClaim.Value);
var age = DateTime.Today.Year - dateOfBirth.Year;
if (age >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}
// Register handler
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();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
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
Checking Permissions in Code
Using IAuthorizationService
csharp
public class ProductService
{
private readonly IAuthorizationService _authorizationService;
private readonly IHttpContextAccessor _httpContextAccessor;
public async Task<Result> DeleteProductAsync(Guid productId)
{
var user = _httpContextAccessor.HttpContext!.User;
var authResult = await _authorizationService.AuthorizeAsync(
user,
null,
new PermissionRequirement("delete:products"));
if (!authResult.Succeeded)
{
return Result.Failure("ACCESS_DENIED", "You don't have permission to delete products");
}
// Proceed with deletion...
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Using ICurrentUserService
csharp
public class ProductService
{
private readonly ICurrentUserService _currentUser;
public bool CanUserEditProduct(Product product)
{
// Check if user has update permission
if (_currentUser.HasPermission("update:products"))
return true;
// Or is the owner
if (product.CreatedBy == _currentUser.UserId)
return true;
return false;
}
}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
ICurrentUserService Methods
| Method | Description |
|---|---|
HasPermission(string) | Check single permission |
HasAnyPermission(params string[]) | Check if user has any of the permissions |
HasAllPermissions(params string[]) | Check if user has all permissions |
IsInRole(string) | Check if user is in role |
IsInAnyRole(params string[]) | Check if user is in any of the roles |
Resource-Based Authorization
For complex scenarios where authorization depends on the resource:
csharp
public class DocumentAuthorizationHandler :
AuthorizationHandler<OperationAuthorizationRequirement, Document>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Document resource)
{
var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (requirement.Name == Operations.Read.Name)
{
// Anyone in the same department can read
if (resource.DepartmentId == GetUserDepartment(context.User))
{
context.Succeed(requirement);
}
}
else if (requirement.Name == Operations.Update.Name)
{
// Only owner or admin can update
if (resource.OwnerId.ToString() == userId ||
context.User.IsInRole("Admin"))
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}
// Usage
var document = await _documentRepository.GetByIdAsync(id);
var authResult = await _authorizationService.AuthorizeAsync(
User, document, Operations.Update);
if (!authResult.Succeeded)
return Forbid();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
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
Hierarchical Permissions
Implement permission hierarchies:
csharp
public class HierarchicalPermissionHandler : AuthorizationHandler<PermissionRequirement>
{
private readonly Dictionary<string, string[]> _hierarchies = new()
{
["admin:*"] = new[] { "create:*", "read:*", "update:*", "delete:*" },
["manage:products"] = new[] { "create:products", "read:products", "update:products" },
};
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
PermissionRequirement requirement)
{
var userPermissions = context.User.Claims
.Where(c => c.Type == "permissions")
.Select(c => c.Value)
.ToHashSet();
// Check direct permission
if (userPermissions.Contains(requirement.Permission))
{
context.Succeed(requirement);
return Task.CompletedTask;
}
// Check hierarchical permissions
foreach (var (parent, children) in _hierarchies)
{
if (userPermissions.Contains(parent) &&
children.Contains(requirement.Permission))
{
context.Succeed(requirement);
break;
}
}
return Task.CompletedTask;
}
}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
Best Practices
- Principle of Least Privilege - Grant minimum permissions needed
- Use Permissions over Roles - Roles group permissions, but check permissions
- Avoid Hardcoded Roles - Use configuration or database
- Audit Permission Changes - Log all authorization changes
- Regular Permission Review - Periodically review user permissions
- Use Resource-Based Auth - For complex scenarios, check the resource itself