🎯

openiddict-authorization

🎯Skill

from thapaliyabikendra/ai-artifacts

VibeIndex|
What it does

Implements permission-based OAuth 2.0 authorization for ABP Framework using OpenIddict, enabling fine-grained access control and multi-tenant security.

openiddict-authorization

Installation

Install skill:
npx skills add https://github.com/thapaliyabikendra/ai-artifacts --skill openiddict-authorization
1
AddedJan 27, 2026

Skill Details

SKILL.md

"Master OAuth 2.0 authorization patterns with OpenIddict and ABP Framework including permission-based authorization, role-based access control, custom claims, and multi-tenant security. Use when implementing authentication/authorization for ABP applications."

Overview

# OpenIddict Authorization Patterns

OAuth 2.0 and authorization patterns for ABP Framework with OpenIddict.

When to Use

  • Implementing permission-based authorization
  • Configuring role-based access control (RBAC)
  • Adding custom claims to tokens
  • Securing API endpoints
  • Implementing multi-tenant authorization
  • Configuring OAuth 2.0 flows

ABP Permission System

Define Permissions

```csharp

// Domain.Shared/Permissions/{ProjectName}Permissions.cs

public static class {ProjectName}Permissions

{

public const string GroupName = "{ProjectName}";

public static class {Feature1}

{

public const string Default = GroupName + ".{Feature1}";

public const string Create = Default + ".Create";

public const string Edit = Default + ".Edit";

public const string Delete = Default + ".Delete";

public const string Export = Default + ".Export";

}

public static class {Feature2}

{

public const string Default = GroupName + ".{Feature2}";

public const string Create = Default + ".Create";

public const string Edit = Default + ".Edit";

public const string Delete = Default + ".Delete";

public const string ViewAll = Default + ".ViewAll"; // Admin only

public const string Cancel = Default + ".Cancel";

}

public static class {Feature3}

{

public const string Default = GroupName + ".{Feature3}";

public const string Create = Default + ".Create";

public const string Edit = Default + ".Edit";

public const string Delete = Default + ".Delete";

public const string Manage = Default + ".Manage";

}

}

```

Register Permission Definitions

```csharp

// Application.Contracts/Permissions/{ProjectName}PermissionDefinitionProvider.cs

public class {ProjectName}PermissionDefinitionProvider : PermissionDefinitionProvider

{

public override void Define(IPermissionDefinitionContext context)

{

var mainGroup = context.AddGroup(

{ProjectName}Permissions.GroupName,

L("Permission:{ProjectName}"));

// Feature1

var feature1Permission = mainGroup.AddPermission(

{ProjectName}Permissions.{Feature1}.Default,

L("Permission:{Feature1}"));

feature1Permission.AddChild(

{ProjectName}Permissions.{Feature1}.Create,

L("Permission:{Feature1}.Create"));

feature1Permission.AddChild(

{ProjectName}Permissions.{Feature1}.Edit,

L("Permission:{Feature1}.Edit"));

feature1Permission.AddChild(

{ProjectName}Permissions.{Feature1}.Delete,

L("Permission:{Feature1}.Delete"));

feature1Permission.AddChild(

{ProjectName}Permissions.{Feature1}.Export,

L("Permission:{Feature1}.Export"));

// Feature2

var feature2Permission = mainGroup.AddPermission(

{ProjectName}Permissions.{Feature2}.Default,

L("Permission:{Feature2}"));

feature2Permission.AddChild(

{ProjectName}Permissions.{Feature2}.Create,

L("Permission:{Feature2}.Create"));

feature2Permission.AddChild(

{ProjectName}Permissions.{Feature2}.ViewAll,

L("Permission:{Feature2}.ViewAll"));

// Feature3 - restricted to admin

var feature3Permission = mainGroup.AddPermission(

{ProjectName}Permissions.{Feature3}.Default,

L("Permission:{Feature3}"),

multiTenancySide: MultiTenancySides.Host); // Host-only permission

}

private static LocalizableString L(string name)

{

return LocalizableString.Create<{ProjectName}Resource>(name);

}

}

```

Using Authorization in AppServices

Declarative Authorization

