fix(auth): MoySklad admin endpoint uses policy-based auth on role claim directly

ASP.NET Core's [Authorize(Roles=...)] relies on ClaimsIdentity.RoleClaimType to
match, which may not be wired to "role" in the OpenIddict validation handler's
identity (depending on middleware order with AddIdentity). Tokens clearly carry
"role": "Admin" but IsInRole("Admin") returns false.

- Register AddAuthorization policy "AdminAccess" that checks the `role` claim
  explicitly (c.Type == Claims.Role && Value in {Admin, SuperAdmin}). Works
  regardless of how ClaimsIdentity was constructed.
- MoySkladImportController now uses [Authorize(Policy = "AdminAccess")].
- Add /api/_debug/whoami that echoes authType, roleClaimType, claims, and
  IsInRole result — makes next auth issue trivial to diagnose.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nurdotnet 2026-04-21 23:18:27 +05:00
parent b07232521b
commit e4a2030ad9
2 changed files with 23 additions and 2 deletions

View file

@ -5,7 +5,7 @@
namespace foodmarket.Api.Controllers.Admin;
[ApiController]
[Authorize(Roles = "Admin,SuperAdmin")]
[Authorize(Policy = "AdminAccess")]
[Route("api/admin/moysklad")]
public class MoySkladImportController : ControllerBase
{

View file

@ -111,7 +111,13 @@
options.DefaultAuthenticateScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
});
builder.Services.AddAuthorization();
builder.Services.AddAuthorization(opts =>
{
// Check the "role" claim explicitly — robust against role-claim-type mismatches between
// OpenIddict validation identity and the default ClaimTypes.Role uri.
opts.AddPolicy("AdminAccess", p => p.RequireAssertion(ctx =>
ctx.User.HasClaim(c => c.Type == Claims.Role && (c.Value == "Admin" || c.Value == "SuperAdmin"))));
});
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
@ -143,6 +149,21 @@
app.MapGet("/health", () => Results.Ok(new { status = "ok", time = DateTime.UtcNow }));
app.MapGet("/api/_debug/whoami", (HttpContext ctx) =>
{
var identity = ctx.User.Identity as System.Security.Claims.ClaimsIdentity;
return Results.Ok(new
{
isAuthenticated = ctx.User.Identity?.IsAuthenticated,
authType = ctx.User.Identity?.AuthenticationType,
nameClaimType = identity?.NameClaimType,
roleClaimType = identity?.RoleClaimType,
isInRoleAdmin = ctx.User.IsInRole("Admin"),
hasAdminRoleClaim = ctx.User.HasClaim(c => c.Type == Claims.Role && c.Value == "Admin"),
claims = ctx.User.Claims.Select(c => new { c.Type, c.Value }),
});
}).RequireAuthorization();
app.MapGet("/api/me", (HttpContext ctx) =>
{
var user = ctx.User;