| name | permissions-and-authorization |
| description | Define and check global named permissions in an ABP app (PermissionDefinitionProvider), protect app services/controllers with [Authorize], check grants via IAuthorizationService / IPermissionChecker, and read the current user through ICurrentUser. USE FOR: defining permissions and groups, granting to roles/users/clients, [Authorize] on services or controllers, programmatic IsGrantedAsync/CheckAsync checks, reading the current user / current principal (ICurrentUser, ICurrentPrincipalAccessor) — id, tenant, roles, claims, and temporarily changing the ambient principal. DO NOT USE FOR: access that depends on a specific entity instance ("this document") — use the authorize-resources skill; configuring the login/token server (OpenIddict) — use the configure-openiddict-authentication skill; refreshing claims mid-session — use the configure-dynamic-claims skill.
|
| license | MIT |
Authorization in ABP
ABP builds on ASP.NET Core authorization and adds a permission system: named permissions are defined in code, granted to roles/users/clients, and checked with the standard [Authorize] attribute or programmatic APIs. Ground everything below in the real APIs shown here.
When to Use
- Defining named permissions and permission groups in a
PermissionDefinitionProvider.
- Protecting an application service, method, or MVC controller/action with
[Authorize("...")].
- Checking granted permissions programmatically via
IAuthorizationService or IPermissionChecker.
- Reading the authenticated user (id, tenant, roles, claims) through
ICurrentUser.
- Understanding how grants are stored/evaluated (role/user/client value providers, Permission Management module).
When Not to Use
- Access that depends on a specific entity instance ("can edit this document") — use the authorize-resources skill instead.
- Configuring the auth server / login / token issuance (OpenIddict) — use the configure-openiddict-authentication skill.
- Refreshing claims mid-session without re-login — use the configure-dynamic-claims skill.
Defining permissions
Create a class deriving from PermissionDefinitionProvider (namespace Volo.Abp.Authorization.Permissions) and override Define. It's auto-registered (ITransientDependency). Keep permission names in a static constants class so you reference them without magic strings.
public static class BookStorePermissions
{
public const string GroupName = "BookStore";
public static class Books
{
public const string Default = GroupName + ".Books";
public const string Create = Default + ".Create";
public const string Edit = Default + ".Edit";
public const string Delete = Default + ".Delete";
}
}
public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
var group = context.AddGroup(
BookStorePermissions.GroupName,
L("Permission:BookStore"));
var books = group.AddPermission(
BookStorePermissions.Books.Default,
L("Permission:Books"));
books.AddChild(BookStorePermissions.Books.Create, L("Permission:Create"));
books.AddChild(BookStorePermissions.Books.Edit, L("Permission:Edit"));
books.AddChild(BookStorePermissions.Books.Delete, L("Permission:Delete"));
}
private static LocalizableString L(string name)
=> LocalizableString.Create<BookStoreResource>(name);
}
context.AddGroup(name, displayName) returns a PermissionGroupDefinition. Group names must be unique.
group.AddPermission(name, displayName, multiTenancySide, isEnabled) returns a PermissionDefinition.
- On a
PermissionDefinition, AddChild(...) adds a nested permission. Nesting is a UI/organizational relationship, not a runtime rule — a child grant is checked on its own regardless of the parent (see the note under Common Pitfalls).
MultiTenancySides (a [Flags] enum: Tenant = 1, Host = 2, Both) restricts where a permission applies. Default is Both. Use MultiTenancySides.Host for host-only features.
permission.WithProviders(...) restricts which value providers can grant it (e.g. ClientPermissionValueProvider.ProviderName for machine-to-machine/client-only permissions). Empty = all providers allowed.
Localizing display names
Display names are ILocalizableString. Use LocalizableString.Create<TResource>("Permission:Xxx") and add the keys to your module's localization JSON (e.g. Localization/BookStore/en.json):
{
"Permission:BookStore": "Book Store",
"Permission:Books": "Book Management",
"Permission:Create": "Create"
}
If a key is missing, the raw key text is shown, so keep the JSON in sync across locales.
Checking authorization
Declarative — [Authorize]
Put the permission name in [Authorize("...")] on an application service, method, or MVC controller/action. ABP maps each permission to an ASP.NET Core authorization policy of the same name automatically.
[Authorize(BookStorePermissions.Books.Default)]
public class BookAppService : ApplicationService, IBookAppService
{
[Authorize(BookStorePermissions.Books.Create)]
public async Task<BookDto> CreateAsync(CreateBookDto input)
{
return new BookDto();
}
}
Programmatic — IAuthorizationService
Inject IAuthorizationService. ABP adds extension methods (in namespace Microsoft.AspNetCore.Authorization, from AbpAuthorizationServiceExtensions) that check the current user:
if (await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Delete))
{
}
await AuthorizationService.CheckAsync(BookStorePermissions.Books.Delete);
await AuthorizationService.IsGrantedAnyAsync(perm1, perm2);
ApplicationService and AbpController already expose an AuthorizationService property, so you usually don't inject it manually.
IPermissionChecker for a specific permission
For a direct permission check (also usable outside a web request), inject IPermissionChecker (impl PermissionChecker):
bool granted = await PermissionChecker.IsGrantedAsync(BookStorePermissions.Books.Edit);
MultiplePermissionGrantResult result = await PermissionChecker.IsGrantedAsync(new[] { permA, permB });
IsGrantedAsync returns false for an unknown, disabled, or wrong-multi-tenancy-side permission — checks never throw for those cases.
The current user — ICurrentUser
Inject ICurrentUser (namespace Volo.Abp.Users) to read the authenticated user. Available on ApplicationService/AbpController as CurrentUser.
if (CurrentUser.IsAuthenticated)
{
Guid? userId = CurrentUser.Id;
string? name = CurrentUser.UserName;
Guid? tenantId = CurrentUser.TenantId;
string[] roles = CurrentUser.Roles;
if (CurrentUser.IsInRole("admin"))
{
}
}
Other members: Email, EmailVerified, PhoneNumber, Name, SurName, and FindClaim(type) / FindClaims(type) / GetAllClaims() for raw claims. Id, UserName, TenantId, etc. are null when not authenticated — always guard with IsAuthenticated.
Current principal (advanced)
ICurrentUser reads its values from claims on the current principal. That principal is served by ICurrentPrincipalAccessor (namespace Volo.Abp.Security.Claims), the low-level service ABP itself uses whenever it needs the ambient ClaimsPrincipal. For a web request it returns HttpContext.User; outside a request (background jobs, console) it falls back to Thread.CurrentPrincipal. You rarely inject it directly — prefer ICurrentUser — but you need it to read uncommon claims off the raw principal or to switch the ambient user for a scope.
Reading arbitrary claims
ICurrentUser's claim methods (FindClaim, FindClaims, GetAllClaims) plus the extension helpers (in CurrentUserExtensions, namespace Volo.Abp.Users) cover most claim reads without touching the accessor:
string? sessionId = CurrentUser.FindClaimValue(AbpClaimTypes.SessionId);
int level = CurrentUser.FindClaimValue<int>("subscription_level");
Guid userId = CurrentUser.GetId();
Use AbpClaimTypes constants (see below) for claim names rather than magic strings.
Temporarily changing the ambient principal
ICurrentPrincipalAccessor.Change(ClaimsPrincipal) returns an IDisposable. It sets the given principal as current inside an AsyncLocal, and disposing the scope restores whatever was current before — so always use it in a using block. This is how you run a block of code as another user — a constructed principal for background work, a system/service identity, or an impersonated user — without any real login.
public class ReportRunner : ITransientDependency
{
private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor;
private readonly ICurrentUser _currentUser;
public ReportRunner(
ICurrentPrincipalAccessor currentPrincipalAccessor,
ICurrentUser currentUser)
{
_currentPrincipalAccessor = currentPrincipalAccessor;
_currentUser = currentUser;
}
public async Task RunAsAsync(Guid userId, string userName)
{
var principal = new ClaimsPrincipal(
new ClaimsIdentity(
new[]
{
new Claim(AbpClaimTypes.UserId, userId.ToString()),
new Claim(AbpClaimTypes.UserName, userName)
},
authenticationType: "Impersonation"));
using (_currentPrincipalAccessor.Change(principal))
{
var name = _currentUser.UserName;
await DoWorkAsync();
}
}
}
CurrentPrincipalAccessorExtensions adds overloads so you can pass a single Claim, an IEnumerable<Claim>, or a ClaimsIdentity instead of building the ClaimsPrincipal yourself. Caveat: the Claim / IEnumerable<Claim> overloads build a ClaimsIdentity without an authentication type, so IsAuthenticated is false — use the full ClaimsPrincipal form above (with an authenticationType) whenever authorization/middleware must see the user as authenticated.
using (_currentPrincipalAccessor.Change(new Claim(AbpClaimTypes.UserId, userId.ToString())))
{
}
The switch is scoped and AsyncLocal-based, so it flows across awaits within the using but does not leak outside it. It changes only the ambient principal for this async flow — it does not sign anyone in or issue a token.
AbpClaimTypes — claim name constants
AbpClaimTypes (static, Volo.Abp.Security.Claims) holds the claim-type strings ABP standardizes on: UserId, UserName, Name, SurName, Email, EmailVerified, PhoneNumber, PhoneNumberVerified, Role, TenantId, EditionId, ClientId, SessionId, Picture, RememberMe, and the impersonation set (ImpersonatorUserId, ImpersonatorTenantId, ImpersonatorUserName, ImpersonatorTenantName). Defaults for UserId/UserName/Role/Email map to System.Security.Claims.ClaimTypes, and Name/SurName to ClaimTypes.GivenName/Surname; the rest use short standard names (e.g. TenantId = "tenantid").
They're mutable get; set; properties: set them once at startup if your identity provider emits different claim type names, and every ABP consumer (including ICurrentUser) follows. That's the reason to reference AbpClaimTypes.UserId instead of hard-coding ClaimTypes.NameIdentifier or "sub" — a hard-coded string breaks the moment the mapping is customized, and it won't track the property change.
Permission management (granting)
Defined permissions are just definitions. Actual grants are stored and evaluated by value providers: RolePermissionValueProvider, UserPermissionValueProvider, ClientPermissionValueProvider. The Permission Management module (Volo.Abp.PermissionManagement) persists grants and powers the "Permissions" modal in the UI, where an admin grants permissions per role, user, or client. A user is granted a permission if any of their roles/direct grants/client grants allows it (and none prohibits it).
How Identity and OpenIddict fit in (brief)
- Identity module (
Volo.Abp.Identity) manages users, roles, and role/user assignments. Roles carry permission grants, so "grant a permission" in practice means granting it to a role and assigning the role to users. Its own permissions (AbpIdentity.Users, AbpIdentity.Roles, ...) are defined in IdentityPermissionDefinitionProvider — a good real-world reference.
- OpenIddict is the auth server: it authenticates users (login) and issues tokens (cookies for MVC/Blazor Server, access tokens for APIs/SPAs/mobile). It establishes who the user is; ABP's permission system decides what they may do. A remote API evaluates permissions from the current user's roles/identity against the Permission Management store (and its cache) — permission grants are not carried as token claims; ABP's dynamic-claims feature keeps the role/profile claims fresh (see configure-dynamic-claims), and permission changes take effect through the store, not by re-issuing the token.
Validation
- The
PermissionDefinitionProvider is auto-discovered (ITransientDependency), but a compile only checks types — whether the provider is actually loaded into the definition providers is a runtime concern. Confirm at runtime by resolving IPermissionDefinitionManager and querying your permission, or by opening the "Permissions" modal for a role/user and seeing your defined group and permissions appear with their localized display names.
- Call a
[Authorize("...")]-protected endpoint as an unauthorized user and confirm it is rejected; grant the permission and confirm it succeeds.
- For programmatic checks, confirm
IsGrantedAsync returns false (not an exception) for unknown/disabled permissions.
Common Pitfalls
- A missing localization key shows the raw key text instead of the display name — keep the JSON in sync across all locales.
- Parent/child is a UI and organizational convention, not a runtime rule —
PermissionChecker does not consult PermissionDefinition.Parent, and PermissionManager.SetAsync will set a child grant directly. The "Permissions" modal ties a child's checkbox to its parent for usability, but a child grant checks as granted on its own regardless of the parent's state.
MultiTenancySides defaults to Both; a permission restricted to Host will read as not-granted on the tenant side (and vice versa), and IsGrantedAsync returns false rather than throwing.
- Group names must be unique across the app; a collision breaks definition.
- Defining a permission does not grant it — grants live in the Permission Management module via the value providers.
- A remote API evaluates permissions from the current user's roles/identity against the Permission Management store — permission grants aren't token claims; dynamic claims keep the role/profile claims fresh (see configure-dynamic-claims).