```csharp

[Authorize({ProjectName}Permissions.{Feature}.Default)]

public class {Entity}AppService : ApplicationService, I{Entity}AppService

{

[Authorize({ProjectName}Permissions.{Feature}.Create)]

public async Task<{Entity}Dto> CreateAsync(CreateUpdate{Entity}Dto input)

{

// Only users with {Feature}.Create permission can execute

}

[Authorize({ProjectName}Permissions.{Feature}.Edit)]

public async Task<{Entity}Dto> UpdateAsync(Guid id, CreateUpdate{Entity}Dto input)

{

// Only users with {Feature}.Edit permission can execute

}

[Authorize({ProjectName}Permissions.{Feature}.Delete)]

public async Task DeleteAsync(Guid id)

{

// Only users with {Feature}.Delete permission can execute

}

[AllowAnonymous]

public async Task<{Entity}Dto> GetPublicInfoAsync(Guid id)

{

// Anyone can access

}

}

```

Imperative Authorization

```csharp

public class {Entity}AppService : ApplicationService, I{Entity}AppService

{

public async Task<{Entity}Dto> GetAsync(Guid id)

{

var entity = await _repository.GetAsync(id);

// Check if user can view this entity

if (entity.OwnerId != CurrentUser.Id &&

entity.AssignedUserId != CurrentUser.Id)

{

// User is neither the owner nor assigned - check admin permission

await AuthorizationService.CheckAsync(

{ProjectName}Permissions.{Feature}.ViewAll);

}

return ObjectMapper.Map<{Entity}, {Entity}Dto>(entity);

}

public async Task CanCancelAsync(Guid id)

{

var entity = await _repository.GetAsync(id);

// Check various conditions

if (entity.Status == {Entity}Status.Completed)

return false;

// Owner can always cancel

if (entity.OwnerId == CurrentUser.Id)

return true;

// Check cancel permission

return await AuthorizationService.IsGrantedAsync(

{ProjectName}Permissions.{Feature}.Cancel);

}

}

```

Policy-Based Authorization

```csharp

// Define policy

public override void ConfigureServices(ServiceConfigurationContext context)

{

context.Services.AddAuthorization(options =>

{

options.AddPolicy("AdminOnly", policy =>

policy.RequireRole("admin"));

options.AddPolicy("DoctorOrAdmin", policy =>

policy.RequireRole("admin", "doctor"));

options.AddPolicy("CanManagePatients", policy =>

policy.RequireAssertion(context =>

context.User.HasClaim("Permission", ClinicPermissions.Patients.Default) ||

context.User.IsInRole("admin")));

});

}

// Use in AppService

[Authorize(Policy = "DoctorOrAdmin")]

public async Task GetScheduleAsync(Guid doctorId)

{

// ...

}

```

Role-Based Access Control

Seed Default Roles

```csharp

public class ClinicDataSeedContributor : IDataSeedContributor, ITransientDependency

{

private readonly IIdentityRoleRepository _roleRepository;

private readonly IdentityRoleManager _roleManager;

private readonly IPermissionManager _permissionManager;

public async Task SeedAsync(DataSeedContext context)

{

await CreateRoleIfNotExistsAsync("Admin");

await CreateRoleIfNotExistsAsync("Doctor");

await CreateRoleIfNotExistsAsync("Receptionist");

await AssignPermissionsToRolesAsync();

}

private async Task CreateRoleIfNotExistsAsync(string roleName)

{

var role = await _roleRepository.FindByNormalizedNameAsync(roleName.ToUpperInvariant());

if (role == null)

{

role = new IdentityRole(GuidGenerator.Create(), roleName);

await _roleManager.CreateAsync(role);

}

}

private async Task AssignPermissionsToRolesAsync()

{

// Admin gets all permissions

var allPermissions = new[]

{

ClinicPermissions.Patients.Default,

ClinicPermissions.Patients.Create,

ClinicPermissions.Patients.Edit,

ClinicPermissions.Patients.Delete,

ClinicPermissions.Appointments.Default,

ClinicPermissions.Appointments.ViewAll,

ClinicPermissions.Doctors.Default,

ClinicPermissions.Doctors.ManageSchedule

};

foreach (var permission in allPermissions)

{

await _permissionManager.SetForRoleAsync("Admin", permission, true);

}

// Doctor permissions

await _permissionManager.SetForRoleAsync("Doctor", ClinicPermissions.Appointments.Default, true);

await _permissionManager.SetForRoleAsync("Doctor", ClinicPermissions.Patients.Default, true);

// Receptionist permissions

await _permissionManager.SetForRoleAsync("Receptionist", ClinicPermissions.Patients.Create, true);

await _permissionManager.SetForRoleAsync("Receptionist", ClinicPermissions.Appointments.Create, true);

}

}

```

