From 209f12edb2dc0c9a237167b4b4dba301a04c8b57 Mon Sep 17 00:00:00 2001 From: Long Hao Date: Thu, 4 Sep 2025 08:28:37 +0000 Subject: [PATCH] fix: find auth_endpoint from OpenIDIssuer for aad --- package-lock.json | 51 +++++++++++++++++++ package.json | 3 +- src/core/utils/openidHelper.ts | 35 +++++++++++++ .../auth/routes/auth-login-provider-custom.ts | 4 +- 4 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 src/core/utils/openidHelper.ts diff --git a/package-lock.json b/package-lock.json index 79484fa0a..cc8f70c04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "keytar": "^7.9.0", "node-fetch": "^2.7.0", "open": "^8.4.2", + "openid-client": "^6.7.1", "ora": "^5.4.1", "pem": "^1.14.8", "prompts": "^2.4.2", @@ -7219,6 +7220,15 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/jose": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.0.tgz", + "integrity": "sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -11125,6 +11135,15 @@ "optional": true, "peer": true }, + "node_modules/oauth4webapi": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.1.tgz", + "integrity": "sha512-olkZDELNycOWQf9LrsELFq8n05LwJgV8UkrS0cburk6FOwf8GvLam+YB+Uj5Qvryee+vwWOfQVeI5Vm0MVg7SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/object-inspect": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", @@ -11195,6 +11214,19 @@ "opencollective-postinstall": "index.js" } }, + "node_modules/openid-client": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.7.1.tgz", + "integrity": "sha512-kOiE4q0kNogr90hXsxPrKeEDuY+V0kkZazvZScOwZkYept9slsaQ3usXTaKkm6I04vLNuw5caBoX7UfrwC6x8w==", + "license": "MIT", + "dependencies": { + "jose": "^6.1.0", + "oauth4webapi": "^3.8.0" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -19958,6 +19990,11 @@ "@sideway/pinpoint": "^2.0.0" } }, + "jose": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.0.tgz", + "integrity": "sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -22693,6 +22730,11 @@ "optional": true, "peer": true }, + "oauth4webapi": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.1.tgz", + "integrity": "sha512-olkZDELNycOWQf9LrsELFq8n05LwJgV8UkrS0cburk6FOwf8GvLam+YB+Uj5Qvryee+vwWOfQVeI5Vm0MVg7SA==" + }, "object-inspect": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", @@ -22739,6 +22781,15 @@ "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", "dev": true }, + "openid-client": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.7.1.tgz", + "integrity": "sha512-kOiE4q0kNogr90hXsxPrKeEDuY+V0kkZazvZScOwZkYept9slsaQ3usXTaKkm6I04vLNuw5caBoX7UfrwC6x8w==", + "requires": { + "jose": "^6.1.0", + "oauth4webapi": "^3.8.0" + } + }, "ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", diff --git a/package.json b/package.json index 3cd12070f..a180d6ed8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "@azure/static-web-apps-cli", "version": "2.0.6", + "name": "@azure/static-web-apps-cli", "description": "Azure Static Web Apps CLI", "type": "module", "scripts": { @@ -55,6 +55,7 @@ "keytar": "^7.9.0", "node-fetch": "^2.7.0", "open": "^8.4.2", + "openid-client": "^6.7.1", "ora": "^5.4.1", "pem": "^1.14.8", "prompts": "^2.4.2", diff --git a/src/core/utils/openidHelper.ts b/src/core/utils/openidHelper.ts new file mode 100644 index 000000000..2f09b7e78 --- /dev/null +++ b/src/core/utils/openidHelper.ts @@ -0,0 +1,35 @@ +import * as client from "openid-client"; + +export class OpenIdHelper { + private issuerUrl: URL; + private clientId: string; + + constructor(issuerUrl: string, clientId: string) { + if (!issuerUrl || issuerUrl.trim() === "") { + throw new Error("Issuer URL is required"); + } + if (!clientId || clientId.trim() === "") { + throw new Error("Client ID is required"); + } + this.issuerUrl = new URL(issuerUrl); + this.clientId = clientId; + } + + /** + * Discover issuer metadata from the OpenID Connect provider + */ + async discoverIssuer() { + return await client.discovery(this.issuerUrl, this.clientId); + } + + /** + * Retrieve the authorization endpoint from the issuer + */ + async getAuthorizationEndpoint(): Promise { + const issuer = await this.discoverIssuer(); + if (!issuer.serverMetadata().authorization_endpoint) { + throw new Error("Authorization endpoint not found in issuer metadata"); + } + return issuer.serverMetadata().authorization_endpoint!; + } +} diff --git a/src/msha/auth/routes/auth-login-provider-custom.ts b/src/msha/auth/routes/auth-login-provider-custom.ts index 600dcf20a..5dfccd8bc 100644 --- a/src/msha/auth/routes/auth-login-provider-custom.ts +++ b/src/msha/auth/routes/auth-login-provider-custom.ts @@ -4,6 +4,7 @@ import { response } from "../../../core/utils/net.js"; import { CUSTOM_AUTH_REQUIRED_FIELDS, ENTRAID_FULL_NAME, SWA_CLI_APP_PROTOCOL } from "../../../core/constants.js"; import { DEFAULT_CONFIG } from "../../../config.js"; import { encryptAndSign, extractPostLoginRedirectUri, hashStateGuid, newNonceWithExpiration } from "../../../core/utils/auth.js"; +import { OpenIdHelper } from "../../../core/utils/openidHelper.js"; export const normalizeAuthProvider = function (providerName?: string) { if (providerName === ENTRAID_FULL_NAME) { @@ -87,7 +88,8 @@ const httpTrigger = async function (context: Context, request: IncomingMessage, location = `https://github.com/login/oauth/authorize?response_type=code&client_id=${authFields?.clientIdSettingName}&redirect_uri=${redirectUri}/.auth/login/github/callback&scope=read:user&state=${hashedState}`; break; case "aad": - location = `${authFields?.openIdIssuer}/authorize?response_type=code&client_id=${authFields?.clientIdSettingName}&redirect_uri=${redirectUri}/.auth/login/aad/callback&scope=openid+profile+email&state=${hashedState}`; + 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}`; break; case "facebook": location = `https://facebook.com/v11.0/dialog/oauth?client_id=${authFields?.appIdSettingName}&redirect_uri=${redirectUri}/.auth/login/facebook/callback&scope=openid&state=${hashedState}&response_type=code`;