英文:
Axios call with Azure AD authorization to my API does not work (error 401)
问题
EDIT 1: 最初我以为在服务器上部署应用程序时通话是有效的,但事实并非如此。
上下文:
- Vue.js 中的单页应用程序(SPA)(vue3 composition API)
- 使用 MSAL 库进行 Azure AD 身份验证(@azure/msal-browser@^2.32.2)
- ASP.Net Core Web API(.Net 7)
- SPA 中使用 Axios 进行 API 调用(axios@1.2.4)
- 请求拦截器使用 MSAL 获取访问令牌(JWT Bearer),该令牌作为请求的授权标头发送。
问题:
从 SPA 到 API 的调用返回 401 错误。
其他调用正在工作:
- 来自 Swagger UI 的本地调用
- 使用 --insecure 选项的本地 curl 命令
- 在禁用证书验证的情况下,来自 Insomnia 的本地调用
- 从 SPA 到 API 的服务器调用正在工作(两者都部署在我们的服务器上)
EDIT 1: 并不是。 - 从 SPA 到不需要授权的 API 操作的本地调用
环境:
- Windows 10 开发计算机
- SPA 在本地运行,使用 Vite
- 使用
vite preview
运行构建版本时出现相同的问题。
- 使用
- 我在测试中使用 Edge(Chromium)。使用 Firefox 或其他 Chromium 浏览器也出现相同的问题。
- API 在 IIS Express 上本地运行(使用 IIS Express 生成的证书)
自从解决方案在服务器上运行正常以来,我认为 Azure AD 配置是正确的。
EDIT 1: 可能在 AAD 配置中存在错误。
并且,由于我可以从其他来源进行本地调用,所以我认为 API 配置也是正确的。
我最好的猜测是 axios 调用存在问题,但仅在本地且仅涉及授权时出现问题。
Azure AD 设置
我按照以下教程设置了 Azure AD
JavaScript 单页应用的 Azure AD 教程
它使用 MSAL 与 Azure AD 进行交互。我还添加了一个 axios 请求拦截器,以自动为每个请求检索访问令牌。
ASP.NET API 的 Azure AD 教程
授权配置设置了一个带有范围要求的策略。策略称为 "AuthZPolicy",我将范围命名为 "access_as_user"。
控制器使用属性 [Authorize(Policy = "AuthZPolicy")]
进行装饰。
我理解身份验证/授权流程如下:
- 用户使用其公司凭据进行身份验证
- MSAL 获取 IdToken 并将其存储在本地存储中
- 当页面发出请求时,拦截器使用 MSAL 获取访问令牌。访问令牌包含声明
scp: access_as_user
。 - 访问令牌在请求发送到 API 的授权标头中发送。
- 在这一点上,我期望 API 验证令牌并返回响应。但是返回了 401 错误。
我尝试过的事情
禁用授权
我尝试通过在控制器上删除 Authorize 属性来禁用 API 上的授权。
我还可以通过不在授权配置中指定任何范围来禁用它
builder.Services.AddAuthorization(config =>
{
config.AddPolicy("AuthZPolicy", policyBuilder =>
policyBuilder.Requirements.Add(new ScopeAuthorizationRequirement(/* no scopes required */)));
}
在这两种情况下,调用返回了预期的数据,但这是不可接受的,因为我们需要授权。
为 SPA 使用 https
我以 http 方式启动了 SPA。在 https 中启动(使用 @vitejs/plugin-basic-ssl)没有改变行为。
在 axios 中禁用 rejectUnauthorized
参见此问题。
我希望将 rejectUnauthorized 设置为 false 会产生与 curl 中的 --insecure 选项或在 Insomnia 中禁用证书验证相同的效果。
我无法使其工作,因为在浏览器环境中无法使用 "https"。我收到以下错误消息
模块 "https" 已为浏览器兼容性而外部化。不能在客户端代码中访问 "https.Agent"。请查看 http://vitejs.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility 以获取更多详细信息。
我没有尝试设置自定义证书,如上面提到的问题中所讨论的那样,因为我不知道如何操作。我假设我需要为 IIS Express 配置证书,然后获取证书链以配置 Axios。
调用详细信息
从 SPA 调用(不工作)
在开发工具中,我看到以下内容
通用标头
请求 URL: https://localhost:44342/api/Reason/GetAll
请求方法: GET
状态代码: 401
远程地址: [::1]:44342
引用政策: strict-origin-when-cross-origin
响应标头
access-control-allow-credentials: true
access-control-allow-origin: https://localhost:44340
access-control-expose-headers: Content-Disposition
date: Fri, 02 Jun 2023 14:07:59 GMT
server: Microsoft-IIS/10.0
vary: Origin
www-authenticate: Bearer
x-powered
<details>
<summary>英文:</summary>
EDIT 1: I initially thought the calls were working when the application was deployed on the server but it is not the case.
**Context:**
- Single Page Application (SPA) in Vue.js (vue3 composition API)
- Use of MSAL library for Azure AD authentication (@azure/msal-browser@^2.32.2)
- ASP.Net Core Web API (.Net 7)
- Axios for API call from the SPA (axios@1.2.4)
- a request interceptor uses MSAL to retrieve an access token (JWT Bearer) which is sent as an authorization header with the request.
**The problem:**
Calls from the SPA to the API return a 401 error.
Other calls are working:
- local calls from Swagger UI
- local curl command with --insecure option
- local calls from [Insomnia](https://insomnia.rest) with certificate validation disabled
- <del>server calls from the SPA to the API are working (both deployed on our server)</del>
EDIT 1: It doesn't.
- local calls from the SPA to operations of the API that do not require authorization
**Environment:**
- Windows 10 development PC
- The SPA runs locally with [Vite](https://vitejs.dev)
- Running the build version with `vite preview` has the same problem.
- I use Edge (chromium) for my tests. The same problem occurs with Firefox or other chromium browsers.
- The API runs locally on IIS Express (https with certificate generated by IIS Express)
---
<del>Since the solution is working on the server, I suppose the Azure AD configuration is correct.</del>
EDIT 1: There may be a mistake in the AAD configuration.
And since I can make local calls to the local API from other sources, I suppose the API configuration is also correct.
My best guess is that there is a problem in the axios call but only locally and only when there is authorization involved.
# Azure AD setup
I followed the following tutorials to setup Azure AD
**[Azure AD tutorial for Javascript Single Page App](https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-javascript-auth-code)**
It uses MSAL to interact with Azure AD. I also added an axios request interceptor to retrieve the access token automatically for each request.
**[Azure AD tutorial for ASP.NET API](https://learn.microsoft.com/en-us/azure/active-directory/develop/web-api-tutorial-01-register-app)**
The authorization configuration sets up a policy with a scope requirement. The policy is called "AuthZPolicy" and I called my scope "access_as_user".
The controllers are decorated with the attribute `[Authorize(Policy = "AuthZPolicy")]`
I understand the authentication/authorization flow is the following:
1. The user authenticates with their company credentials
1. MSAL gets an IdToken and stores it in the local storage
1. When a page makes a request, the interceptor uses MSAL to fetch an access token. The access token contains the claim `"scp": "access_as_user"`.
1. The access token is sent in the authorization header of the request to the API.
1. At this point, I expect the API to validate the token and return a response. However a *401* error is returned.
# What I tried
## Disable authorization
I tried disabling the authorization in the API by removing the *Authorize* attribute on the controller.
I can also disable it by not specifying any scopes in the authorization configuration
```cs
builder.Services.AddAuthorization(config =>
{
config.AddPolicy("AuthZPolicy", policyBuilder =>
policyBuilder.Requirements.Add(new ScopeAuthorizationRequirement(/* no scopes required */)));
}
In both cases, the calls return the expected data, but this is not acceptable as we need authorization.
Use https for the SPA
I was launching the SPA in http. Lauching it in https (using @vitejs/plugin-basic-ssl) did not change the behaviour.
Disable rejectUnauthorized in axios
See this question.
I was hoping setting rejectUnauthorized to false would have the same effect as the --insecure option in curl or disabling certificate validation in Insomnia.
I can't make it work because "https" cannot be used in a browser context. I get the following error
> Module "https" has been externalized for browser compatibility. Cannot access "https.Agent" in client code. See http://vitejs.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.
I did not try setting up a custom certificate as discussed in the question mentioned above as I don't know how to do it. I assume I would have to configure a certificate for IIS Express and then get the certificate chain to configure Axios.
Call details
Call from the SPA (not working)
In the developer tools I see the following
General Headers
Request URL: https://localhost:44342/api/Reason/GetAll
Request Method: GET
Status Code: 401
Remote Address: [::1]:44342
Referrer Policy: strict-origin-when-cross-origin
Response Headers
access-control-allow-credentials: true
access-control-allow-origin: https://localhost:44340
access-control-expose-headers: Content-Disposition
date: Fri, 02 Jun 2023 14:07:59 GMT
server: Microsoft-IIS/10.0
vary: Origin
www-authenticate: Bearer
x-powered-by: ASP.NET
Request Headers
:authority: localhost:44342
:method: GET
:path: /api/Reason/GetAll
:scheme: https
accept: application/json, text/plain, */*
accept-encoding: gzip, deflate, br
accept-language: fr,fr-FR;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
authorisation: Bearer t0K3n
origin: https://localhost:44340
referer: https://localhost:44340/
sec-ch-ua: "Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
sec-fetch-dest: empty
sec-fetch-mode: cors
sec-fetch-site: same-site
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.57
API logs
In the console of the API, I see the following messages
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/2 OPTIONS https://localhost:44342/api/Reason/GetAll - -
info: Microsoft.AspNetCore.Cors.Infrastructure.CorsService[4]
CORS policy execution successful.
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished HTTP/2 OPTIONS https://localhost:44342/api/Reason/GetAll - - - 204 - - 0.4755ms
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/2 GET https://localhost:44342/api/Reason/GetAll - -
info: Microsoft.AspNetCore.Cors.Infrastructure.CorsService[4]
CORS policy execution successful.
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed. These requirements were not met:
ScopeAuthorizationRequirement:Scope=
info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[12]
AuthenticationScheme: Bearer was challenged.
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished HTTP/2 GET https://localhost:44342/api/Reason/GetAll - - - 401 - - 0.5798ms
Even though the scope is present in the access token, we see the follwing message :
> Authorization failed. These requirements were not met:
> ScopeAuthorizationRequirement:Scope=
Call from Swagger UI (working)
Swagger UI opens when I launch the API.
If I authenticate in Swagger UI with the token, I get a code 200 response with the expected results. The token is copy-pasted from the authorization header in the request from the dev tools.
Call with curl (working with --insecure option)
Swagger UI generates a curl command. It looks like this:
curl -X 'GET' \
'https://localhost:44342/api/Reason/GetAll' \
-H 'accept: text/plain' \
-H 'Authorization: Bearer t0K3n'
When execute it in a git bash terminal, I get the following error message:
> curl: (60) SSL certificate problem: self signed certificate
> More details here: https://curl.se/docs/sslcerts.html
>
> curl failed to verify the legitimacy of the server and therefore could not
> establish a secure connection to it. To learn more about this situation and
> how to fix it, please visit the web page mentioned above.
I understand this is because the API is running on IIS Express with a self signed certificate.
When I use the --insecure
option I get the expected results.
Call with Insomnia (working without certificate validation)
Insomina is a tool to test rest API's: https://insomnia.rest
Similarly to curl, I get an error when Insomnia tries to validate the certificate of the server but I get the expected response when I disable certificate validation.
答案1
得分: 0
Authorization header 名称中有一个拼写错误,我将其拼写为 "Authorisation"(s 而非 z)。
英文:
There was a typo in the name of the Authorization header, I had spelled it "Authorisation" (with s instead of z)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论