Custom Claims

Add Custom Claims to Token

```csharp

// AuthServer/ClaimsPrincipalContributor.cs

public class ClinicClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency

{

private readonly IRepository _doctorRepository;

public ClinicClaimsPrincipalContributor(IRepository doctorRepository)

{

_doctorRepository = doctorRepository;

}

public async Task ContributeAsync(AbpClaimsPrincipalContributorContext context)

{

var identity = context.ClaimsPrincipal.Identities.FirstOrDefault();

if (identity == null) return;

var userId = identity.FindUserId();

if (!userId.HasValue) return;

// Add custom claims

var doctor = await _doctorRepository.FirstOrDefaultAsync(d => d.UserId == userId);

if (doctor != null)

{

identity.AddClaim(new Claim("doctor_id", doctor.Id.ToString()));

identity.AddClaim(new Claim("specialization", doctor.Specialization));

}

}

}

// Register in module

public override void ConfigureServices(ServiceConfigurationContext context)

{

Configure(options =>

{

options.RequestedClaims.Add("doctor_id");

options.RequestedClaims.Add("specialization");

});

}

```

Access Custom Claims

```csharp

public class AppointmentAppService : ApplicationService

{

public async Task> GetMyAppointmentsAsync()

{

// Get doctor_id from claims

var doctorIdClaim = CurrentUser.FindClaim("doctor_id");

if (doctorIdClaim == null)

{

throw new BusinessException("User is not a doctor");

}

var doctorId = Guid.Parse(doctorIdClaim.Value);

return await _appointmentRepository

.GetListAsync(a => a.DoctorId == doctorId);

}

}

```

Multi-Tenant Authorization

Tenant-Aware Permissions

```csharp

public class TenantPermissionDefinitionProvider : PermissionDefinitionProvider

{

public override void Define(IPermissionDefinitionContext context)

{

var group = context.AddGroup("TenantSettings");

// Tenant-side permission (available in tenants)

group.AddPermission(

"TenantSettings.Manage",

multiTenancySide: MultiTenancySides.Tenant);

// Host-side permission (only host/admin)

group.AddPermission(

"TenantSettings.ManageAll",

multiTenancySide: MultiTenancySides.Host);

}

}

```

Cross-Tenant Data Access

```csharp

public class CrossTenantReportService : ApplicationService

{

private readonly IDataFilter _dataFilter;

private readonly ICurrentTenant _currentTenant;

[Authorize("Reports.CrossTenant")]

public async Task GenerateCrossTenantReportAsync()

{

// Verify host-level access

if (_currentTenant.Id.HasValue)

{

throw new BusinessException("Cross-tenant reports only available from host");

}

using (_dataFilter.Disable())

{

// Access data across all tenants

var allPatients = await _patientRepository.GetCountAsync();

// ...

}

}

}

```

OpenIddict Configuration

AuthServer Setup

```csharp

// AuthServer/AuthServerModule.cs

public override void PreConfigureServices(ServiceConfigurationContext context)

{

PreConfigure(builder =>

{

builder.AddValidation(options =>

{

options.AddAudiences("ClinicManagementSystem");

options.UseLocalServer();

options.UseAspNetCore();

});

});

}

public override void ConfigureServices(ServiceConfigurationContext context)

{

Configure(options =>

{

options.AddDevelopmentEncryptionAndSigningCertificate = true;

});

// Configure OpenIddict scopes

Configure(options =>

{

options.Scopes.Add("offline_access");

options.Scopes.Add("profile");

options.Scopes.Add("email");

options.Scopes.Add("clinic_api");

});

}

```

