Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"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)",
"packageName": "@azure/msal-common",
"email": "nicknamer@users.noreply.github.com",
"dependentChangeType": "patch"
}
2 changes: 1 addition & 1 deletion lib/msal-browser/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const msalInstance = new PublicClientApplication(msalConfig);
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `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. |
| `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` |
| `knownAuthorities` | An array of URIs that are known to be valid. Used in B2C scenarios. | Array of strings in URI format | Empty array `[]` |
| `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 `[]` |
| `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 `""` |
Comment thread
lalimasharda marked this conversation as resolved.
| `authorityMetadata` | A string containing the .well-known/openid-configuration endpoint response. See [Performance](../../msal-common/docs/performance.md) for more info | string | Empty string `""` |
| `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) |
Expand Down
2 changes: 1 addition & 1 deletion lib/msal-common/apiReview/msal-common.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4839,7 +4839,7 @@ const X_MS_LIB_CAPABILITY_VALUE: string;
// 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
// 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
// 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
// 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
// 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
// src/authority/AuthorityOptions.ts:25:5 - (ae-forgotten-export) The symbol "CloudInstanceDiscoveryResponse" needs to be exported by the entry point index.d.ts
// 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
// 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
Expand Down
27 changes: 26 additions & 1 deletion lib/msal-common/docs/authority.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,32 @@ Where `tenant` would mean:
- GUID (tenantId)
- a verified domain for the tenant

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.
#### CIAM Issuer Validation and `knownAuthorities`

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:

```
https://<tenant-guid>.ciamlogin.com/<tenant-guid>/v2.0
```

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`:

```javascript
const pca = new PublicClientApplication({
auth: {
clientId: "<client-id>",
authority: "https://contoso.ciamlogin.com/contoso.onmicrosoft.com/",
knownAuthorities: [
"contoso.ciamlogin.com",
"<tenant-guid>.ciamlogin.com" // Add the GUID-based issuer host
]
}
});
```

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`.

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.

### Other OIDC-compliant IdPs

Expand Down
25 changes: 20 additions & 5 deletions lib/msal-common/src/authority/Authority.ts
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,7 @@ export class Authority {
}

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

/**
* Helper function to determine if this host is included in the knownAuthorities config option
* Helper function to determine if a host is included in the knownAuthorities config option.
*/
private isInKnownAuthorities(): boolean {
private isInKnownAuthorities(host: string): boolean {
const normalizedHost = host.toLowerCase();
const matches = this.authorityOptions.knownAuthorities.filter(
(authority) => {
return (
authority &&
UrlString.getDomainFromUrl(authority).toLowerCase() ===
this.hostnameAndPort
normalizedHost
);
}
);
Comment thread
lalimasharda marked this conversation as resolved.
Expand Down Expand Up @@ -1214,6 +1215,10 @@ export class Authority {
* 4. Same as (2), but the issuer host matches the CIAM tenant pattern
* `{tenant}.ciamlogin.com` with an optional `/{tenant}[.onmicrosoft.com][/v2.0]`
* path.
* 5. The issuer host is HTTPS and is explicitly listed in the
* developer-configured `knownAuthorities`. This covers scenarios where
* the OIDC discovery document returns an issuer host that differs from
* the authority (e.g., a GUID-based issuer for a name-based CIAM authority).
*
* @param issuer The `issuer` value returned in the OIDC discovery document.
* @throws ClientConfigurationError("issuer_validation_failed") on failure.
Expand Down Expand Up @@ -1274,12 +1279,22 @@ export class Authority {
this.canonicalAuthorityUrlComponents.PathSegments
);

/*
* Rule 5: The issuer host is explicitly listed in the developer-configured
* knownAuthorities. This covers scenarios where the OIDC discovery document
* returns an issuer with a different host than the authority
* (e.g., a GUID-based issuer for a name-based authority).
*/
const matchesKnownAuthority =
issuerScheme === "https:" && this.isInKnownAuthorities(issuerHost);

// Each rule is an independent boolean; the issuer is valid if ANY rule matches.
if (
matchesAuthorityOrigin ||
matchesKnownMicrosoftHost ||
matchesRegionalMicrosoftHost ||
matchesCiamTenantPattern
matchesCiamTenantPattern ||
matchesKnownAuthority
) {
return;
}
Expand Down
71 changes: 71 additions & 0 deletions lib/msal-common/test/authority/Authority.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2016,6 +2016,77 @@ describe("Authority.ts Class Unit Tests", () => {
).toThrow(issuerValidationFailedError);
});
});

describe("Rule 5: issuer host in knownAuthorities", () => {
it("accepts an issuer whose host is explicitly listed in knownAuthorities", () => {
const tenantGuid =
"12345678-1234-1234-1234-123456789abc";
const testAuthority = buildAuthority(
"https://contoso.ciamlogin.com/contoso.onmicrosoft.com/",
[
"contoso.ciamlogin.com",
`${tenantGuid}.ciamlogin.com`,
]
);
expect(() =>
callValidateIssuer(
testAuthority,
`https://${tenantGuid}.ciamlogin.com/${tenantGuid}/v2.0`
)
).not.toThrow();
});

it("rejects an issuer whose host is NOT in knownAuthorities and no other rule matches", () => {
const tenantGuid =
"12345678-1234-1234-1234-123456789abc";
const testAuthority = buildAuthority(
"https://contoso.ciamlogin.com/contoso.onmicrosoft.com/",
["contoso.ciamlogin.com"]
);
expect(() =>
callValidateIssuer(
testAuthority,
`https://${tenantGuid}.ciamlogin.com/${tenantGuid}/v2.0`
)
).toThrow(issuerValidationFailedError);
});

it("rejects an HTTP issuer even when the host is listed in knownAuthorities", () => {
const tenantGuid =
"12345678-1234-1234-1234-123456789abc";
const testAuthority = buildAuthority(
"https://contoso.ciamlogin.com/contoso.onmicrosoft.com/",
[
"contoso.ciamlogin.com",
`${tenantGuid}.ciamlogin.com`,
]
);
expect(() =>
callValidateIssuer(
testAuthority,
`http://${tenantGuid}.ciamlogin.com/${tenantGuid}/v2.0`
)
).toThrow(issuerValidationFailedError);
});

it("accepts an issuer whose host is in knownAuthorities regardless of case", () => {
const tenantGuid =
"12345678-1234-1234-1234-123456789abc";
const testAuthority = buildAuthority(
"https://contoso.ciamlogin.com/contoso.onmicrosoft.com/",
[
"contoso.ciamlogin.com",
`${tenantGuid}.ciamlogin.com`,
]
);
expect(() =>
callValidateIssuer(
testAuthority,
`https://${tenantGuid.toUpperCase()}.ciamlogin.com/${tenantGuid}/v2.0`
)
).not.toThrow();
});
});
Comment thread
lalimasharda marked this conversation as resolved.
});
});

Expand Down
Loading