Skip to content

Commit cb53a41

Browse files
authored
Updates for Azure AD Graph API retirement (#741)
See #739 https://techcommunity.microsoft.com/blog/microsoft-entra-blog/action-required-azure-ad-graph-api-retirement/4090533 We can get rid of code to handle the "nonce" field that isn't part of the JOSE spec. Signed-off-by: Thomas Segismont <tsegismont@gmail.com>
1 parent adef3fb commit cb53a41

File tree

10 files changed

+104
-109
lines changed

10 files changed

+104
-109
lines changed

vertx-auth-common/azure.sh

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/bin/bash
2+
3+
#
4+
# Copyright (c) 2011-2026 Contributors to the Eclipse Foundation
5+
#
6+
# This program and the accompanying materials are made available under the
7+
# terms of the Eclipse Public License 2.0 which is available at
8+
# http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
9+
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
10+
#
11+
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
12+
#
13+
14+
# Generated with Gemini
15+
16+
###############################################################################
17+
# UNDERSTANDING THE JSON FIELDS (JWK - RFC 7517)
18+
#
19+
# This script generates a mock Microsoft discovery file (azure-sample-keys.json)
20+
# and a signed JWT (azure-sample-token.txt).
21+
#
22+
# Field | Description
23+
# ------|----------------------------------------------------------------------
24+
# kid | Key ID: Must match the 'kid' in the JWT header. Used to find the key.
25+
# kty | Key Type: Set to 'RSA'.
26+
# use | Public Key Use: Set to 'sig' (signature).
27+
# n | Modulus: The "large number" part of the RSA public key (Base64Url).
28+
# e | Exponent: The RSA public exponent, usually 'AQAB' (decimal 65537).
29+
# x5c | Cert Chain: Base64-encoded X.509 certificate (Standard Base64).
30+
# x5t | Thumbprint: SHA-1 hash of the DER-encoded certificate (Base64Url).
31+
###############################################################################
32+
33+
# 1. Configuration
34+
KID="test-kid-2026"
35+
TARGET_DIR="src/test/resources"
36+
JWKS_FILE="$TARGET_DIR/azure-sample-keys.json"
37+
TOKEN_FILE="$TARGET_DIR/azure-sample-token.txt"
38+
39+
# Ensure the directory exists
40+
mkdir -p "$TARGET_DIR"
41+
42+
# 2. Generate Temporary Keys
43+
# We use temporary names to ensure we don't overwrite anything important
44+
openssl genrsa -out temp_private.pem 2048 2>/dev/null
45+
openssl req -new -x509 -key temp_private.pem -out temp_cert.pem -days 10950 -subj "/CN=login.microsoftonline.test" 2>/dev/null
46+
47+
# 3. Helper function for Base64Url encoding that handles different OS flags
48+
b64url() {
49+
# Try -w0 (Linux/GNU), if it fails, use -b0 (macOS/BSD), then remove newlines manually just in case
50+
(base64 -w0 2>/dev/null || base64 -b0 2>/dev/null || base64) | tr -d '\n=' | tr '+/' '-_'
51+
}
52+
53+
# 4. Extract Components for JWKS
54+
MOD=$(openssl rsa -in temp_private.pem -noout -modulus | cut -d'=' -f2 | xxd -r -p | b64url)
55+
X5C=$(openssl x509 -in temp_cert.pem -outform DER | base64 | tr -d '\n')
56+
X5T=$(openssl x509 -in temp_cert.pem -fingerprint -noout | cut -d'=' -f2 | sed 's/://g' | xxd -r -p | b64url)
57+
58+
# 5. Create the JWKS file
59+
printf '{\n "keys": [\n {\n "kty": "RSA",\n "use": "sig",\n "kid": "%s",\n "x5t": "%s",\n "n": "%s",\n "e": "AQAB",\n "x5c": ["%s"]\n }\n ]\n}\n' \
60+
"$KID" "$X5T" "$MOD" "$X5C" > "$JWKS_FILE"
61+
62+
# 6. Generate and Sign the Token
63+
HEADER='{"alg":"RS256","typ":"JWT","kid":"'$KID'"}'
64+
PAYLOAD='{"iss":"https://sts.windows.net/test-tenant/","sub":"junit-test","aud":"your-client-id","iat":1704067200,"exp":2524608000}'
65+
66+
H_B64=$(printf '%s' "$HEADER" | b64url)
67+
P_B64=$(printf '%s' "$PAYLOAD" | b64url)
68+
69+
# IMPORTANT: Sign the "HEADER.PAYLOAD" string
70+
# 'openssl dgst' can sometimes add metadata, so we pipe strictly the string
71+
SIG=$(printf '%s.%s' "$H_B64" "$P_B64" | openssl dgst -sha256 -sign temp_private.pem -binary | b64url)
72+
73+
# Save the token as a single continuous line with NO trailing characters
74+
printf "%s.%s.%s" "$H_B64" "$P_B64" "$SIG" | tr -d '[:space:]' > "$TOKEN_FILE"
75+
76+
# 7. Cleanup
77+
rm temp_private.pem
78+
rm temp_cert.pem
79+
80+
echo "Cleanup complete. Assets generated in $TARGET_DIR:"
81+
echo " - $JWKS_FILE"
82+
echo " - $TOKEN_FILE"

vertx-auth-common/src/main/java/io/vertx/ext/auth/JWTOptions.java

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ public class JWTOptions {
2121
private String issuer;
2222
private String subject;
2323
private List<String> permissions;
24-
private String nonceAlgorithm;
2524

2625
public JWTOptions() {
2726
header = new JsonObject();
@@ -38,7 +37,6 @@ public JWTOptions(JWTOptions other) {
3837
this.issuer = other.issuer;
3938
this.subject = other.subject;
4039
this.permissions = other.permissions == null ? null : new ArrayList<>(other.permissions);
41-
this.nonceAlgorithm = other.nonceAlgorithm;
4240
}
4341

4442
public JWTOptions(JsonObject json) {
@@ -145,13 +143,4 @@ public JWTOptions setSubject(String subject) {
145143
this.subject = subject;
146144
return this;
147145
}
148-
149-
public String getNonceAlgorithm() {
150-
return nonceAlgorithm;
151-
}
152-
153-
public JWTOptions setNonceAlgorithm(String nonceAlgorithm) {
154-
this.nonceAlgorithm = nonceAlgorithm;
155-
return this;
156-
}
157146
}

vertx-auth-common/src/main/java/io/vertx/ext/auth/impl/jose/JWT.java

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ public final class JWT {
5151

5252
private boolean allowEmbeddedKey = false;
5353
private X509Certificate rootCA;
54-
private MessageDigest nonceDigest;
5554

5655
// keep 2 maps (1 for sing, 1 for verify) this simplifies the lookups
5756
private final Map<String, List<JWS>> SIGN = new ConcurrentHashMap<>();
@@ -116,19 +115,6 @@ public JWT embeddedKeyRootCA(String rootCA) throws CertificateException {
116115
return this;
117116
}
118117

119-
public JWT nonceAlgorithm(String alg) {
120-
if (alg == null) {
121-
nonceDigest = null;
122-
} else {
123-
try {
124-
nonceDigest = MessageDigest.getInstance(alg);
125-
} catch (NoSuchAlgorithmException e) {
126-
throw new IllegalArgumentException(e);
127-
}
128-
}
129-
return this;
130-
}
131-
132118
private void addJWK(List<JWS> current, JWK jwk) {
133119
boolean replaced = false;
134120
for (int i = 0; i < current.size(); i++) {
@@ -281,16 +267,6 @@ public JsonObject decode(final String token, boolean full, List<X509CRL> crls) t
281267
throw new SignatureException("missing signature segment");
282268
}
283269
byte[] payloadInput = base64UrlDecode(signatureSeg);
284-
if (nonceDigest != null && header.containsKey("nonce")) {
285-
// this is an Azure Graph extension, a nonce is added to the token
286-
// after the serialization. The original value is the digest of the
287-
// post value.
288-
synchronized (this) {
289-
nonceDigest.reset();
290-
header.put("nonce", base64UrlEncode(nonceDigest.digest(header.getString("nonce").getBytes(StandardCharsets.UTF_8))));
291-
headerSeg = base64UrlEncode(header.encode().getBytes(StandardCharsets.UTF_8));
292-
}
293-
}
294270
byte[] signingInput = (headerSeg + "." + payloadSeg).getBytes(UTF8);
295271

296272
String kid = header.getString("kid");

vertx-auth-common/src/test/java/io/vertx/tests/JWKTest.java

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import io.vertx.ext.auth.JWTOptions;
77
import io.vertx.ext.auth.impl.jose.JWK;
88
import io.vertx.ext.auth.impl.jose.JWT;
9-
import org.junit.Ignore;
109
import org.junit.Test;
1110

1211
import java.io.IOException;
@@ -173,22 +172,18 @@ public void publicECK() {
173172
new JWK(jwk);
174173
}
175174

176-
@Ignore("Certificate expired")
177175
@Test
178176
public void loadAzure() throws Exception {
179177
Vertx vertx = Vertx.vertx();
180-
JsonObject azure = new JsonObject(vertx.fileSystem()
181-
.readFileBlocking("azure.json"));
178+
JsonObject azureSampleKeys = new JsonObject(vertx.fileSystem().readFileBlocking("azure-sample-keys.json"));
179+
String azureSampleToken = vertx.fileSystem().readFileBlocking("azure-sample-token.txt").toString();
182180

183-
JWK key = new JWK(azure.getJsonArray("keys").getJsonObject(1));
184-
185-
JWT jwt = new JWT()
186-
.addJWK(key)
187-
.nonceAlgorithm("SHA-256");
181+
JWK key = new JWK(azureSampleKeys.getJsonArray("keys").getJsonObject(0));
188182

183+
JWT jwt = new JWT().addJWK(key);
189184

190185
System.out.println(
191-
jwt.decode("eyJ0eXAiOiJKV1QiLCJub25jZSI6InM5TzdaZ2F6WVJEd2VCZzZhbDNWVkhuZFFOQ0JHSVZZaDMxTDZRVFljTDAiLCJhbGciOiJSUzI1NiIsIng1dCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyIsImtpZCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyJ9.eyJhdWQiOiIwMDAwMDAwMy0wMDAwLTAwMDAtYzAwMC0wMDAwMDAwMDAwMDAiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9mY2FhZjcxMC03YzllLTRjYTAtOWNkYS02OTczOWZhZmJhNGIvIiwiaWF0IjoxNjExNjg3NDE1LCJuYmYiOjE2MTE2ODc0MTUsImV4cCI6MTYxMTY5MTMxNSwiYWNjdCI6MCwiYWNyIjoiMSIsImFjcnMiOlsidXJuOnVzZXI6cmVnaXN0ZXJzZWN1cml0eWluZm8iLCJ1cm46bWljcm9zb2Z0OnJlcTEiLCJ1cm46bWljcm9zb2Z0OnJlcTIiLCJ1cm46bWljcm9zb2Z0OnJlcTMiLCJjMSIsImMyIiwiYzMiLCJjNCIsImM1IiwiYzYiLCJjNyIsImM4IiwiYzkiLCJjMTAiLCJjMTEiLCJjMTIiLCJjMTMiLCJjMTQiLCJjMTUiLCJjMTYiLCJjMTciLCJjMTgiLCJjMTkiLCJjMjAiLCJjMjEiLCJjMjIiLCJjMjMiLCJjMjQiLCJjMjUiXSwiYWlvIjoiQVVRQXUvOFNBQUFBMFYwa0M4S25EM01RbUJ0WGk4OG9lT3NzT2xzU0JLZ1Rld1diQ1RnV2x0MEZ4dG9POVZtVjVjOGRLTStNckE1MXJoZW1Ebm9HMGJGUkRJWkpnM3Izb0E9PSIsImFtciI6WyJwd2QiLCJtZmEiXSwiYXBwX2Rpc3BsYXluYW1lIjoicG9iLXNlcnZlciIsImFwcGlkIjoiMTNjOWUxMTItYjE1OC00YjE5LThkNTctZTFmMzg4MWM0MzgzIiwiYXBwaWRhY3IiOiIxIiwiZmFtaWx5X25hbWUiOiJMb3BlcyIsImdpdmVuX25hbWUiOiJQYXVsbyIsImlkdHlwIjoidXNlciIsImlwYWRkciI6IjIxNy4xMDIuMTY1LjQ2IiwibmFtZSI6IkxhYiBQYXVsbyBMb3BlcyIsIm9pZCI6IjUyYmY4YWMyLTNlODMtNGNmNC04MTYxLTBiMTM1YWUwNjk4YiIsInBsYXRmIjoiMTQiLCJwdWlkIjoiMTAwMzIwMDEwRjQzQjcyRSIsInJoIjoiMC5BQUFBRVBlcV9KNThvRXljMm1sem42LTZTeExoeVJOWXNSbExqVmZoODRnY1E0TjVBRHMuIiwic2NwIjoiZW1haWwgb3BlbmlkIHByb2ZpbGUgVXNlci5SZWFkIiwic2lnbmluX3N0YXRlIjpbImttc2kiXSwic3ViIjoiWXltcTZLUmlLMHVUX1NVZ0JzYWUtaElIRE5VX0FLQVpvRG5TTWwxR3VpZyIsInRlbmFudF9yZWdpb25fc2NvcGUiOiJFVSIsInRpZCI6ImZjYWFmNzEwLTdjOWUtNGNhMC05Y2RhLTY5NzM5ZmFmYmE0YiIsInVuaXF1ZV9uYW1lIjoicGF1bG9AbGFiLnRlbnRpeG8uY29tIiwidXBuIjoicGF1bG9AbGFiLnRlbnRpeG8uY29tIiwidXRpIjoiZGNzM1ZzMXk5VW1SOXBzV05JaUxBQSIsInZlciI6IjEuMCIsIndpZHMiOlsiNjJlOTAzOTQtNjlmNS00MjM3LTkxOTAtMDEyMTc3MTQ1ZTEwIiwiYjc5ZmJmNGQtM2VmOS00Njg5LTgxNDMtNzZiMTk0ZTg1NTA5Il0sInhtc19zdCI6eyJzdWIiOiJiNGRXNmVuWk9fRGhFLUMyZE1Qdks5Y1JXcW8zXy1FQVc5MVJ4WmRKNnNVIn0sInhtc190Y2R0IjoxNjEwOTEyOTM2fQ.M4dXPZszAsL_rnceagjZnmd8yzbbB3hou4L6vVLGzqAt4wVg8KwQKxcxIGqBqgDWRlBvLYqWs61dvt8vSa-9GaMJifHwmfWoXPvyVzdxhx3qrqgHdsz1HWX5WzcDlEbHrPXZGE8KM-0czE67rePMxEHK7vLf5TbmERLGJt4QDOGZxVHYvnplIrIM1eGjANIeWYTyW5g-YDx3VX6yVl5QHvP4CFdINhDV7i-L3bjmV4M8F6wYs7Xs7nIrKYEiyjTrpXGUL7u29eHXgzeGlSxfXeqTdYmgEt6lFOxx-fZzO0m92AhPGGAe6IB85FtqSi5T95Nif3pHPsouryhDco8y3g")
186+
jwt.decode(azureSampleToken).encodePrettily()
192187
);
193188
}
194189

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"keys": [
3+
{
4+
"kty": "RSA",
5+
"use": "sig",
6+
"kid": "test-kid-2026",
7+
"x5t": "DKOCmlcao8v6VzwsfMZdBPYSwgw",
8+
"n": "igMDD4eqJc_gwbwPN9mg_19y59p-a8erLALe5g0xoTvJaizMHZdOevDkgwSX22z3XWX38qfSLhf_BrzMIRKdQCeKxw4blOk__FqoC8dZn0d48f3hqvY4NGqe0JVAw9ReKBz0JZHrxTnt12KiDfm8FGrcG-Ku-MuW5RPv1jg_s7wD5NgCVpy1WujcffKySa7-HI-9fkIfRd4XYFS5vsqS-2CTuazZdnMlP4lqJ5AmoJetMXIIgVHuPOnXwWjKEd3ywpOJO4jrPoBO3p3kruPUXz4vBhTtS9OfszeZPfdP-edcoP-MT2L2v3wdwFeEACunhuVMz-FEOCTyWSlYIWJpYw",
9+
"e": "AQAB",
10+
"x5c": [
11+
"MIIDLTCCAhWgAwIBAgIUK6goEG+ypWIUui1TmUT9w7WKUk4wDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwabG9naW4ubWljcm9zb2Z0b25saW5lLnRlc3QwIBcNMjYwMTEzMTM0NTMzWhgPMjA1NjAxMDYxMzQ1MzNaMCUxIzAhBgNVBAMMGmxvZ2luLm1pY3Jvc29mdG9ubGluZS50ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAigMDD4eqJc/gwbwPN9mg/19y59p+a8erLALe5g0xoTvJaizMHZdOevDkgwSX22z3XWX38qfSLhf/BrzMIRKdQCeKxw4blOk//FqoC8dZn0d48f3hqvY4NGqe0JVAw9ReKBz0JZHrxTnt12KiDfm8FGrcG+Ku+MuW5RPv1jg/s7wD5NgCVpy1WujcffKySa7+HI+9fkIfRd4XYFS5vsqS+2CTuazZdnMlP4lqJ5AmoJetMXIIgVHuPOnXwWjKEd3ywpOJO4jrPoBO3p3kruPUXz4vBhTtS9OfszeZPfdP+edcoP+MT2L2v3wdwFeEACunhuVMz+FEOCTyWSlYIWJpYwIDAQABo1MwUTAdBgNVHQ4EFgQUY3EXqAtFxyVz/USG7rLU05X9wMEwHwYDVR0jBBgwFoAUY3EXqAtFxyVz/USG7rLU05X9wMEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAGyI9jRKyLXXpGaxIl8usfNj7Z7AzrPyF9gczRBd1/a+KB73Lb2jkCq7RrhpOiWfKnXksWHDHK/jMYDXHFDg+b/DRWN+iCXug9KFezvYDa+UN4OEzFZb+gC5MGJskCFNsX4i1OrYO+GrsPpv54U6wrgWjSP8GFPobug1M3qHDf+xXV+/MlBqJoaLNbzM5yuZYMWvdD/nOsJ+nLcmWIhDw29fmi3hcimbeUHTcAZn+hHluzMqmTOyXmOx0bTbQQnKRMeyc273kc8eRnRZlehB3gL5GRJS2a8FdxvhZNfDIlo8VvWDcsrYVYOiLXg0QU72YRPvlI9v+v5i9v1Qpq67g/w=="
12+
]
13+
}
14+
]
15+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InRlc3Qta2lkLTIwMjYifQ.eyJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC90ZXN0LXRlbmFudC8iLCJzdWIiOiJqdW5pdC10ZXN0IiwiYXVkIjoieW91ci1jbGllbnQtaWQiLCJpYXQiOjE3MDQwNjcyMDAsImV4cCI6MjUyNDYwODAwMH0.KOy_yvvHTTDP-cXkjSBWdJWTOmeA1dMQtYIMWL64D-2UmyDbEoLeOFhHoGrVtQkT3uiPx2GgpAwYqu3DbZOxj_1t4fJK7C21FgFMs9a7P7z2D7zeq-w-OqRO63B2MoztjgzUx6aNMDDkdCI4HA76NKhv49TVnRfQmGlCfj5xlfBAcEM6ytelOgnRdlA5rWnkIiNdkBjuRpbykluRQl03Y2NdjP8FEJoQ-QNIby_uKKhOe50fy6a04MZ7njtVA-i_b4Oq0HzJfsqkWi2Kss7M_yPA3uuD-nSx5mR8tFkumamhHNsNNRsFZvTAoQlo7Ohw786bR4WLb-DfEjXORWxLLA

vertx-auth-common/src/test/resources/azure.json

Lines changed: 0 additions & 48 deletions
This file was deleted.

vertx-auth-jwt/src/main/java/io/vertx/ext/auth/jwt/impl/JWTAuthProviderImpl.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ public class JWTAuthProviderImpl implements JWTAuth {
6161

6262
public JWTAuthProviderImpl(Vertx vertx, JWTAuthOptions config) {
6363
this.jwtOptions = config.getJWTOptions();
64-
// set the nonce algorithm
65-
jwt.nonceAlgorithm(jwtOptions.getNonceAlgorithm());
6664

6765
final KeyStoreOptions keyStore = config.getKeyStore();
6866

vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/impl/OAuth2AuthProviderImpl.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,6 @@ public OAuth2AuthProviderImpl(Vertx vertx, OAuth2Options config) {
6969
this.config.replaceVariables(true);
7070
this.config.validate();
7171

72-
// set the nonce algorithm
73-
jwt.nonceAlgorithm(this.config.getJWTOptions().getNonceAlgorithm());
74-
7572
if (config.getPubSecKeys() != null) {
7673
for (PubSecKeyOptions pubSecKey : config.getPubSecKeys()) {
7774
try {
@@ -131,10 +128,6 @@ public Future<Void> jWKSet() {
131128
((VertxInternal) vertx).removeCloseHook(this);
132129
}
133130

134-
JWT jwt = new JWT()
135-
// set the nonce algorithm
136-
.nonceAlgorithm(config.getJWTOptions().getNonceAlgorithm());
137-
138131
JsonArray keys = json.getJsonArray("keys");
139132
for (Object key : keys) {
140133
try {

0 commit comments

Comments
 (0)