Skip to content

Commit 10e66ff

Browse files
committed
Add OIDC issuer validation and new testing style
1 parent a65cd3c commit 10e66ff

File tree

15 files changed

+1611
-528
lines changed

15 files changed

+1611
-528
lines changed

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AbstractClientApplicationBase.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,10 @@ public T correlationId(String val) {
579579
((OidcAuthority) authenticationAuthority).setAuthorityProperties(
580580
OidcDiscoveryProvider.performOidcDiscovery(
581581
(OidcAuthority) authenticationAuthority, this));
582+
583+
if (!((OidcAuthority) authenticationAuthority).isIssuerValid()) {
584+
throw new MsalClientException(String.format("Invalid issuer from OIDC discovery: %s", ((OidcAuthority) authenticationAuthority).issuerFromOidcDiscovery), "issuer_validation");
585+
}
582586
}
583587
}
584588
}

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/OidcAuthority.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public class OidcAuthority extends Authority {
1010
//Part of the OpenIdConnect standard, this is appended to the authority to create the endpoint that has OIDC metadata
1111
static final String WELL_KNOWN_OPENID_CONFIGURATION = ".well-known/openid-configuration";
1212
private static final String AUTHORITY_FORMAT = "https://%s/%s/";
13+
String issuerFromOidcDiscovery;
1314

1415
OidcAuthority(URL authorityUrl) throws MalformedURLException {
1516
super(createOidcDiscoveryUrl(authorityUrl), AuthorityType.OIDC);
@@ -29,5 +30,49 @@ void setAuthorityProperties(OidcDiscoveryResponse instanceDiscoveryResponse) {
2930
this.tokenEndpoint = instanceDiscoveryResponse.tokenEndpoint();
3031
this.deviceCodeEndpoint = instanceDiscoveryResponse.deviceCodeEndpoint();
3132
this.selfSignedJwtAudience = this.tokenEndpoint;
33+
this.issuerFromOidcDiscovery = instanceDiscoveryResponse.issuer();
34+
}
35+
36+
/**
37+
* Validates the issuer from OIDC discovery.
38+
* Issuer is valid if it matches the authority URL (without the well-known segment)
39+
* or if it follows the CIAM issuer format.
40+
*
41+
* @return true if the issuer is valid, false otherwise
42+
*/
43+
boolean isIssuerValid() {
44+
if (issuerFromOidcDiscovery == null) {
45+
return false;
46+
}
47+
48+
// Normalize issuer by removing trailing slashes
49+
String normalizedIssuer = issuerFromOidcDiscovery;
50+
while (normalizedIssuer.endsWith("/")) {
51+
normalizedIssuer = normalizedIssuer.substring(0, normalizedIssuer.length() - 1);
52+
}
53+
54+
// Case 1: Check against canonicalAuthorityUrl without the well-known segment
55+
String authorityWithoutWellKnown = canonicalAuthorityUrl.toString();
56+
if (authorityWithoutWellKnown.endsWith(WELL_KNOWN_OPENID_CONFIGURATION)) {
57+
authorityWithoutWellKnown = authorityWithoutWellKnown.substring(0,
58+
authorityWithoutWellKnown.length() - WELL_KNOWN_OPENID_CONFIGURATION.length());
59+
60+
// Remove trailing slash if present
61+
if (authorityWithoutWellKnown.endsWith("/")) {
62+
authorityWithoutWellKnown = authorityWithoutWellKnown.substring(0, authorityWithoutWellKnown.length() - 1);
63+
}
64+
65+
if (normalizedIssuer.equals(authorityWithoutWellKnown)) {
66+
return true;
67+
}
68+
}
69+
70+
// Case 2: Check CIAM format: "https://{tenant}.ciamlogin.com/{tenant}/"
71+
if (tenant != null && !tenant.isEmpty()) {
72+
String ciamPattern = "https://" + tenant + ".ciamlogin.com/" + tenant;
73+
return normalizedIssuer.startsWith(ciamPattern);
74+
}
75+
76+
return false;
3277
}
3378
}

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/OidcDiscoveryResponse.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class OidcDiscoveryResponse implements JsonSerializable<OidcDiscoveryResponse> {
1515
private String authorizationEndpoint;
1616
private String tokenEndpoint;
1717
private String deviceCodeEndpoint;
18+
private String issuer;
1819

1920
public static OidcDiscoveryResponse fromJson(JsonReader jsonReader) throws IOException {
2021
OidcDiscoveryResponse response = new OidcDiscoveryResponse();
@@ -32,6 +33,9 @@ public static OidcDiscoveryResponse fromJson(JsonReader jsonReader) throws IOExc
3233
case "device_authorization_endpoint":
3334
response.deviceCodeEndpoint = reader.getString();
3435
break;
36+
case "issuer":
37+
response.issuer = reader.getString();
38+
break;
3539
default:
3640
reader.skipChildren();
3741
break;
@@ -61,4 +65,8 @@ String tokenEndpoint() {
6165
String deviceCodeEndpoint() {
6266
return this.deviceCodeEndpoint;
6367
}
68+
69+
String issuer() {
70+
return this.issuer;
71+
}
6472
}

0 commit comments

Comments
 (0)