Introduction
In microservices applications, different types of users often access the same backend service:
Back-Office users – admins, support staff
Transporter Panel users – drivers, fleet managers
For security, certain endpoints should be restricted only to Transporter users, and internal logic may require UserId and CompanyId from the JWT token.
This article demonstrates:
Method-level endpoint security for Transporter users
JWT authentication with claims
Attribute-based policies in ASP.NET Core
Accessing UserId and CompanyId in controllers
JWT Token Structure
For Transporter users, the JWT might contain claims like:
{
"UserType": "Transporter",
"CompanyId": "1002",
"UserId": "89",
"sub": "1001",
"exp": 1710000000
}
UserType differentiates roles (Transporter vs BackOffice)
CompanyId identifies the organization
UserId uniquely identifies the user
Step 1: Create TransporterWithCompany Attribute
We create a custom attribute to restrict access to Transporter users with a company:
using Microsoft.AspNetCore.Authorization;
namespace MyApp.API.Attributes
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class TransporterWithCompanyAttribute : AuthorizeAttribute
{
public TransporterWithCompanyAttribute()
{
Policy = "TransporterWithCompany";
}
}
}
This attribute can be applied per method, leaving other endpoints unrestricted.
Step 2: Configure JWT Authentication and Authorization
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using MyApp.API.Attributes;
var builder = WebApplication.CreateBuilder(args);
// Add controllers
builder.Services.AddControllers();
// Swagger
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// JWT Authentication
var secret = builder.Configuration["Jwt:Secret"];
if (string.IsNullOrWhiteSpace(secret))
throw new Exception("JWT Secret is missing in configuration.");
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)),
ClockSkew = TimeSpan.Zero
};
});
// Authorization policy for Transporter users with CompanyId
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("TransporterWithCompany", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireAssertion(context =>
{
var userType = context.User.FindFirst("UserType")?.Value;
var companyId = context.User.FindFirst("CompanyId")?.Value;
return userType == "Transporter" && !string.IsNullOrWhiteSpace(companyId);
});
});
});
// Middleware registration
builder.Services.AddScoped<TokenContextMiddleware>();
var app = builder.Build();
// Swagger
app.UseSwagger();
app.UseSwaggerUI();
// HTTPS
app.UseHttpsRedirection();
// Authentication & Authorization
app.UseAuthentication();
app.UseAuthorization();
// Middleware to inject UserId and CompanyId into HttpContext.Items
app.UseMiddleware<TokenContextMiddleware>();
app.MapControllers();
app.Run();
appsettings.json example:
{
"Jwt": {
"Issuer": "https://example-auth-server.com/",
"Audience": "https://example-api.com/",
"Secret": "YourSuperSecretKey123!@#"
}
}
Step 3: Claims Helper
using System.Security.Claims;
public static class ClaimsHelper
{
public static string GetUserId(this ClaimsPrincipal user) =>
user.FindFirst("UserId")?.Value ?? string.Empty;
public static string GetCompanyId(this ClaimsPrincipal user) =>
user.FindFirst("CompanyId")?.Value ?? string.Empty;
}
Step 4: Middleware to Inject User Context
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
public class TokenContextMiddleware
{
private readonly RequestDelegate _next;
public TokenContextMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext context)
{
if (context.User.Identity?.IsAuthenticated == true)
{
context.Items["UserId"] = context.User.GetUserId();
context.Items["CompanyId"] = context.User.GetCompanyId();
}
await _next(context);
}
}
Step 5: Sample Controller Using Attribute
using Microsoft.AspNetCore.Mvc;
using MyApp.API.Attributes;
[ApiController]
[Route("api/issue-tracking")]
public class IssueTrackingController : ControllerBase
{
private readonly IIssueTrackingService _issueTrackingService;
public IssueTrackingController(IIssueTrackingService issueTrackingService)
{
_issueTrackingService = issueTrackingService;
}
// Accessible only by Transporter users with CompanyId
[HttpGet("{id}")]
[TransporterWithCompany]
public async Task<IActionResult> GetIssueTrackingById(long id)
{
var userId = User.GetUserId();
var companyId = User.GetCompanyId();
if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(companyId))
return Unauthorized(new { Message = "Invalid token claims" });
var issue = await _issueTrackingService.GetByIdAndCompanyAsync(id, companyId);
if (issue == null) return NotFound();
return Ok(new
{
Issue = issue,
UserId = userId,
CompanyId = companyId
});
}
// Public endpoint
[HttpGet("all")]
public async Task<IActionResult> GetAllIssueTracking()
{
var issues = await _issueTrackingService.GetAllAsync();
return Ok(issues);
}
}
Only Transporters with a valid CompanyId can access /api/issue-tracking/{id}. Other users receive 401 Unauthorized.
Step 6: Testing
Generate a JWT token with:
{
"UserType": "Transporter",
"CompanyId": "1002",
"UserId": "89"
}
Call the endpoint using Postman:
GET https://localhost:5001/api/issue-tracking/1
Authorization: Bearer <JWT_TOKEN>
Conclusion
This article shows how to:
Secure method-level endpoints for Transporter users
Extract UserId and CompanyId from JWT claims
Use middleware to make claims easily accessible
Separate Back-Office and Transporter access cleanly
This approach is scalable, clean, and ready for production microservices.