Implementing Role-Based Authorization in ABP Framework using Angular
Introduction
Authorization is one of the most critical aspects of any business application. You need to control who can read documents, who can create them, who can publish them, and who can delete them. ABP Framework ships with a permission-based authorization system built on top of ASP.NET Core's policy-based authorization, and it integrates cleanly with Angular through dedicated guards, directives, and services.
This article walks through building a document management feature that demonstrates every layer of ABP's authorization stack. You will define granular permissions in the Application.Contracts layer, protect application service methods with the [Authorize] attribute, enforce invariants in the domain entity, and wire up the Angular front end so that navigation items, buttons, and routes are all gated by the same permission names your back end uses.
The key insight is that ABP treats permissions as automatic ASP.NET Core policies. When you define a permission named RoleBasedAuthorization.Documents.Publish, ABP registers it as a policy behind the scenes. You can then use [Authorize("RoleBasedAuthorization.Documents.Publish")] on any controller action or application service method without any extra registration code.
Source Files
| Source | Path | Description |
|---|---|---|
| Reference implementation | BookStore/angulars/angular-primeng/ | Working ABP + PrimeNG + LeptonX shell replacement |
| Target (your app) | BookStore/angular/ (or your solution’s Angular project) | ABP Angular project to integrate |
Step 1 - Domain.Shared Layer: Constants, Enums, and Error Codes
The Domain.Shared layer is the foundation. It holds types that every other layer can safely reference - constants, enums, and error codes. Nothing in this layer should depend on other project layers.
Start by defining the DocumentStatus enum, which models the three states a document can be in:
// File: src/Acme.RoleBasedAuthorization.Domain.Shared/Documents/DocumentStatus.cs
namespace Acme.RoleBasedAuthorization.Documents;
public enum DocumentStatus
{
Draft = 0,
Published = 1,
Archived = 2
}
Then add the constraint constants and a domain error code:
// File: src/Acme.RoleBasedAuthorization.Domain.Shared/Documents/DocumentConsts.cs
namespace Acme.RoleBasedAuthorization.Documents;
public static class DocumentConsts
{
public const int MaxTitleLength = 256;
public const int MaxContentLength = 65536;
public const int MaxCategoryLength = 128;
}
// File: src/Acme.RoleBasedAuthorization.Domain.Shared/RoleBasedAuthorizationDomainErrorCodes.cs
namespace Acme.RoleBasedAuthorization;
public static class RoleBasedAuthorizationDomainErrorCodes
{
public const string DocumentAlreadyPublished = "RoleBasedAuthorization:DocumentAlreadyPublished";
}
Defining max-length constants here lets both the domain entity and the DTOs reference the same values. When the [MaxLength] attribute on a DTO and the EF Core column configuration both point to DocumentConsts.MaxTitleLength, there is no risk of them drifting out of sync.
Note: Error codes are namespaced with a colon (
ModuleName:ErrorCode) so they are unique across modules and easy to localize.
Step 2 - Domain Layer: The Document Aggregate Root
The Document class represents the core business concept. It inherits from FullAuditedAggregateRoot<Guid>, which gives it creation, modification, and soft-deletion audit fields automatically.
// File: src/Acme.RoleBasedAuthorization.Domain/Documents/Document.cs
using System;
using Volo.Abp;
using Volo.Abp.Domain.Entities.Auditing;
namespace Acme.RoleBasedAuthorization.Documents;
public class Document : FullAuditedAggregateRoot<Guid>
{
public string Title { get; private set; }
public string Content { get; private set; }
public DocumentStatus Status { get; private set; }
public string Category { get; private set; }
protected Document()
{
}
public Document(Guid id, string title, string content, string category)
: base(id)
{
SetTitle(title);
SetContent(content);
Category = Check.NotNullOrWhiteSpace(category, nameof(category), DocumentConsts.MaxCategoryLength);
Status = DocumentStatus.Draft;
}
public virtual Document SetTitle(string title)
{
Title = Check.NotNullOrWhiteSpace(title, nameof(title), DocumentConsts.MaxTitleLength);
return this;
}
public virtual Document SetContent(string content)
{
Content = Check.NotNullOrWhiteSpace(content, nameof(content), DocumentConsts.MaxContentLength);
return this;
}
public virtual Document Publish()
{
if (Status == DocumentStatus.Published)
{
throw new BusinessException(RoleBasedAuthorizationDomainErrorCodes.DocumentAlreadyPublished)
.WithData("title", Title);
}
Status = DocumentStatus.Published;
return this;
}
public virtual Document Update(string title, string content, string category)
{
SetTitle(title);
SetContent(content);
Category = Check.NotNullOrWhiteSpace(category, nameof(category), DocumentConsts.MaxCategoryLength);
return this;
}
}
Several ABP conventions are at work here. The primary constructor accepts an id parameter; the calling code is responsible for generating it via IGuidGenerator, so the entity never creates its own Guid. The protected parameterless constructor satisfies EF Core's requirement to instantiate entities during query materialization. Properties have private setters, so the only way to change state is through the domain methods SetTitle, SetContent, and Publish. All public methods are virtual to support inheritance-based extensibility.
The Publish method enforces a business rule: you cannot publish a document that is already published. When this invariant is violated, it throws a BusinessException with a namespaced error code rather than a generic exception.
The Repository Interface
Define the repository interface in the domain layer, not in the infrastructure layer. This keeps the domain independent of any ORM:
// File: src/Acme.RoleBasedAuthorization.Domain/Documents/IDocumentRepository.cs
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
namespace Acme.RoleBasedAuthorization.Documents;
public interface IDocumentRepository : IBasicRepository<Document, Guid>
{
Task<List<Document>> GetListAsync(
string filter = null,
string category = null,
DocumentStatus? status = null,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
bool includeDetails = false,
CancellationToken cancellationToken = default);
Task<int> GetCountAsync(
string filter = null,
string category = null,
DocumentStatus? status = null,
CancellationToken cancellationToken = default);
}
The interface inherits from IBasicRepository<Document, Guid> rather than IRepository<Document, Guid>. The distinction matters: IBasicRepository does not expose IQueryable, so callers cannot bypass your filtering logic by running arbitrary LINQ queries. The custom GetListAsync and GetCountAsync methods accept optional CancellationToken parameters for proper async cancellation support.
Step 3 - Application.Contracts Layer: Permissions and DTOs
This is where the authorization story really starts. The Application.Contracts layer defines what permissions exist, what the service interfaces look like, and what data flows in and out.
Defining Permissions
First define the permission name constants:
// File: src/Acme.RoleBasedAuthorization.Application.Contracts/Permissions/RoleBasedAuthorizationPermissions.cs
namespace Acme.RoleBasedAuthorization.Permissions;
public static class RoleBasedAuthorizationPermissions
{
public const string GroupName = "RoleBasedAuthorization";
public static class Documents
{
public const string Default = GroupName + ".Documents";
public const string Create = Default + ".Create";
public const string Edit = Default + ".Edit";
public const string Delete = Default + ".Delete";
public const string Publish = Default + ".Publish";
}
}
Then register them with the ABP permission system by overriding the Define method of PermissionDefinitionProvider:
// File: src/Acme.RoleBasedAuthorization.Application.Contracts/Permissions/RoleBasedAuthorizationPermissionDefinitionProvider.cs
using Acme.RoleBasedAuthorization.Localization;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization;
namespace Acme.RoleBasedAuthorization.Permissions;
public class RoleBasedAuthorizationPermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
var myGroup = context.AddGroup(
RoleBasedAuthorizationPermissions.GroupName,
L("Permission:RoleBasedAuthorization"));
var documentsPermission = myGroup.AddPermission(
RoleBasedAuthorizationPermissions.Documents.Default,
L("Permission:Documents"));
documentsPermission.AddChild(
RoleBasedAuthorizationPermissions.Documents.Create,
L("Permission:Documents.Create"));
documentsPermission.AddChild(
RoleBasedAuthorizationPermissions.Documents.Edit,
L("Permission:Documents.Edit"));
documentsPermission.AddChild(
RoleBasedAuthorizationPermissions.Documents.Delete,
L("Permission:Documents.Delete"));
documentsPermission.AddChild(
RoleBasedAuthorizationPermissions.Documents.Publish,
L("Permission:Documents.Publish"));
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<RoleBasedAuthorizationResource>(name);
}
}
ABP discovers this class automatically at startup and registers each permission as an ASP.NET Core policy. The hierarchy matters: Documents.Create is a child of Documents. Granting the parent permission does not automatically grant the children - each must be granted explicitly. The hierarchy is purely visual in the Permission Management UI.
The DTOs
DTOs carry data across the application boundary. They inherit from ABP base classes to get standard fields for free:
// File: src/Acme.RoleBasedAuthorization.Application.Contracts/Documents/DocumentDto.cs
using System;
using Acme.RoleBasedAuthorization.Documents;
using Volo.Abp.Application.Dtos;
namespace Acme.RoleBasedAuthorization.Documents;
public class DocumentDto : FullAuditedEntityDto<Guid>
{
public string Title { get; set; }
public string Content { get; set; }
public DocumentStatus Status { get; set; }
public string Category { get; set; }
}
// File: src/Acme.RoleBasedAuthorization.Application.Contracts/Documents/CreateDocumentDto.cs
using System.ComponentModel.DataAnnotations;
namespace Acme.RoleBasedAuthorization.Documents;
public class CreateDocumentDto
{
[Required]
[MaxLength(DocumentConsts.MaxTitleLength)]
public string Title { get; set; }
[Required]
[MaxLength(DocumentConsts.MaxContentLength)]
public string Content { get; set; }
[Required]
[MaxLength(DocumentConsts.MaxCategoryLength)]
public string Category { get; set; }
}
// File: src/Acme.RoleBasedAuthorization.Application.Contracts/Documents/GetDocumentListDto.cs
using Volo.Abp.Application.Dtos;
namespace Acme.RoleBasedAuthorization.Documents;
public class GetDocumentListDto : PagedAndSortedResultRequestDto
{
public string Filter { get; set; }
public string Category { get; set; }
public DocumentStatus? Status { get; set; }
public GetDocumentListDto()
{
MaxResultCount = 20;
}
}
Create and Update DTOs are separate types - never reuse an input DTO for different operations. The Update DTO does not include an id field because the id comes from the URL route parameter, not from the request body.
The Service Interface
// File: src/Acme.RoleBasedAuthorization.Application.Contracts/Documents/IDocumentAppService.cs
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace Acme.RoleBasedAuthorization.Documents;
public interface IDocumentAppService : IApplicationService
{
Task<DocumentDto> GetAsync(Guid id);
Task<PagedResultDto<DocumentDto>> GetListAsync(GetDocumentListDto input);
Task<DocumentDto> CreateAsync(CreateDocumentDto input);
Task<DocumentDto> UpdateAsync(Guid id, UpdateDocumentDto input);
Task DeleteAsync(Guid id);
Task<DocumentDto> PublishAsync(Guid id);
}
All methods are async and end with Async. The PublishAsync method is a dedicated action rather than a generic update method - this reflects the intent clearly and makes it easy to attach a specific permission check to it.
Step 4 - Application Layer: Service Implementation with Permission Checks
The application service implementation is where permission checks live. The [Authorize] attribute on the class sets a baseline: any authenticated user must hold Documents.Default just to call any method. Individual methods then add stricter requirements on top.
// File: src/Acme.RoleBasedAuthorization.Application/Documents/DocumentAppService.cs
using System;
using System.Threading.Tasks;
using Acme.RoleBasedAuthorization.Permissions;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace Acme.RoleBasedAuthorization.Documents;
[Authorize(RoleBasedAuthorizationPermissions.Documents.Default)]
public class DocumentAppService : ApplicationService, IDocumentAppService
{
private readonly IDocumentRepository _documentRepository;
private readonly RoleBasedAuthorizationApplicationMappers _mappers;
public DocumentAppService(
IDocumentRepository documentRepository,
RoleBasedAuthorizationApplicationMappers mappers)
{
_documentRepository = documentRepository;
_mappers = mappers;
}
public virtual async Task<DocumentDto> GetAsync(Guid id)
{
var document = await _documentRepository.GetAsync(id);
return _mappers.ToDocumentDto(document);
}
public virtual async Task<PagedResultDto<DocumentDto>> GetListAsync(GetDocumentListDto input)
{
var totalCount = await _documentRepository.GetCountAsync(
input.Filter, input.Category, input.Status);
var documents = await _documentRepository.GetListAsync(
input.Filter, input.Category, input.Status,
input.Sorting, input.MaxResultCount, input.SkipCount);
return new PagedResultDto<DocumentDto>(
totalCount,
documents.ConvertAll(_mappers.ToDocumentDto));
}
[Authorize(RoleBasedAuthorizationPermissions.Documents.Create)]
public virtual async Task<DocumentDto> CreateAsync(CreateDocumentDto input)
{
var document = new Document(
GuidGenerator.Create(),
input.Title,
input.Content,
input.Category);
await _documentRepository.InsertAsync(document);
return _mappers.ToDocumentDto(document);
}
[Authorize(RoleBasedAuthorizationPermissions.Documents.Edit)]
public virtual async Task<DocumentDto> UpdateAsync(Guid id, UpdateDocumentDto input)
{
var document = await _documentRepository.GetAsync(id);
document.Update(input.Title, input.Content, input.Category);
await _documentRepository.UpdateAsync(document);
return _mappers.ToDocumentDto(document);
}
[Authorize(RoleBasedAuthorizationPermissions.Documents.Delete)]
public virtual async Task DeleteAsync(Guid id)
{
await _documentRepository.DeleteAsync(id);
}
[Authorize(RoleBasedAuthorizationPermissions.Documents.Publish)]
public virtual async Task<DocumentDto> PublishAsync(Guid id)
{
var document = await _documentRepository.GetAsync(id);
document.Publish();
await _documentRepository.UpdateAsync(document);
return _mappers.ToDocumentDto(document);
}
}
Notice how GuidGenerator.Create() is called in the application service, not inside the Document constructor. This is the ABP convention: the domain entity never generates its own id; the caller provides it. This makes entity creation testable without infrastructure dependencies.
The UpdateAsync method loads the entity, calls the domain method, then explicitly calls UpdateAsync on the repository. ABP does not rely on EF Core's change tracking to persist modifications - every update must be explicit.
The mapper in this project uses Mapperly, a source-generator-based mapping library that ABP 10 projects use by default. The mapping declaration is minimal:
// File: src/Acme.RoleBasedAuthorization.Application/RoleBasedAuthorizationApplicationMappers.cs
using Acme.RoleBasedAuthorization.Documents;
using Riok.Mapperly.Abstractions;
using Volo.Abp.Mapperly;
namespace Acme.RoleBasedAuthorization;
[Mapper]
public partial class RoleBasedAuthorizationApplicationMappers
{
public partial DocumentDto ToDocumentDto(Document document);
}
Mapperly generates the mapping code at compile time. This eliminates the reflection overhead of AutoMapper and makes the mapping visible in the debugger.
Step 5 - EF Core Layer: DbContext and Repository
The EF Core layer wires the domain types to the database. The key rule is: never configure entities directly inside OnModelCreating. Instead, create a ModelBuilder extension method and call it from there.
First, add the Document DbSet to the DbContext and call the configuration extension:
// File: src/Acme.RoleBasedAuthorization.EntityFrameworkCore/EntityFrameworkCore/RoleBasedAuthorizationDbContext.cs
// (partial - showing the Document-related additions)
public DbSet<Document> Documents { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// ... existing module configurations ...
builder.ConfigureRoleBasedAuthorization();
}
The extension method contains the actual entity configuration:
// File: src/Acme.RoleBasedAuthorization.EntityFrameworkCore/EntityFrameworkCore/RoleBasedAuthorizationDbContextModelCreatingExtensions.cs
using Acme.RoleBasedAuthorization.Documents;
using Microsoft.EntityFrameworkCore;
using Volo.Abp;
using Volo.Abp.EntityFrameworkCore.Modeling;
namespace Acme.RoleBasedAuthorization.EntityFrameworkCore;
public static class RoleBasedAuthorizationDbContextModelCreatingExtensions
{
public static void ConfigureRoleBasedAuthorization(this ModelBuilder builder)
{
Check.NotNull(builder, nameof(builder));
builder.Entity<Document>(b =>
{
b.ToTable(RoleBasedAuthorizationConsts.DbTablePrefix + "Documents",
RoleBasedAuthorizationConsts.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Title)
.IsRequired()
.HasMaxLength(DocumentConsts.MaxTitleLength);
b.Property(x => x.Content)
.IsRequired()
.HasMaxLength(DocumentConsts.MaxContentLength);
b.Property(x => x.Category)
.IsRequired()
.HasMaxLength(DocumentConsts.MaxCategoryLength);
});
}
}
ConfigureByConvention() applies ABP's standard mapping conventions for all the audit fields from the base class. Without this call, the audit columns would not be mapped correctly.
The repository implementation inherits from EfCoreRepository, which already provides all IBasicRepository operations. You only need to implement the custom query methods:
// File: src/Acme.RoleBasedAuthorization.EntityFrameworkCore/EntityFrameworkCore/Documents/EfCoreDocumentRepository.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Acme.RoleBasedAuthorization.Documents;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
namespace Acme.RoleBasedAuthorization.EntityFrameworkCore;
public class EfCoreDocumentRepository :
EfCoreRepository<RoleBasedAuthorizationDbContext, Document, Guid>,
IDocumentRepository
{
public EfCoreDocumentRepository(
IDbContextProvider<RoleBasedAuthorizationDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public virtual async Task<List<Document>> GetListAsync(
string filter = null,
string category = null,
DocumentStatus? status = null,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
var dbSet = await GetDbSetAsync();
return await dbSet
.WhereIf(!filter.IsNullOrWhiteSpace(), d =>
d.Title.Contains(filter) || d.Content.Contains(filter))
.WhereIf(!category.IsNullOrWhiteSpace(), d => d.Category == category)
.WhereIf(status.HasValue, d => d.Status == status!.Value)
.OrderByDescending(d => d.CreationTime)
.PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<int> GetCountAsync(
string filter = null,
string category = null,
DocumentStatus? status = null,
CancellationToken cancellationToken = default)
{
var dbSet = await GetDbSetAsync();
return await dbSet
.WhereIf(!filter.IsNullOrWhiteSpace(), d =>
d.Title.Contains(filter) || d.Content.Contains(filter))
.WhereIf(!category.IsNullOrWhiteSpace(), d => d.Category == category)
.WhereIf(status.HasValue, d => d.Status == status!.Value)
.CountAsync(GetCancellationToken(cancellationToken));
}
}
WhereIf is an ABP extension method that adds a Where clause only when the condition is true. GetCancellationToken merges the provided token with any ambient token from ABP's request context. PageBy is an ABP extension that applies Skip and Take.
After adding these files, create the migration to add the AppDocuments table:
cd src/Acme.RoleBasedAuthorization.EntityFrameworkCore
dotnet ef migrations add Added_Document_Entity
Step 6 - Angular: Guards, Directives, and the Documents Route
ABP's Angular packages mirror the back-end permission system. The same permission names you defined in RoleBasedAuthorizationPermissions are available in the Angular application through the ConfigStateService and permission utilities.
Route Protection with permissionGuard
Register the documents route in app.routes.ts and protect it with both authGuard (ensures the user is logged in) and permissionGuard (checks the specific permission):
// File: angular/src/app/document/document.routes.ts
import { Routes } from '@angular/router';
import { authGuard, permissionGuard } from '@abp/ng.core';
export const documentRoutes: Routes = [
{
path: '',
canActivate: [authGuard, permissionGuard],
data: {
requiredPolicy: 'RoleBasedAuthorization.Documents',
},
loadComponent: () =>
import('./document-list/document-list.component').then(
m => m.DocumentListComponent
),
},
];
The requiredPolicy value in route.data is the permission name string. If the current user does not have this permission, permissionGuard redirects them away before the component loads. This protects the route at the Angular router level, complementing the back-end authorization checks.
Adding the Navigation Item
Route the menu item through RoutesService so ABP can show or hide it based on permissions:
// File: angular/src/app/route.provider.ts
import { RoutesService, eLayoutType } from '@abp/ng.core';
import { APP_INITIALIZER } from '@angular/core';
export const APP_ROUTE_PROVIDER = [
{ provide: APP_INITIALIZER, useFactory: configureRoutes, deps: [RoutesService], multi: true },
];
function configureRoutes(routesService: RoutesService) {
return () => {
routesService.add([
{
path: '/',
name: '::Menu:Home',
iconClass: 'fas fa-home',
order: 1,
layout: eLayoutType.application,
},
{
path: '/documents',
name: '::Menu:Documents',
iconClass: 'fas fa-file-alt',
order: 2,
layout: eLayoutType.application,
requiredPolicy: 'RoleBasedAuthorization.Documents',
},
]);
};
}
The requiredPolicy on the route definition tells the ABP layout to hide the "Documents" navigation link for users who lack the permission. This is purely cosmetic - the back-end enforces the real authorization.
Permission-Gated UI Elements with *abpPermission
Inside the document list component, use ABP's *abpPermission structural directive to conditionally show buttons based on the current user's permissions:
<!-- Visible only to users with the Create permission -->
<button
*abpPermission="'RoleBasedAuthorization.Documents.Create'"
class="btn btn-primary btn-sm"
type="button">
New Document
</button>
<!-- Visible only to users with the Publish permission -->
<button
*abpPermission="'RoleBasedAuthorization.Documents.Publish'"
[disabled]="doc.status === DocumentStatus.Published"
(click)="publishDocument(doc.id)"
class="btn btn-outline-success"
type="button">
Publish
</button>
<!-- Visible only to users with the Delete permission -->
<button
*abpPermission="'RoleBasedAuthorization.Documents.Delete'"
(click)="deleteDocument(doc.id)"
class="btn btn-outline-danger"
type="button">
Delete
</button>
*abpPermission checks the current user's permissions from the application state loaded at startup. If the user lacks the specified permission, the element is removed from the DOM entirely - not just hidden. An editor who lacks Documents.Publish will never see the Publish button.
The component also uses PermissionDirective from @abp/ng.core in the standalone imports array, which is what makes *abpPermission available in the template.
The Document Service
The DocumentService in Angular calls the auto-generated ABP API endpoints:
// File: angular/src/app/document/document.service.ts
import { Injectable } from '@angular/core';
import { RestService, PagedResultDto } from '@abp/ng.core';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class DocumentService {
private apiName = 'Default';
constructor(private restService: RestService) {}
publish(id: string): Observable<DocumentDto> {
return this.restService.request<void, DocumentDto>(
{ method: 'POST', url: `/api/app/document/${id}/publish` },
{ apiName: this.apiName }
);
}
}
ABP auto-generates API endpoints for application services. The PublishAsync(Guid id) method on IDocumentAppService becomes a POST /api/app/document/{id}/publish endpoint. The Angular service calls these endpoints directly using RestService, which handles authentication tokens automatically.
Conclusion
This article implemented a complete role-based authorization flow across the full ABP stack. On the back end, permissions are defined as named constants in Application.Contracts, registered with PermissionDefinitionProvider, and enforced at the application service boundary using [Authorize] attributes. The domain entity guards its own state transitions - the Publish method throws a BusinessException if the document is already published, regardless of who is calling. The EF Core layer persists the entity with proper column mappings and audit fields.
On the Angular side, the same permission names connect three different enforcement mechanisms: permissionGuard blocks navigation to routes the user cannot access, the requiredPolicy on RoutesService entries hides navigation items, and the *abpPermission directive removes individual buttons from the DOM. All three use the permission state loaded by ABP at application startup, so there is no extra HTTP round-trip to check permissions.
The result is a clean permission model where every layer enforces its own rules: the Angular router, the UI layer, the HTTP API, the application service, and the domain entity itself.
References
- ABP Authorization Documentation
- ABP Permission Management Module
- ABP Angular Authorization
- ABP Angular Permission Directive
- ABP Domain Driven Design
- ABP Application Services
- ABP Framework Documentation
Source Code
The complete source code for this article is available on GitHub: abp-role-based-authorization
Share this article
