Skip to content

Commit cf5eb29

Browse files
lalimashardaCopilotCopilotCopilot
authored
fix: add knownAuthorities check to issuer validation for CIAM GUID-based issuer URL (#8595)
Entra External ID (CIAM) may return an OIDC issuer with the tenant GUID as the host (e.g. https://<guid>.ciamlogin.com/<guid>/v2.0) even when the authority was configured with a tenant name. The existing issuer validation rules only match against the authority host or hardcoded Microsoft hosts, causing endpoints_resolution_error for CIAM tenants. Changes: - Add Rule 5 to validateIssuer: accept issuers whose HTTPS host is explicitly listed in the developer-configured knownAuthorities - Refactor isInKnownAuthorities to accept a host parameter so it can be reused for both cloud discovery and issuer validation - Add unit tests for Rule 5 (accept, reject, reject-HTTP) - Update CIAM docs in authority.md with workaround guidance - Update knownAuthorities description in configuration.md Fixes #8592 --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lalimasharda <26092202+lalimasharda@users.noreply.github.com>
1 parent 738ade6 commit cf5eb29

6 files changed

Lines changed: 126 additions & 8 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "Add issuer validation Rule 5: accept issuer hosts explicitly listed in knownAuthorities. Fixes Entra External ID (CIAM) regression where a GUID-based issuer is returned for a name-based authority [#8592](https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/8592)",
4+
"packageName": "@azure/msal-common",
5+
"email": "nicknamer@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}

lib/msal-browser/docs/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const msalInstance = new PublicClientApplication(msalConfig);
7474
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
7575
| `clientId` | App ID of your application. Can be found in your [portal registration](../README#prerequisites). | UUID/GUID | None. This parameter is required in order for MSAL to perform any actions. |
7676
| `authority` | URI of the tenant to authenticate and authorize with. Usually takes the form of `https://{uri}/{tenantid}` (see [Authority](../../msal-common/docs/authority.md)) | String in URI format with tenant - `https://{uri}/{tenantid}` | `https://login.microsoftonline.com/common` |
77-
| `knownAuthorities` | An array of URIs that are known to be valid. Used in B2C scenarios. | Array of strings in URI format | Empty array `[]` |
77+
| `knownAuthorities` | An array of known authority URIs. Used in B2C and CIAM scenarios. For CIAM with Entra External ID, include the GUID-based issuer host if it differs from the authority host (see [Authority - CIAM](../../msal-common/docs/authority.md#ciam-issuer-validation-and-knownauthorities)). | Array of strings in URI format | Empty array `[]` |
7878
| `cloudDiscoveryMetadata` | A string containing the cloud discovery response. Used in AAD scenarios. See [Performance](../../msal-common/docs/performance.md) for more info | string | Empty string `""` |
7979
| `authorityMetadata` | A string containing the .well-known/openid-configuration endpoint response. See [Performance](../../msal-common/docs/performance.md) for more info | string | Empty string `""` |
8080
| `redirectUri` | URI where the authorization code response is sent back to. Whatever location is specified here must have the MSAL library available to handle the response. | String in absolute or relative URI format | Login request page (`window.location.href` of page which made auth request) |

lib/msal-common/apiReview/msal-common.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4839,7 +4839,7 @@ const X_MS_LIB_CAPABILITY_VALUE: string;
48394839
// src/authority/Authority.ts:694:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
48404840
// src/authority/Authority.ts:805:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
48414841
// src/authority/Authority.ts:1003:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
4842-
// src/authority/Authority.ts:1218:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
4842+
// src/authority/Authority.ts:1223:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
48434843
// src/authority/AuthorityOptions.ts:25:5 - (ae-forgotten-export) The symbol "CloudInstanceDiscoveryResponse" needs to be exported by the entry point index.d.ts
48444844
// src/cache/CacheManager.ts:355:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
48454845
// src/cache/CacheManager.ts:356:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen

lib/msal-common/docs/authority.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,32 @@ Where `tenant` would mean:
133133
- GUID (tenantId)
134134
- a verified domain for the tenant
135135

136-
Note: MSAL JS currently is previeing the `CIAM` support. This is an emerging space and there could be some changes to the support until we GA the feature.
136+
#### CIAM Issuer Validation and `knownAuthorities`
137+
138+
The Entra External ID (CIAM) service may return an OIDC issuer whose host uses the **tenant GUID** rather than the tenant name. For example, an authority configured as `https://contoso.ciamlogin.com/contoso.onmicrosoft.com/` may return an issuer of the form:
139+
140+
```
141+
https://<tenant-guid>.ciamlogin.com/<tenant-guid>/v2.0
142+
```
143+
144+
Because the issuer host (`<tenant-guid>.ciamlogin.com`) differs from the authority host (`contoso.ciamlogin.com`) and MSAL JS does not have a reliable way to know the mapping beforehand, MSAL's issuer validation will reject it unless the GUID-based host is explicitly trusted. To resolve this, add the GUID-based host to `knownAuthorities`:
145+
146+
```javascript
147+
const pca = new PublicClientApplication({
148+
auth: {
149+
clientId: "<client-id>",
150+
authority: "https://contoso.ciamlogin.com/contoso.onmicrosoft.com/",
151+
knownAuthorities: [
152+
"contoso.ciamlogin.com",
153+
"<tenant-guid>.ciamlogin.com" // Add the GUID-based issuer host
154+
]
155+
}
156+
});
157+
```
158+
159+
You can find your tenant GUID in the Azure portal under **Tenant properties** or by inspecting the OIDC discovery document at `https://<tenantName>.ciamlogin.com/<tenantName>.onmicrosoft.com/v2.0/.well-known/openid-configuration`.
160+
161+
Note: MSAL JS currently is previewing the `CIAM` support. This is an emerging space and there could be some changes to the support until we GA the feature.
137162

138163
### Other OIDC-compliant IdPs
139164

lib/msal-common/src/authority/Authority.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -984,7 +984,7 @@ export class Authority {
984984
}
985985

986986
// If cloudDiscoveryMetadata is empty or does not contain the host, check knownAuthorities
987-
if (this.isInKnownAuthorities()) {
987+
if (this.isInKnownAuthorities(this.hostnameAndPort)) {
988988
this.logger.verbose(
989989
"The host is included in knownAuthorities. Creating new cloud discovery metadata from the host.",
990990
this.correlationId
@@ -1112,15 +1112,16 @@ export class Authority {
11121112
}
11131113

11141114
/**
1115-
* Helper function to determine if this host is included in the knownAuthorities config option
1115+
* Helper function to determine if a host is included in the knownAuthorities config option.
11161116
*/
1117-
private isInKnownAuthorities(): boolean {
1117+
private isInKnownAuthorities(host: string): boolean {
1118+
const normalizedHost = host.toLowerCase();
11181119
const matches = this.authorityOptions.knownAuthorities.filter(
11191120
(authority) => {
11201121
return (
11211122
authority &&
11221123
UrlString.getDomainFromUrl(authority).toLowerCase() ===
1123-
this.hostnameAndPort
1124+
normalizedHost
11241125
);
11251126
}
11261127
);
@@ -1214,6 +1215,10 @@ export class Authority {
12141215
* 4. Same as (2), but the issuer host matches the CIAM tenant pattern
12151216
* `{tenant}.ciamlogin.com` with an optional `/{tenant}[.onmicrosoft.com][/v2.0]`
12161217
* path.
1218+
* 5. The issuer host is HTTPS and is explicitly listed in the
1219+
* developer-configured `knownAuthorities`. This covers scenarios where
1220+
* the OIDC discovery document returns an issuer host that differs from
1221+
* the authority (e.g., a GUID-based issuer for a name-based CIAM authority).
12171222
*
12181223
* @param issuer The `issuer` value returned in the OIDC discovery document.
12191224
* @throws ClientConfigurationError("issuer_validation_failed") on failure.
@@ -1274,12 +1279,22 @@ export class Authority {
12741279
this.canonicalAuthorityUrlComponents.PathSegments
12751280
);
12761281

1282+
/*
1283+
* Rule 5: The issuer host is explicitly listed in the developer-configured
1284+
* knownAuthorities. This covers scenarios where the OIDC discovery document
1285+
* returns an issuer with a different host than the authority
1286+
* (e.g., a GUID-based issuer for a name-based authority).
1287+
*/
1288+
const matchesKnownAuthority =
1289+
issuerScheme === "https:" && this.isInKnownAuthorities(issuerHost);
1290+
12771291
// Each rule is an independent boolean; the issuer is valid if ANY rule matches.
12781292
if (
12791293
matchesAuthorityOrigin ||
12801294
matchesKnownMicrosoftHost ||
12811295
matchesRegionalMicrosoftHost ||
1282-
matchesCiamTenantPattern
1296+
matchesCiamTenantPattern ||
1297+
matchesKnownAuthority
12831298
) {
12841299
return;
12851300
}

lib/msal-common/test/authority/Authority.spec.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2016,6 +2016,77 @@ describe("Authority.ts Class Unit Tests", () => {
20162016
).toThrow(issuerValidationFailedError);
20172017
});
20182018
});
2019+
2020+
describe("Rule 5: issuer host in knownAuthorities", () => {
2021+
it("accepts an issuer whose host is explicitly listed in knownAuthorities", () => {
2022+
const tenantGuid =
2023+
"12345678-1234-1234-1234-123456789abc";
2024+
const testAuthority = buildAuthority(
2025+
"https://contoso.ciamlogin.com/contoso.onmicrosoft.com/",
2026+
[
2027+
"contoso.ciamlogin.com",
2028+
`${tenantGuid}.ciamlogin.com`,
2029+
]
2030+
);
2031+
expect(() =>
2032+
callValidateIssuer(
2033+
testAuthority,
2034+
`https://${tenantGuid}.ciamlogin.com/${tenantGuid}/v2.0`
2035+
)
2036+
).not.toThrow();
2037+
});
2038+
2039+
it("rejects an issuer whose host is NOT in knownAuthorities and no other rule matches", () => {
2040+
const tenantGuid =
2041+
"12345678-1234-1234-1234-123456789abc";
2042+
const testAuthority = buildAuthority(
2043+
"https://contoso.ciamlogin.com/contoso.onmicrosoft.com/",
2044+
["contoso.ciamlogin.com"]
2045+
);
2046+
expect(() =>
2047+
callValidateIssuer(
2048+
testAuthority,
2049+
`https://${tenantGuid}.ciamlogin.com/${tenantGuid}/v2.0`
2050+
)
2051+
).toThrow(issuerValidationFailedError);
2052+
});
2053+
2054+
it("rejects an HTTP issuer even when the host is listed in knownAuthorities", () => {
2055+
const tenantGuid =
2056+
"12345678-1234-1234-1234-123456789abc";
2057+
const testAuthority = buildAuthority(
2058+
"https://contoso.ciamlogin.com/contoso.onmicrosoft.com/",
2059+
[
2060+
"contoso.ciamlogin.com",
2061+
`${tenantGuid}.ciamlogin.com`,
2062+
]
2063+
);
2064+
expect(() =>
2065+
callValidateIssuer(
2066+
testAuthority,
2067+
`http://${tenantGuid}.ciamlogin.com/${tenantGuid}/v2.0`
2068+
)
2069+
).toThrow(issuerValidationFailedError);
2070+
});
2071+
2072+
it("accepts an issuer whose host is in knownAuthorities regardless of case", () => {
2073+
const tenantGuid =
2074+
"12345678-1234-1234-1234-123456789abc";
2075+
const testAuthority = buildAuthority(
2076+
"https://contoso.ciamlogin.com/contoso.onmicrosoft.com/",
2077+
[
2078+
"contoso.ciamlogin.com",
2079+
`${tenantGuid}.ciamlogin.com`,
2080+
]
2081+
);
2082+
expect(() =>
2083+
callValidateIssuer(
2084+
testAuthority,
2085+
`https://${tenantGuid.toUpperCase()}.ciamlogin.com/${tenantGuid}/v2.0`
2086+
)
2087+
).not.toThrow();
2088+
});
2089+
});
20192090
});
20202091
});
20212092

0 commit comments

Comments
 (0)