Azure AD授权的Axios调用到我的API不起作用(错误401)。

huangapple go评论78阅读模式
英文:

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")] 进行装饰。

我理解身份验证/授权流程如下:

  1. 用户使用其公司凭据进行身份验证
  2. MSAL 获取 IdToken 并将其存储在本地存储中
  3. 当页面发出请求时,拦截器使用 MSAL 获取访问令牌。访问令牌包含声明 scp: access_as_user
  4. 访问令牌在请求发送到 API 的授权标头中发送。
  5. 在这一点上,我期望 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
- &lt;del&gt;server calls from the SPA to the API are working (both deployed on our server)&lt;/del&gt;  
  EDIT 1: It doesn&#39;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)

---
&lt;del&gt;Since the solution is working on the server, I suppose the Azure AD configuration is correct.&lt;/del&gt;  
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 &quot;AuthZPolicy&quot; and I called my scope &quot;access_as_user&quot;.

The controllers are decorated with the attribute `[Authorize(Policy = &quot;AuthZPolicy&quot;)]`

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 `&quot;scp&quot;: &quot;access_as_user&quot;`.
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 =&gt;
{
    config.AddPolicy(&quot;AuthZPolicy&quot;, policyBuilder =&gt;
        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: &quot;Microsoft Edge&quot;;v=&quot;113&quot;, &quot;Chromium&quot;;v=&quot;113&quot;, &quot;Not-A.Brand&quot;;v=&quot;24&quot;
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: &quot;Windows&quot;
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 &#39;GET&#39; \
  &#39;https://localhost:44342/api/Reason/GetAll&#39; \
  -H &#39;accept: text/plain&#39; \
  -H &#39;Authorization: Bearer t0K3n&#39;

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)

huangapple
  • 本文由 发表于 2023年6月6日 16:35:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/76412820.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定