Skip to content

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() { ... }

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 ManageProducts() { ... } // Requires ALL permissions

Permission Format

GrydAuth uses the convention {action}:{entity}:

PermissionDescription
create:productsCan create products
read:productsCan view products
update:productsCan update products
delete:productsCan delete products
manage:usersCan manage users
admin:systemSystem administration

Role Management

Built-in Roles

GrydAuth comes with predefined roles:

RoleDescription
SuperAdminFull system access
AdminAdministrative access
UserStandard user access
GuestLimited read-only access

Creating Roles

http
POST /api/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"
  ]
}

Assigning Roles to Users

http
POST /api/users/{userId}/roles
Authorization: Bearer {admin-token}
Content-Type: application/json

{
  "roleNames": ["Manager", "User"]
}

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);
    }
}

Permission Management

Creating Permissions

http
POST /api/permissions
Authorization: Bearer {admin-token}
Content-Type: application/json

{
  "name": "export:reports",
  "description": "Can export reports to PDF/Excel",
  "category": "Reports"
}

Auto-Generated Permissions

Use [AutoPermission] attribute to automatically generate permissions for your controllers:

csharp
using Gryd.API.Attributes;

[ApiController]
[Route("api/[controller]")]
[AutoPermission("products")] // Generates: 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
}

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
        var permissions = _permissionGenerator.GeneratePermissions(
            typeof(ProductsController).Assembly,
            typeof(UsersController).Assembly
        );
        
        foreach (var permission in permissions)
        {
            await _permissionRepository.CreateIfNotExistsAsync(permission);
        }
    }
}

Authorization Attributes

[Authorize]

Standard ASP.NET Core authorization:

csharp
[Authorize] // Any authenticated user
public class SecureController : ControllerBase { }

[Authorize(Roles = "Admin")]
public class AdminController : ControllerBase { }

[RequirePermission]

GrydAuth permission-based authorization:

csharp
// Single permission
[RequirePermission("read:products")]
public IActionResult GetProducts() { ... }

// Multiple permissions (ALL required)
[RequirePermission("read:products", "export:products")]
public IActionResult ExportProducts() { ... }

[AllowAnonymous]

Skip authorization:

csharp
[AllowAnonymous]
public IActionResult PublicEndpoint() { ... }

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)));
});

Using Policies

csharp
[Authorize(Policy = "CanManageProducts")]
public IActionResult ManageProducts() { ... }

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>();

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...
    }
}

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;
    }
}

ICurrentUserService Methods

MethodDescription
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();

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;
    }
}

Best Practices

  1. Principle of Least Privilege - Grant minimum permissions needed
  2. Use Permissions over Roles - Roles group permissions, but check permissions
  3. Avoid Hardcoded Roles - Use configuration or database
  4. Audit Permission Changes - Log all authorization changes
  5. Regular Permission Review - Periodically review user permissions
  6. Use Resource-Based Auth - For complex scenarios, check the resource itself

Released under the MIT License.