diff --git a/frontend-new/src/auth/services/Authentication.service.test.ts b/frontend-new/src/auth/services/Authentication.service.test.ts index 261c323e8..8ad484875 100644 --- a/frontend-new/src/auth/services/Authentication.service.test.ts +++ b/frontend-new/src/auth/services/Authentication.service.test.ts @@ -480,35 +480,55 @@ describe("AuthenticationService", () => { expect(console.warn).not.toHaveBeenCalled(); }); - test.each([ - [ - "expired token", - { iat: currentTime - CLOCK_TOLERANCE - 100, exp: currentTime - CLOCK_TOLERANCE - 1 }, - "TOKEN_EXPIRED", - ], - [ - "future token", - { iat: currentTime + CLOCK_TOLERANCE + 1, exp: currentTime + CLOCK_TOLERANCE + 100 }, - "TOKEN_NOT_YET_VALID", - ], - ])("should return false for %s", (_, payload, expectedFailureCause) => { - // GIVEN a token with invalid timing - const givenToken = "invalid-token"; + test("should return false for expired token", () => { + // GIVEN an expired token + const givenToken = "expired-token"; (jwtDecode as jest.Mock) .mockReturnValueOnce({ typ: "JWT", alg: "RS256", kid: "key-id", } as TokenHeader) - .mockReturnValueOnce(payload as Token); + .mockReturnValueOnce({ + iat: currentTime - CLOCK_TOLERANCE - 100, + exp: currentTime - CLOCK_TOLERANCE - 1, + } as Token); // WHEN isTokenValid is called const result = service.isTokenValid(givenToken); - // THEN it should return false + // THEN it should return false with TOKEN_EXPIRED expect(result.isValid).toBe(false); expect(result.decodedToken).toBeNull(); - expect(result.failureCause).toBe(expectedFailureCause); + expect(result.failureCause).toBe(TokenValidationFailureCause.TOKEN_EXPIRED); + + // AND expect no errors or warning to have occurred + expect(console.error).not.toHaveBeenCalled(); + expect(console.warn).not.toHaveBeenCalled(); + }); + + test("should return true when iat is in the future due to client clock skew", () => { + // GIVEN a token where the server clock is ahead of the client (iat appears to be in the future) + // but the token has not yet expired + const givenToken = "clock-skewed-token"; + (jwtDecode as jest.Mock) + .mockReturnValueOnce({ + typ: "JWT", + alg: "RS256", + kid: "key-id", + } as TokenHeader) + .mockReturnValueOnce({ + iat: currentTime + CLOCK_TOLERANCE + 1, + exp: currentTime + CLOCK_TOLERANCE + 100, + } as Token); + + // WHEN isTokenValid is called + const result = service.isTokenValid(givenToken); + + // THEN it should return true — iat is not checked client-side + expect(result.isValid).toBe(true); + expect(result.decodedToken).toBeDefined(); + expect(result.failureCause).toBeUndefined(); // AND expect no errors or warning to have occurred expect(console.error).not.toHaveBeenCalled(); diff --git a/frontend-new/src/auth/services/Authentication.service.ts b/frontend-new/src/auth/services/Authentication.service.ts index 4f9134cca..e384dce49 100644 --- a/frontend-new/src/auth/services/Authentication.service.ts +++ b/frontend-new/src/auth/services/Authentication.service.ts @@ -14,7 +14,6 @@ export const CLOCK_TOLERANCE = 10; // 10 second buffer for expiration and issuan export enum TokenValidationFailureCause { TOKEN_EXPIRED = "TOKEN_EXPIRED", - TOKEN_NOT_YET_VALID = "TOKEN_NOT_YET_VALID", TOKEN_NOT_A_JWT = "TOKEN_NOT_A_JWT", TOKEN_NOT_SIGNED = "TOKEN_NOT_SIGNED", TOKEN_DOES_NOT_HAVE_A_KEY_ID = "TOKEN_DOES_NOT_HAVE_A_KEY_ID", @@ -218,20 +217,6 @@ abstract class AuthenticationService { ); } - // Check issued time with buffer - // similarly, we add a buffer to the issuance time to account for the potential time difference between the server and client clocks - if (currentTime < decodedToken.iat - CLOCK_TOLERANCE) { - console.debug("Token issued in the future: ", { iat: decodedToken.iat, currentTime }); - return { isValid: false, decodedToken: null, failureCause: TokenValidationFailureCause.TOKEN_NOT_YET_VALID }; - } else if (currentTime < decodedToken.iat) { - console.warn( - "Warning: token issued at time was after the current time, but within the acceptable tolerance period ", - { - iat: decodedToken.iat, - currentTime, - } - ); - } return { isValid: true, decodedToken: decodedToken }; } catch (error) { return { isValid: false, decodedToken: null, failureCause: TokenValidationFailureCause.ERROR_DECODING_TOKEN };