Skip to content

Commit 336f62e

Browse files
authored
Merge pull request #87 from speakeasy-sdks/ms/machine-auth--v2-changes
Add new machine auth support, auth with both secret key and machine key
2 parents 21003d4 + 5aa0f31 commit 336f62e

File tree

5 files changed

+107
-3
lines changed

5 files changed

+107
-3
lines changed

src/main/java/com/clerk/backend_api/helpers/security/AuthenticateRequest.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,16 @@ private static VerifyTokenOptions buildVerifyTokenOptions(AuthenticateRequestOpt
114114
.authorizedParties(options.authorizedParties()) //
115115
.clockSkew(options.clockSkewInMs(), TimeUnit.MILLISECONDS) //
116116
.build();
117-
} else {
117+
}
118+
else if (options.machineSecretKey().isPresent()) {
119+
return VerifyTokenOptions
120+
.machineSecretKey(options.machineSecretKey().get())
121+
.audience(options.audience())
122+
.authorizedParties(options.authorizedParties())
123+
.clockSkew(options.clockSkewInMs(), TimeUnit.MILLISECONDS)
124+
.build();
125+
}
126+
else {
118127
return new VerifyTokenOptions.Builder().build();
119128
}
120129
}

src/main/java/com/clerk/backend_api/helpers/security/models/AuthenticateRequestOptions.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public final class AuthenticateRequestOptions {
1919

2020
private final Optional<String> secretKey;
2121
private final Optional<String> jwtKey;
22+
private final Optional<String> machineSecretKey;
2223
private final Optional<String> audience;
2324
private final Set<String> authorizedParties;
2425
private final long clockSkewInMs;
@@ -42,6 +43,7 @@ public final class AuthenticateRequestOptions {
4243
public AuthenticateRequestOptions(
4344
Optional<String> secretKey,
4445
Optional<String> jwtKey,
46+
Optional<String> machineSecretKey,
4547
Optional<String> audience,
4648
Set<String> authorizedParties,
4749
Optional<Long> clockSkewInMs,
@@ -50,12 +52,14 @@ public AuthenticateRequestOptions(
5052

5153
Utils.checkNotNull(secretKey, "secretKey");
5254
Utils.checkNotNull(jwtKey, "jwtKey");
55+
Utils.checkNotNull(machineSecretKey, "machineSecretKey");
5356
Utils.checkNotNull(audience, "audience");
5457
Utils.checkNotNull(authorizedParties, "authorizedParties");
5558
Utils.checkNotNull(clockSkewInMs, "clockSkewInMs");
5659

5760
this.secretKey = secretKey;
5861
this.jwtKey = jwtKey;
62+
this.machineSecretKey = machineSecretKey;
5963
this.audience = audience;
6064
this.authorizedParties = authorizedParties;
6165
this.clockSkewInMs = clockSkewInMs.orElse(DEFAULT_CLOCK_SKEW_MS);
@@ -72,6 +76,10 @@ public Optional<String> jwtKey() {
7276
return jwtKey;
7377
}
7478

79+
public Optional<String> machineSecretKey() {
80+
return machineSecretKey;
81+
}
82+
7583
public Optional<String> audience() {
7684
return audience;
7785
}
@@ -105,6 +113,7 @@ public static final class Builder {
105113

106114
private Optional<String> secretKey = Optional.empty();
107115
private Optional<String> jwtKey = Optional.empty();
116+
private Optional<String> machineSecretKey = Optional.empty();
108117
private Optional<String> audience = Optional.empty();
109118
private Set<String> authorizedParties = new HashSet<>();
110119
private Optional<List<String>> acceptsToken = Optional.empty();
@@ -126,6 +135,13 @@ public static Builder withJwtKey(String jwtKey) {
126135
return builder;
127136
}
128137

138+
public static Builder withMachineSecretKey(String machineSecretKey) {
139+
Utils.checkNotNull(machineSecretKey, "machineSecretKey");
140+
Builder builder = new Builder();
141+
builder.machineSecretKey = Optional.of(machineSecretKey);
142+
return builder;
143+
}
144+
129145
public Builder audience(String audience) {
130146
Utils.checkNotNull(audience, "audience");
131147
return audience(Optional.of(audience));
@@ -171,6 +187,7 @@ public Builder acceptsTokens(List<String> acceptsToken) {
171187
public AuthenticateRequestOptions build() {
172188
return new AuthenticateRequestOptions(secretKey,
173189
jwtKey,
190+
machineSecretKey,
174191
audience,
175192
authorizedParties,
176193
Optional.of(clockSkewInMs),acceptsToken);

src/main/java/com/clerk/backend_api/helpers/security/models/VerifyTokenOptions.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public final class VerifyTokenOptions {
1919

2020
private final Optional<String> secretKey;
2121
private final Optional<String> jwtKey;
22+
private final Optional<String> machineSecretKey;
2223
private final Optional<String> audience;
2324
private final Set<String> authorizedParties;
2425
private final long clockSkewInMs;
@@ -47,6 +48,7 @@ public final class VerifyTokenOptions {
4748
public VerifyTokenOptions(
4849
Optional<String> secretKey,
4950
Optional<String> jwtKey,
51+
Optional<String> machineSecretKey,
5052
Optional<String> audience,
5153
Set<String> authorizedParties,
5254
Optional<Long> clockSkewInMs,
@@ -58,6 +60,7 @@ public VerifyTokenOptions(
5860
Utils.checkNotNull(clockSkewInMs, "clockSkewInMs");
5961
Utils.checkNotNull(jwtKey, "jwtKey");
6062
Utils.checkNotNull(secretKey, "secretKey");
63+
Utils.checkNotNull(machineSecretKey, "machineSecretKey");
6164
Utils.checkNotNull(apiUrl, "apiUrl");
6265
Utils.checkNotNull(apiVersion, "apiVersion");
6366

@@ -66,6 +69,7 @@ public VerifyTokenOptions(
6669
this.clockSkewInMs = clockSkewInMs.orElse(DEFAULT_CLOCK_SKEW_MS);
6770
this.jwtKey = jwtKey;
6871
this.secretKey = secretKey;
72+
this.machineSecretKey = machineSecretKey;
6973
this.apiUrl = apiUrl.orElse(DEFAULT_API_URL);
7074
this.apiVersion = apiVersion.orElse(DEFAULT_API_VERSION);
7175
}
@@ -90,6 +94,10 @@ public Optional<String> secretKey() {
9094
return secretKey;
9195
}
9296

97+
public Optional<String> machineSecretKey() {
98+
return machineSecretKey;
99+
}
100+
93101
public String apiUrl() {
94102
return apiUrl;
95103
}
@@ -106,11 +114,15 @@ public static Builder jwtKey(String jwtKey) {
106114
return Builder.withJwtKey(jwtKey);
107115
}
108116

117+
public static Builder machineSecretKey(String machineSecretKey) {
118+
return Builder.withMachineSecretKey(machineSecretKey);
119+
}
120+
109121
public static final class Builder {
110122

111123
private Optional<String> secretKey = Optional.empty();
112124
private Optional<String> jwtKey = Optional.empty();
113-
125+
private Optional<String> machineSecretKey = Optional.empty();
114126
private Optional<String> audience = Optional.empty();
115127
private Set<String> authorizedParties = new HashSet<>();
116128
private long clockSkewInMs = DEFAULT_CLOCK_SKEW_MS;
@@ -131,6 +143,13 @@ public static Builder withJwtKey(String jwtKey) {
131143
return builder;
132144
}
133145

146+
public static Builder withMachineSecretKey(String machineSecretKey) {
147+
Utils.checkNotNull(machineSecretKey, "machineSecretKey");
148+
Builder builder = new Builder();
149+
builder.machineSecretKey = Optional.of(machineSecretKey);
150+
return builder;
151+
}
152+
134153
public Builder audience(String audience) {
135154
Utils.checkNotNull(audience, "audience");
136155
return audience(Optional.of(audience));
@@ -194,6 +213,7 @@ public Builder apiVersion(Optional<String> apiVersion) {
194213
public VerifyTokenOptions build() {
195214
return new VerifyTokenOptions(secretKey,
196215
jwtKey,
216+
machineSecretKey,
197217
audience,
198218
authorizedParties,
199219
Optional.of(clockSkewInMs),

src/main/java/com/clerk/backend_api/helpers/security/token_verifiers/impl/MachineTokenVerifier.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public TokenVerificationResponse<MachineAuthVerificationData> verify(String toke
4141
HttpRequest request = HttpRequest.newBuilder()
4242
.uri(URI.create(options.apiUrl() + MACHINE_TOKEN_VERIFICATION_URL))
4343
.header("Content-Type", "application/json")
44-
.header("Authorization", "Bearer " + options.secretKey().get())
44+
.header("Authorization", "Bearer " + ( options.secretKey().isPresent() ? options.secretKey().get() : options.machineSecretKey().get()))
4545
.timeout(Duration.ofSeconds(30))
4646
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
4747
.build();

src/test/java/com/clerk/backend_api/helpers/security/token_verifiers/impl/MachineTokenVerifierTests.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
import static org.mockito.Mockito.any;
2222
import static org.mockito.Mockito.mock;
2323
import static org.mockito.Mockito.mockStatic;
24+
import static org.mockito.Mockito.verify;
2425
import static org.mockito.Mockito.when;
26+
import static org.mockito.ArgumentMatchers.argThat;
2527

2628
class MachineTokenVerifierTests {
2729

@@ -70,6 +72,62 @@ void verify_shouldReturnValidResponse_whenHttpStatusIs200() throws Exception {
7072
}
7173
}
7274

75+
@Test
76+
void verify_shouldUseSecretKeyInAuthorizationHeader_whenSecretKeyIsPresent() throws Exception {
77+
String token = "mt_test_token";
78+
String secretKey = "sk_test_secret_key";
79+
80+
HttpClient mockClient = mock(HttpClient.class);
81+
HttpResponse<String> mockResponse = mock(HttpResponse.class);
82+
83+
when(mockResponse.statusCode()).thenReturn(200);
84+
when(mockResponse.body()).thenReturn(getMockResponse());
85+
when(mockClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandler.class)))
86+
.thenReturn(mockResponse);
87+
88+
try (MockedStatic<HttpClient> staticHttpClient = mockStatic(HttpClient.class)) {
89+
staticHttpClient.when(HttpClient::newHttpClient).thenReturn(mockClient);
90+
91+
MachineTokenVerifier verifier = new MachineTokenVerifier();
92+
VerifyTokenOptions options = VerifyTokenOptions.secretKey(secretKey).build();
93+
94+
verifier.verify(token, options);
95+
96+
// Verify the authorization header contains the secret key
97+
verify(mockClient).send(argThat(request -> request.headers().firstValue("Authorization")
98+
.map(auth -> auth.equals("Bearer " + secretKey))
99+
.orElse(false)), any(HttpResponse.BodyHandler.class));
100+
}
101+
}
102+
103+
@Test
104+
void verify_shouldUseMachineSecretKeyInAuthorizationHeader_whenMachineSecretKeyIsPresent() throws Exception {
105+
String token = "mt_test_token";
106+
String machineSecretKey = "msk_test_machine_secret_key";
107+
108+
HttpClient mockClient = mock(HttpClient.class);
109+
HttpResponse<String> mockResponse = mock(HttpResponse.class);
110+
111+
when(mockResponse.statusCode()).thenReturn(200);
112+
when(mockResponse.body()).thenReturn(getMockResponse());
113+
when(mockClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandler.class)))
114+
.thenReturn(mockResponse);
115+
116+
try (MockedStatic<HttpClient> staticHttpClient = mockStatic(HttpClient.class)) {
117+
staticHttpClient.when(HttpClient::newHttpClient).thenReturn(mockClient);
118+
119+
MachineTokenVerifier verifier = new MachineTokenVerifier();
120+
VerifyTokenOptions options = VerifyTokenOptions.machineSecretKey(machineSecretKey).build();
121+
122+
verifier.verify(token, options);
123+
124+
// Verify the authorization header contains the machine secret key
125+
verify(mockClient).send(argThat(request -> request.headers().firstValue("Authorization")
126+
.map(auth -> auth.equals("Bearer " + machineSecretKey))
127+
.orElse(false)), any(HttpResponse.BodyHandler.class));
128+
}
129+
}
130+
73131
@Test
74132
void verify_shouldThrowTokenVerificationException_whenHttpStatusIsNot200() throws Exception {
75133
HttpClient mockClient = mock(HttpClient.class);

0 commit comments

Comments
 (0)