API Host JWT Configuration

```csharp

// HttpApi.Host configuration

public override void ConfigureServices(ServiceConfigurationContext context)

{

var configuration = context.Services.GetConfiguration();

context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)

.AddJwtBearer(options =>

{

options.Authority = configuration["AuthServer:Authority"];

options.RequireHttpsMetadata = Convert.ToBoolean(

configuration["AuthServer:RequireHttpsMetadata"]);

options.Audience = "ClinicManagementSystem";

options.TokenValidationParameters = new TokenValidationParameters

{

ValidateIssuer = true,

ValidateAudience = true,

ValidateLifetime = true,

ClockSkew = TimeSpan.Zero

};

});

}

```

Security Best Practices

Input Validation in Authorization

```csharp

public async Task GetAsync(Guid id)

{

// Validate input

if (id == Guid.Empty)

{

throw new AbpValidationException("Invalid patient ID");

}

var patient = await _patientRepository.FindAsync(id);

if (patient == null)

{

throw new EntityNotFoundException(typeof(Patient), id);

}

// Authorization check

await AuthorizationService.CheckAsync(ClinicPermissions.Patients.Default);

return ObjectMapper.Map(patient);

}

```

Audit Logging

```csharp

[Audited] // Enable audit logging for this service

public class PatientAppService : ApplicationService

{

[DisableAuditing] // Disable for specific methods

public async Task> GetListAsync()

{

// ...

}

}

```

Quality Checklist

  • [ ] Permissions defined for all features
  • [ ] Permission hierarchy established (parent/child)
  • [ ] Roles seeded with appropriate permissions
  • [ ] All mutation endpoints have [Authorize]
  • [ ] Custom claims added for domain-specific data
  • [ ] Multi-tenancy permissions configured correctly
  • [ ] Audit logging enabled for sensitive operations
  • [ ] Token validation configured properly

Shared Knowledge

For foundational patterns, see the shared knowledge base:

| Topic | File | Description |

|-------|------|-------------|

| Permission naming | [knowledge/conventions/permissions.md](../../knowledge/conventions/permissions.md) | Permission format and hierarchy |

| Naming conventions | [knowledge/conventions/naming.md](../../knowledge/conventions/naming.md) | Permission constant naming |

Integration Points

This skill is used by:

  • abp-developer: Authorization implementation
  • security-engineer: Security audit and review
  • /review:permissions: Permission coverage analysis
  • backend-architect: Security architecture design

More from this repository10

🎯
clean-code-dotnet🎯Skill

Provides clean code guidelines and refactoring techniques for C#/.NET, focusing on improving code readability, maintainability, and adherence to SOLID principles.

🎯
abp-api-implementation🎯Skill

Implements comprehensive REST APIs in ABP Framework with robust AppServices, DTOs, pagination, filtering, and authorization for .NET applications.

🎯
abp-entity-patterns🎯Skill

Implements domain layer patterns for ABP Framework, providing robust entity, aggregate, repository, and domain service implementations following DDD principles.

🎯
fluentvalidation-patterns🎯Skill

Validates input DTOs in ABP Framework using FluentValidation with async checks, conditional rules, custom validators, and localized error messages.

🎯
abp-infrastructure-patterns🎯Skill

abp-infrastructure-patterns skill from thapaliyabikendra/ai-artifacts

🎯
efcore-patterns🎯Skill

Configures and optimizes Entity Framework Core patterns for ABP Framework, focusing on entity configuration, migrations, and relationship design with PostgreSQL.

🎯
content-retrieval🎯Skill

content-retrieval skill from thapaliyabikendra/ai-artifacts

🎯
system-design-patterns🎯Skill

Designs scalable, reliable distributed systems by applying proven architectural patterns and evaluating trade-offs across performance, consistency, and availability.

🎯
abp-contract-scaffolding🎯Skill

Generates ABP Application.Contracts layer scaffolding, enabling parallel development by creating standardized interfaces, DTOs, and permissions for .NET microservices.

🎯
api-response-patterns🎯Skill

api-response-patterns skill from thapaliyabikendra/ai-artifacts