Introduction
Modern microservice architectures rarely deal with simple “logged-in vs not logged-in” authorization. Real-world systems must make authorization decisions based on context — who the user is, which customer they belong to, and which company or tenant they are operating under.
In this article, we will design an enterprise-grade, attribute-based authorization system in ASP.NET Core Web API using JWT claims such as:
UserIdCustomerIdCompanyIdUserType
We will:
Validate tokens locally (no runtime auth-service calls)
Enforce authorization using policies
Expose intent using custom attributes
Consume contextual data safely inside controllers
Keep controllers clean and scalable
This approach is suitable for multi-tenant systems, B2B SaaS platforms, and microservice ecosystems.
Why Attribute-Based Authorization?
Traditional role-based authorization ([Authorize(Roles = "Admin")]) quickly breaks down when:
Users operate in multiple companies
Access depends on ownership, not just role
APIs require different context levels per endpoint
Attributes allow intent to be declared at the endpoint, while policies centralize the rules.
JWT Token Design (Context-Aware)
A context-aware JWT might look like this:
{
"UserType": "Transporter",
"UserId": "89",
"CustomerId": "501",
"CompanyId": "42",
"exp": 1770228296
}
Each claim represents authorization context, not UI state.
Authentication Configuration
JWT validation happens locally in each microservice.
builder.Services
.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey =
new SymmetricSecurityKey(
Encoding.UTF8.GetBytes("VeryStrongSecretKeyHere"))
};
});
This ensures:
No network dependency on the auth service
High performance
Predictable authorization behavior
Defining Authorization Policies
Policies describe rules, not endpoints.
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("Transporter", policy =>
policy.RequireClaim("UserType", "Transporter"));
options.AddPolicy("TransporterWithCustomer", policy =>
policy.RequireAssertion(ctx =>
ctx.User.HasClaim("UserType", "Transporter") &&
ctx.User.HasClaim(c => c.Type == "CustomerId")));
options.AddPolicy("TransporterWithCompany", policy =>
policy.RequireAssertion(ctx =>
ctx.User.HasClaim("UserType", "Transporter") &&
ctx.User.HasClaim(c => c.Type == "CompanyId")));
options.AddPolicy("TransporterWithCustomerAndCompany", policy =>
policy.RequireAssertion(ctx =>
ctx.User.HasClaim("UserType", "Transporter") &&
ctx.User.HasClaim(c => c.Type == "CustomerId") &&
ctx.User.HasClaim(c => c.Type == "CompanyId")));
});
Join the conversation! Your thoughts help the community grow.