[英]c# .NET 6.0 API using Azure AD token (called from Blazor server side) returns 401 unautorized
I have a Blazor server application that uses Azure AD login.我有一个 Blazor 服务器应用程序,它使用 Azure AD 登录。 This works fine, but when setting [Authorize] on my API controller, the controller returns 401 unauthorized event though the Bearer token is passed.
这工作正常,但是当在我的 API controller 上设置 [Authorize] 时,controller 返回 401 未授权事件,尽管 Bearer 令牌已传递。
I can see the token being passed in the API, so I am kind of stuck.我可以看到令牌在 API 中传递,所以我有点卡住了。 Problem with config or some code error?
配置问题或某些代码错误? Any help would be appreciated.
任何帮助,将不胜感激。
Azure config for Blazor project: Blazor 项目的 Azure 配置:
Azure setup for Blazor (server side project): Azure Blazor 设置(服务器端项目):
Azure setup for Blazor (server side project) Azure 设置为 Blazor(服务器端项目)
Auth setup for Blazor: Auth setup for Blazor Blazor 的授权设置: Blazor 的授权设置
Secret added for Blazor: Secret added for Blazor为 Blazor 添加的秘密:为 Blazor 添加的秘密
Api permissions for Blazor: Api permissions for Blazor Api Blazor 权限: Api Blazor 权限
Azure config for API project: API 项目的 Azure 配置:
Azure setup for API: Azure setup for API Azure 设置为 API: Azure 设置为 API
Auth setup for API: Auth setup for API API 的身份验证设置: API 的身份验证设置
API permissions for API (probably too many, found a guide that suggested adding more): API permissions for API (probably too many, found a guide that suggested adding more) API permissions for API (probably tooman, found a guide that suggested adding more): API permissions for API (probably tooman, found a guide that suggested adding more)
Exposed an API for API (type-o in admin.accsess): Exposed an API for API (type-o in admin.accsess) Exposed an API for API (type-o in admin.accsess): Exposed an API for API (type-o in admin.accsess)
Code for blazor project: blazor项目代码:
appsettings.json (new secret will be made and moved to keyvault as soon as this works): ` appsettings.json(一旦这个工作,新的秘密将被制作并移动到 keyvault):`
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "3c 8c",
"TenantId": "eb a0",
"Scopes": "api://3c 8c/admin.accsess"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
` `
Startup.cs ` Startup.cs`
public void ConfigureServices(IServiceCollection services)
{
services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { Configuration["AzureAd.Scopes"] })
.AddInMemoryTokenCaches();
services.AddHttpContextAccessor();
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy
options.FallbackPolicy = options.DefaultPolicy;
});
services.AddRazorPages();
services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
[...]
}
` `
` `
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
` `
Test controller for attempting the api call (always get 401 from api): `测试 controller 以尝试调用 api(始终从 api 获得 401):`
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Diagnostics;
using Microsoft.Identity.Web;
using Newtonsoft.Json.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace S********.Controllers
{
[Authorize]
public class TenantController : Controller
{
private readonly IHttpClientFactory _clientFactory;
private readonly ITokenAcquisition _tokenAcquisitionService;
private HttpClient _client;
public TenantController(IHttpClientFactory clientFactory, ITokenAcquisition tokenAcquisitionService)
{
_clientFactory = clientFactory;
_tokenAcquisitionService = tokenAcquisitionService;
}
public IActionResult Index()
{
return View();
}
public async Task<IActionResult> GetStuff()
{
string[] scopes = { "api://3c*******************c8c/admin.accsess" };
string accessToken = await _tokenAcquisitionService.GetAccessTokenForUserAsync(scopes);
_client = _clientFactory.CreateClient();
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await _client.GetAsync("https://localhost:44394/api/SystemConfig/GetByName/System.Tenants");
return Ok(response);
}
}
}
` `
Code for API project: API项目代码:
appsettings.json:应用设置.json:
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "custodisas.onmicrosoft.com",
"TenantId": "eb a0",
"ClientId": "7d de",
"ClientSecret": "G 5",
"CallbackPath": "/auth-response"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
Startup.cs:启动.cs:
` `
public void ConfigureServices(IServiceCollection services)
{
var NlogSetup = new SetupLogger();
NLogConfig = NlogSetup.SetUpLogger("C://Logtest//Skynet//SiloApi.txt").Result;
LogManager.Configuration = NLogConfig;
Logger = LogManager.GetLogger("SkynetAPI");
Logger.Info("Logger up and running");
services.AddAuthentication(AzureADDefaults.JwtBearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
// Have tried both with and without scope as required claim
var scopePolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
//.RequireClaim("scope", "api://3c7*****************c8c/admin.accsess")
.Build();
// Have tried both with and without adding scopepolicy as filter
services.AddMvc(options =>
{
//options.Filters.Add(new AuthorizeFilter(scopePolicy));
options.EnableEndpointRouting = false;
});
[...other services...]
services.AddControllers();
[...other services...]
}
` `
` `
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseMvc(routes =>
{
routes.MapRoute(name: "default", template: "api/{controller}/{action}/{id?}");
});
}
` `
Controller beind called (returns correct info when removing [Authorize]): ` Controller 被调用(删除 [Authorize] 时返回正确的信息):`
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web.Resource;
using Newtonsoft.Json;
using NLog;
using Orleans;
using *****.Grains.Interfaces;
using *****.Grains.Interfaces.Core.ApplicationScheduler;
using *****.Shared.Core;
using *****.Shared.Core.ApplicationScheduler;
using *****.Shared.Core.GrainStates;
using *****.Shared.Core.RunApplication;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Identity.Web;
using Microsoft.AspNetCore.Cors;
using System.IdentityModel.Tokens.Jwt;
namespace *****.API.Controllers
{
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class SystemConfigController : MasterController
{
public SystemConfigController(Logger logger, IClusterClient clusterClient) : base(logger, clusterClient)
{
}
[HttpGet]
[Route("GetByName/{configGrainName}")]
public async Task<ActionResult<string>> GetSystemConfigByName(string configGrainName)
{
// Tried adding this code to get the token, it is passed and I can see it when removing [Authorize]
var re = Request;
var headers = re.Headers;
var tokenString = headers["Authorization"];
Logger.Info("Token: \n\n" + tokenString + "\n\n");
var tmpGrain = clusterClient.GetGrain<I*****>(0);
return JsonConvert.SerializeObject(await tmpGrain.GetSystemGrainByName(configGrainName));
}
}
}
` `
MasterController: `主控制器:`
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using NLog;
using Orleans;
namespace *****.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class MasterController : ControllerBase
{
// TODO: Get from config.
public Guid SystemID { get; set; } = Guid.Parse("a9****************71c");
public Logger Logger;
public IClusterClient clusterClient;
public MasterController(Logger logger, IClusterClient client)
{
Logger = logger;
clusterClient = client;
}
}
}
` `
Hope this is enough info.希望这是足够的信息。 I am completely stuck, so any help would be appreciated.
我完全被卡住了,所以任何帮助将不胜感激。 Thank you:)
谢谢:)
I tried to reproduce in my environment.我试图在我的环境中重现。
Given API permissions, to the API for you application and granting admin consent to your application.给定 API 权限,向 API 申请并授予管理员同意您的申请。
api://43xxxxxxx6b3edf/admin.access
, api://438xxxxx-a788-xxxx/user.access
in the portal expose an API section, both of them must be given in code.api://43xxxxxxx6b3edf/admin.access
和api://438xxxxx-a788-xxxx/user.access
公开了一个 API 部分,因此它们都必须在代码中给出。 ***For client_credentials the scope to access your api must be api://<client_id>/.default ***对于 client_credentials scope 访问您的 api 必须是 api://<client_id>/.default
I could get the token successfully after giving the above scope.***给上面的scope后我就可以成功拿到token了。***
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.