Skip to content

Commit 06d15db

Browse files
committed
validate issuer
1 parent 965ae2d commit 06d15db

File tree

2 files changed

+57
-5
lines changed

2 files changed

+57
-5
lines changed

src/main/java/uk/ac/cam/cl/dtg/segue/auth/MicrosoftAuthenticator.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ private DecodedJWT parseAndVerifyToken (String tokenStr) throws AuthenticatorSec
162162
var algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey());
163163
var verifier = JWT.require(algorithm)
164164
.withAudience(clientId)
165+
.withIssuer(String.format("https://login.microsoftonline.com/%s/v2.0", tenantId))
165166
.build();
166167
verifier.verify(tokenStr); // TODO: does this check validity of cert?
167168
if (null == keyId) {

src/test/java/uk/ac/cam/cl/dtg/segue/auth/MicrosoftAuthenticatorTest.java

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ void getUserInfo_validToken_returnsUserInformation() throws Throwable {
4646
.withNotBefore(oneHourAgo)
4747
.withExpiresAt(inOneHour)
4848
.withAudience(clientId)
49+
.withIssuer(expectedIssuer)
4950
.withPayload("{\"email\": \"test@example.com\"}"));
5051
var userInfo = testGetUserInfo(token);
5152
assertEquals("test@example.com", userInfo.getEmail());
@@ -58,11 +59,10 @@ void getUserInfo_nullToStore_throwsError() {
5859

5960
@Test
6061
void getUserInfo_noSuchToken_throwsError() throws MalformedURLException {
61-
var store = getStore();
6262
var subject = new MicrosoftAuthenticator(
6363
clientId, "", "", "http://localhost:8888/keys"
6464
) {{
65-
MicrosoftAuthenticator.credentialStore = store;
65+
MicrosoftAuthenticator.credentialStore = getStore();
6666
}};
6767
var error = assertThrows(AuthenticatorSecurityException.class, () -> subject.getUserInfo("no_token_for_id"));
6868
assertEquals("Token verification: TOKEN_MISSING", error.getMessage());
@@ -75,7 +75,8 @@ void getUserInfo_tokenSignatureNoKeyId_throwsError() {
7575
.withNotBefore(oneHourAgo)
7676
.withExpiresAt(inOneHour)
7777
.withAudience(clientId)
78-
.withKeyId((String) null));
78+
.withIssuer(expectedIssuer)
79+
.withKeyId(null));
7980
var error = assertThrows(AuthenticatorSecurityException.class, () -> testGetUserInfo(token));
8081
assertEquals("Token verification: NO_KEY_ID", error.getMessage());
8182
}
@@ -96,9 +97,11 @@ void getUserInfo_tokenSignatureMismatch_throwsError() {
9697

9798
@Test
9899
void getUserInfo_tokenNoExp_throwsError() {
99-
String token = signedToken(validSigningKey, t -> t.withAudience(clientId)
100+
String token = signedToken(validSigningKey, t -> t
100101
.withIssuedAt(oneHourAgo)
101102
.withNotBefore(oneHourAgo)
103+
.withAudience(clientId)
104+
.withIssuer(expectedIssuer)
102105
);
103106
var error = assertThrows(AuthenticatorSecurityException.class, () -> testGetUserInfo(token));
104107
assertEquals("Token verification: NULL_EXPIRY", error.getMessage());
@@ -110,6 +113,7 @@ void getUserInfo_tokenNullExp_throwsError() {
110113
.withIssuedAt(oneHourAgo)
111114
.withNotBefore(oneHourAgo)
112115
.withAudience(clientId)
116+
.withIssuer(expectedIssuer)
113117
.withExpiresAt((Date) null)
114118
);
115119
var error = assertThrows(AuthenticatorSecurityException.class, () -> testGetUserInfo(token));
@@ -122,6 +126,7 @@ void getUserInfo_tokenExpired_throwsError() {
122126
.withIssuedAt(oneHourAgo)
123127
.withNotBefore(oneHourAgo)
124128
.withAudience(clientId)
129+
.withIssuer(expectedIssuer)
125130
.withExpiresAt(oneHourAgo)
126131
);
127132
var error = assertThrows(TokenExpiredException.class, () -> testGetUserInfo(token));
@@ -134,6 +139,7 @@ void getUserInfo_tokenNoIat_throwsError() {
134139
.withExpiresAt(inOneHour)
135140
.withNotBefore(oneHourAgo)
136141
.withAudience(clientId)
142+
.withIssuer(expectedIssuer)
137143
);
138144
var error = assertThrows(AuthenticatorSecurityException.class, () -> testGetUserInfo(token));
139145
assertEquals("Token verification: NULL_ISSUED_AT", error.getMessage());
@@ -145,6 +151,7 @@ void getUserInfo_tokenNullIat_throwsError() {
145151
.withExpiresAt(inOneHour)
146152
.withNotBefore(oneHourAgo)
147153
.withAudience(clientId)
154+
.withIssuer(expectedIssuer)
148155
.withIssuedAt((Date) null)
149156
);
150157
var error = assertThrows(AuthenticatorSecurityException.class, () -> testGetUserInfo(token));
@@ -157,6 +164,7 @@ void getUserInfo_tokenIatFuture_throwsError() {
157164
.withExpiresAt(inOneHour)
158165
.withNotBefore(oneHourAgo)
159166
.withAudience(clientId)
167+
.withIssuer(expectedIssuer)
160168
.withIssuedAt(inOneHour)
161169
);
162170
var error = assertThrows(IncorrectClaimException.class, () -> testGetUserInfo(token));
@@ -169,6 +177,7 @@ void getUserInfo_tokenNoNbf_throwsError() {
169177
.withExpiresAt(inOneHour)
170178
.withIssuedAt(oneHourAgo)
171179
.withAudience(clientId)
180+
.withIssuer(expectedIssuer)
172181
);
173182
var error = assertThrows(AuthenticatorSecurityException.class, () -> testGetUserInfo(token));
174183
assertEquals("Token verification: NULL_NOT_BEFORE", error.getMessage());
@@ -180,6 +189,7 @@ void getUserInfo_tokenNullNbf_throwsError() {
180189
.withIssuedAt(oneHourAgo)
181190
.withExpiresAt(inOneHour)
182191
.withAudience(clientId)
192+
.withIssuer(expectedIssuer)
183193
.withNotBefore((Date) null)
184194
);
185195
var error = assertThrows(AuthenticatorSecurityException.class, () -> testGetUserInfo(token));
@@ -192,6 +202,7 @@ void getUserInfo_tokenNbfFuture_throwsError() {
192202
.withExpiresAt(inOneHour)
193203
.withIssuedAt(oneHourAgo)
194204
.withAudience(clientId)
205+
.withIssuer(expectedIssuer)
195206
.withNotBefore(inOneHour)
196207
);
197208
var error = assertThrows(IncorrectClaimException.class, () -> testGetUserInfo(token));
@@ -231,13 +242,51 @@ void getUserInfo_tokenAudIncorrect_throwsError() {
231242
var error = assertThrows(IncorrectClaimException.class, () -> testGetUserInfo(token));
232243
assertEquals("The Claim 'aud' value doesn't contain the required audience.", error.getMessage());
233244
}
245+
246+
@Test
247+
void getUserInfo_tokenNoIssuer_throwsError() {
248+
String token = signedToken(validSigningKey, t -> t
249+
.withIssuedAt(oneHourAgo)
250+
.withExpiresAt(inOneHour)
251+
.withNotBefore(oneHourAgo)
252+
.withAudience(clientId)
253+
);
254+
var error = assertThrows(MissingClaimException.class, () -> testGetUserInfo(token));
255+
assertEquals("The Claim 'iss' is not present in the JWT.", error.getMessage());
256+
}
257+
258+
@Test
259+
void getUserInfo_tokenIssuerIncorrrect_throwsError() {
260+
String token = signedToken(validSigningKey, t -> t
261+
.withIssuedAt(oneHourAgo)
262+
.withExpiresAt(inOneHour)
263+
.withNotBefore(oneHourAgo)
264+
.withAudience(clientId)
265+
.withIssuer(null)
266+
);
267+
var error = assertThrows(IncorrectClaimException.class, () -> testGetUserInfo(token));
268+
assertEquals("The Claim 'iss' value doesn't match the required issuer.", error.getMessage());
269+
}
270+
271+
@Test
272+
void getUserInfo_tokenNullIssuer_throwsError() {
273+
String token = signedToken(validSigningKey, t -> t
274+
.withIssuedAt(oneHourAgo)
275+
.withExpiresAt(inOneHour)
276+
.withNotBefore(oneHourAgo)
277+
.withAudience(clientId)
278+
.withIssuer("some_bad_issuer")
279+
);
280+
var error = assertThrows(IncorrectClaimException.class, () -> testGetUserInfo(token));
281+
assertEquals("The Claim 'iss' value doesn't match the required issuer.", error.getMessage());
282+
}
234283
}
235284

236285
class Helpers {
237286
static UserFromAuthProvider testGetUserInfo(String token) throws Throwable {
238287
var store = getStore();
239288
var subject = new MicrosoftAuthenticator(
240-
clientId, "", "", "http://localhost:8888/keys"
289+
clientId, tenantId, "", "http://localhost:8888/keys"
241290
) {{
242291
MicrosoftAuthenticator.credentialStore = store;
243292
}};
@@ -271,6 +320,8 @@ static Cache<String, String> getStore() {
271320
static Instant oneHourAgo = Instant.now().minusSeconds(60 * 60).truncatedTo(ChronoUnit.SECONDS);
272321
static Instant inOneHour = Instant.now().plusSeconds(60 * 60).truncatedTo(ChronoUnit.SECONDS);
273322
static String clientId = "the_client_id";
323+
static String tenantId = "common";
324+
static String expectedIssuer = "https://login.microsoftonline.com/common/v2.0";
274325
}
275326

276327
class TestKeyServer {

0 commit comments

Comments
 (0)