Azure / Azure/static-web-apps-cli

Local AAD auth emulator: loginParameters ignored, userId missing, userDetails empty (preferred_username not used)

Đang mở
#997 1 bình luận 0 reaction 0 người được giao Xem trên GitHub
Ngôn ngữ chính
TypeScript
Star
668
Fork
156
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Mô tả

- `swa --version` → `2.0.9` (verified latest at time of filing).
- Yes, I am accessing the CLI from port `:4280`.
- Debug logs were collected with `--verbose=silly` and confirm the behavior described below.
---
### Describe the bug
When using a real Microsoft Entra ID tenant via the `azureActiveDirectory` identity provider (i.e. not the built-in mock auth), the local emulator behaves differently from production Azure Static Web Apps in three ways. All three are in `dist/msha/auth/routes/`:
1. **`loginParameters` from `staticwebapp.config.json` are ignored** — the AAD authorize URL is built with a hardcoded `scope=openid+profile+email` and no `prompt`, `domain_hint`, `login_hint`, or extra `scope` is forwarded.
2. **`clientPrincipal.userId` is empty for AAD users** — the callback handler reads `user["id"]` / `data.id`, but AAD's OIDC `/userinfo` returns the user id as `sub` (per OIDC spec). Additionally, `userId` is not even included in the principal object literal that is returned.
3. **`clientPrincipal.userDetails` is empty for AAD users whose userinfo response lacks `email`/`login`** — there is no fallback to `preferred_username`, which is the standard AAD claim.
Each one breaks an app that works correctly when deployed.
### To Reproduce
1. Create a project with a `staticwebapp.config.json` configured for a real Entra ID tenant:
```json
{
"auth": {
"identityProviders": {
"azureActiveDirectory": {
"registration": {
"openIdIssuer": "https://login.microsoftonline.com//v2.0",
"clientIdSettingName": "AZURE_CLIENT_ID",
"clientSecretSettingName": "AZURE_CLIENT_SECRET"
},
"login": {
"loginParameters": [
"scope=openid profile email api:///Backend.Access",
"prompt=select_account"
]
}
}
}
},
"routes": [{ "route": "/*", "allowedRoles": ["authenticated"] }]
}
```
2. Set `AZURE_CLIENT_ID` / `AZURE_CLIENT_SECRET` env vars to a valid AAD app registration (Web platform, redirect URI `http://localhost:4280/.auth/login/aad/callback`).
3. Run:
```bash
swa start http://localhost:5173 --run "npm run dev" --api-location http://localhost:7217 --verbose=silly
```
4. Open `http://localhost:4280/.auth/login/aad` in a fresh browser session.
5. Inspect the redirect URL to `login.microsoftonline.com/.../authorize`.
6. Complete login, then `GET http://localhost:4280/.auth/me`.
### Expected behavior
1. The authorize URL contains every entry from `loginParameters` (matching what deployed SWA does), e.g. `prompt=select_account`, `scope=openid profile email api://.../Backend.Access`, etc.
2. `clientPrincipal.userId` is populated with the AAD `sub` claim.
3. `clientPrincipal.userDetails` is populated even when only `preferred_username` is present.
In short, `/.auth/me` should return the same `clientPrincipal` shape as deployed SWA so backends consuming `x-ms-client-principal` behave the same locally and in production.
### Actual behavior
1. The authorize URL is:
```
https://login.microsoftonline.com//oauth2/v2.0/authorize?response_type=code&client_id=&redirect_uri=http://localhost:4280/.auth/login/aad/callback&scope=openid+profile+email&state=
```
`loginParameters` are silently dropped.
2. `clientPrincipal.userId` is missing (the field is omitted because the value resolves to `undefined`).
3. `clientPrincipal.userDetails` is empty for AAD users without `email`/`login` in userinfo.
### Suggested fix (drop-in diff against `2.0.9`)
Happy to open a PR against `main` with these changes plus tests, if maintainers are interested.
```diff
diff --git a/dist/msha/auth/routes/auth-login-provider-callback.js b/dist/msha/auth/routes/auth-login-provider-callback.js
@@
- const userDetails = user["login"] || user["email"] || user?.data?.["username"];
+ const userDetails = user["login"] || user["email"] || user?.data?.["username"] || user["preferred_username"];
const name = user["name"] || user?.data?.["name"];
const givenName = user["given_name"];
const familyName = user["family_name"];
const picture = user["picture"];
- const userId = user["id"] || user?.data?.["id"];
+ // AAD's `/oidc/userinfo` returns the user id as `sub`, not `id`.
+ const userId = user["id"] || user?.data?.["id"] || user["sub"];
@@
return {
identityProvider: authProvider,
+ userId,
userDetails,
claims,
userRoles: ["authenticated", "anonymous"],
diff --git a/dist/msha/auth/routes/auth-login-provider-custom.js b/dist/msha/auth/routes/auth-login-provider-custom.js
@@
- case "aad":
+ case "aad": {
const authorizationEndpoint = await new OpenIdHelper(authFields?.openIdIssuer, authFields?.clientIdSettingName).getAuthorizationEndpoint();
- location = `${authorizationEndpoint}?response_type=code&client_id=${authFields?.clientIdSettingName}&redirect_uri=${redirectUri}/.auth/login/aad/callback&scope=openid+profile+email&state=${hashedState}`;
+ const aadLoginCfg = customAuth?.identityProviders?.[ENTRAID_FULL_NAME]?.login;
+ const aadLoginParams = Array.isArray(aadLoginCfg?.loginParameters) ? aadLoginCfg.loginParameters : [];
+ let aadScope = "openid profile email";
+ const aadExtra = [];
+ for (const p of aadLoginParams) {
+ if (typeof p !== "string") continue;
+ if (p.startsWith("scope=")) {
+ aadScope = p.substring("scope=".length);
+ } else {
+ aadExtra.push(p);
+ }
+ }
+ location = `${authorizationEndpoint}?response_type=code&client_id=${authFields?.clientIdSettingName}&redirect_uri=${redirectUri}/.auth/login/aad/callback&scope=${encodeURIComponent(aadScope)}&state=${hashedState}`;
+ if (aadExtra.length) {
+ location += "&" + aadExtra.join("&");
+ }
break;
+ }
```
### Screenshots
Not applicable — bugs are visible in the redirect URL and in the JSON returned by `/.auth/me` (text only).
### Desktop
- OS: Windows 11
- Node.js: v22.22.3
- `@azure/static-web-apps-cli`: 2.0.9
- Azure Functions Core Tools: 4.10.0
- Browser tested: Edge / Chrome (behavior identical; the bugs are server-side in the emulator)
### Additional context
- The Azure Functions backend in this project is `dotnet-isolated` on `net8.0` and consumes the `x-ms-client-principal` header. The principal-shape mismatch (bugs 2 and 3) forces the backend to add a fallback parser for `userId` purely to work around the local emulator — something that should not be necessary if the emulator matches production.
- All three fixes have been running in production-equivalent local development for several weeks via `patch-package` against `node_modules/@azure/static-web-apps-cli`. No regressions observed for other identity providers (GitHub, Twitter, Google, Facebook) — the `aad` change is scoped to `case "aad":`, and the `userId` / `userDetails` changes are pure additional fallbacks.
- Related issues (if maintainers want to triage together): `Azure/static-web-apps#1123` (federated logout / SSO behavior) — adjacent area but a different code path.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Hướng nghiên cứu

Bắt đầu bằng cách theo dõi flow AAD trong dist/msha/auth/routes/auth-login-provider-custom.js và dist/msha/auth/routes/auth-login-provider-callback.js, sử dụng các bước tái hiện và log redirect chi tiết. Hoàn tất khi loginParameters đến được authorize URL và /.auth/me trả về clientPrincipal.userId từ AAD subject cùng với userDetails từ preferred_username khi cần.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
azure, typescript
Lĩnh vực
authentication, cli
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Ít trao đổi
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
68/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.