@@ -259,6 +259,36 @@ public void testDPoPByPublicClient() throws Exception {
259259 successTokenProceduresWithDPoP (dpopProofEcEncoded , jktEc , true , true );
260260 }
261261
262+ @ Test
263+ public void testDPoPByPublicClientClockSkew () throws Exception {
264+ getTestingClient ().testing ().setTestingInfinispanTimeService ();
265+ try {
266+ sendAuthorizationRequestWithDPoPJkt (null );
267+
268+ // get a DPoP proof 10 seconds in the future
269+ String dpopProofEcEncoded = generateSignedDPoPProof (UUID .randomUUID ().toString (), HttpMethod .POST , oauth .getEndpoints ().getToken (),
270+ (long ) (Time .currentTime () + 10 ), Algorithm .ES256 , jwsEcHeader , ecKeyPair .getPrivate (), null );
271+
272+ AccessTokenResponse response = successTokenProceduresWithDPoP (dpopProofEcEncoded , jktEc , true , true , false );
273+
274+ setTimeOffset (25 ); // 25 <= 10+10+15, proof not expired because clockSkew, detected by replay check
275+ response = oauth .refreshRequest (response .getRefreshToken ()).dpopProof (dpopProofEcEncoded ).send ();
276+ assertEquals (400 , response .getStatusCode ());
277+ assertEquals (OAuthErrorException .INVALID_REQUEST , response .getError ());
278+ assertEquals ("DPoP proof has already been used" , response .getErrorDescription ());
279+
280+ setTimeOffset (36 ); // 36 > 10+10+15, proof expired definitely
281+ response = oauth .refreshRequest (response .getRefreshToken ()).dpopProof (dpopProofEcEncoded ).send ();
282+ assertEquals (400 , response .getStatusCode ());
283+ assertEquals (response .getError (), OAuthErrorException .INVALID_REQUEST );
284+ assertEquals ("DPoP proof is not active" , response .getErrorDescription ());
285+
286+ oauth .logoutForm ().idTokenHint (response .getIdToken ()).open ();
287+ } finally {
288+ getTestingClient ().testing ().revertTestingInfinispanTimeService ();
289+ }
290+ }
291+
262292 @ Test
263293 public void testDPoPByPublicClientTokenRefreshWithoutDPoPProof () throws Exception {
264294 // use pre-computed EC key
@@ -1205,7 +1235,12 @@ private void sendAuthorizationRequestWithDPoPJkt(String dpopJkt) {
12051235 oauth .loginForm ().dpopJkt (dpopJkt ).doLogin (TEST_USER_NAME , TEST_USER_PASSWORD );
12061236 }
12071237
1208- private void successTokenProceduresWithDPoP (String dpopProofEncoded , String jkt , boolean accessTokenBound , boolean refreshTokenBound ) throws Exception {
1238+ private AccessTokenResponse successTokenProceduresWithDPoP (String dpopProofEncoded , String jkt , boolean accessTokenBound , boolean refreshTokenBound ) throws Exception {
1239+ return successTokenProceduresWithDPoP (dpopProofEncoded , jkt , accessTokenBound , refreshTokenBound , true );
1240+ }
1241+
1242+ private AccessTokenResponse successTokenProceduresWithDPoP (String dpopProofEncoded , String jkt , boolean accessTokenBound ,
1243+ boolean refreshTokenBound , boolean performLogout ) throws Exception {
12091244 String code = oauth .parseLoginResponse ().getCode ();
12101245 AccessTokenResponse response = oauth .accessTokenRequest (code ).dpopProof (dpopProofEncoded ).send ();
12111246 assertEquals (accessTokenBound ? TokenUtil .TOKEN_TYPE_DPOP : TokenUtil .TOKEN_TYPE_BEARER , response .getTokenType ());
@@ -1256,7 +1291,10 @@ private void successTokenProceduresWithDPoP(String dpopProofEncoded, String jkt,
12561291 }
12571292
12581293 // logout
1259- oauth .logoutForm ().idTokenHint (response .getIdToken ()).open ();
1294+ if (performLogout ) {
1295+ oauth .logoutForm ().idTokenHint (response .getIdToken ()).open ();
1296+ }
1297+ return response ;
12601298 }
12611299
12621300 private void failureRefreshTokenProceduresWithoutDPoP (String dpopProofEncoded , String jkt ) throws Exception {
0 commit comments