From e4a2030ad92e25972a7a5e9cdb97b1ee6b801de0 Mon Sep 17 00:00:00 2001 From: nurdotnet <278048682+nurdotnet@users.noreply.github.com> Date: Tue, 21 Apr 2026 23:18:27 +0500 Subject: [PATCH] fix(auth): MoySklad admin endpoint uses policy-based auth on role claim directly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../Admin/MoySkladImportController.cs | 2 +- src/food-market.api/Program.cs | 23 ++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/food-market.api/Controllers/Admin/MoySkladImportController.cs b/src/food-market.api/Controllers/Admin/MoySkladImportController.cs index 5380ba4..9645967 100644 --- a/src/food-market.api/Controllers/Admin/MoySkladImportController.cs +++ b/src/food-market.api/Controllers/Admin/MoySkladImportController.cs @@ -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 { diff --git a/src/food-market.api/Program.cs b/src/food-market.api/Program.cs index 453854b..d353fb7 100644 --- a/src/food-market.api/Program.cs +++ b/src/food-market.api/Program.cs @@ -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;