From c436dc56e909a97e6ce013bb7415ed7d0f7a281f Mon Sep 17 00:00:00 2001 From: Johannes Becker Date: Wed, 25 Nov 2020 18:32:00 +0100 Subject: [PATCH 1/2] feat: refresh keycloak access token until refresh token expires --- cypress/fixtures/auth.token.json | 4 +- cypress/integration/tokens.spec.ts | 110 ++++++++++++++++++ cypress/support/commands.ts | 6 +- src/components/app/app.tsx | 31 ++--- src/components/auth/auth.ts | 88 ++++++++++++++ src/components/logout/logout.ts | 2 + src/components/registration/autoLogin.ts | 10 +- .../accessSessionLocalStorage.ts | 41 +++++++ .../refreshKeycloakAccessToken.ts | 35 ++++++ 9 files changed, 302 insertions(+), 25 deletions(-) create mode 100644 cypress/integration/tokens.spec.ts create mode 100644 src/components/auth/auth.ts create mode 100644 src/components/sessionCookie/accessSessionLocalStorage.ts create mode 100644 src/components/sessionCookie/refreshKeycloakAccessToken.ts diff --git a/cypress/fixtures/auth.token.json b/cypress/fixtures/auth.token.json index cb20356ac..08ea815cd 100644 --- a/cypress/fixtures/auth.token.json +++ b/cypress/fixtures/auth.token.json @@ -1,7 +1,7 @@ { "access_token": "access_token", - "expires_in": 43200000, - "refresh_expires_in": 900, + "expires_in": 600, + "refresh_expires_in": 1800, "refresh_token": "refresh_token", "token_type": "bearer", "not-before-policy": 0, diff --git a/cypress/integration/tokens.spec.ts b/cypress/integration/tokens.spec.ts new file mode 100644 index 000000000..35c8cc0f7 --- /dev/null +++ b/cypress/integration/tokens.spec.ts @@ -0,0 +1,110 @@ +import { RENEW_BEFORE_EXPIRY_TIME } from '../../src/components/auth/auth'; +import { getTokenExpiryFromLocalStorage } from '../../src/components/sessionCookie/accessSessionLocalStorage'; + +const waitForTokenProcessing = () => { + // TODO: don't arbitrarily wait for token to be processed, find some + // way to cleanly determine that the token was processed instead. + // possible canidates are spying on `localStorage` or `setTimeout` + return cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting +}; + +describe('Keycloak Tokens', () => { + let authTokenJson; + before(() => { + cy.fixture('auth.token.json').then((fixture) => { + authTokenJson = fixture; + }); + }); + + it('should get and store tokens and expiry time on login', () => { + cy.caritasMockedLogin(); + + cy.get('#appRoot').then(() => { + cy.getCookie('keycloak').should('exist'); + cy.getCookie('refreshToken').should('exist'); + + const tokenExpiry = getTokenExpiryFromLocalStorage(); + expect(tokenExpiry.validUntil).to.exist; + expect(tokenExpiry.refreshValidUntil).to.exist; + }); + }); + + it('should keep refreshing access token before it expires', () => { + cy.clock(); + cy.caritasMockedLogin(); + + for (let check = 0; check < 3; check++) { + waitForTokenProcessing(); + + cy.tick(authTokenJson.expires_in * 1000 - RENEW_BEFORE_EXPIRY_TIME); + cy.wait('@authToken').then((interception) => { + expect(interception.request.body).to.include( + 'grant_type=refresh_token' + ); + }); + } + }); + + it('should refresh the access token if its expired when loading the app', () => { + cy.clock(); + cy.caritasMockedLogin(); + + cy.clock().then((clock) => { + clock.restore(); + }); + cy.clock(authTokenJson.expires_in * 1000 + 1); + cy.reload(); + cy.get('#appRoot'); + + cy.wait('@authToken').then((interception) => { + expect(interception.request.body).to.include( + 'grant_type=refresh_token' + ); + }); + + cy.tick(1000); // logout() call uses setTimeout + cy.get('#appRoot').should('exist'); + }); + + it('should logout if refresh token is already expired when loading the app', () => { + cy.clock(); + cy.caritasMockedLogin(); + + cy.clock().then((clock) => { + clock.restore(); + }); + cy.clock(authTokenJson.refresh_expires_in * 1000 + 1); + cy.reload(); + cy.get('#appRoot'); + waitForTokenProcessing(); + + cy.tick(1000); // logout() call uses setTimeout + cy.get('#loginRoot').should('exist'); + }); + + it('should logout if refresh token is expired while the app is loaded', () => { + cy.clock(); + cy.caritasMockedLogin(); + waitForTokenProcessing(); + + cy.tick(authTokenJson.refresh_expires_in * 1000 + 1); + waitForTokenProcessing(); + + cy.tick(1000); // logout() call uses setTimeout + cy.get('#loginRoot').should('exist'); + }); + + it('should not logout if refresh token is expired but access token is still valid', () => { + cy.clock(); + cy.caritasMockedLogin({ + auth: { expires_in: 1800, refresh_expires_in: 600 } + }); + + waitForTokenProcessing(); + cy.tick(600 * 1000); + waitForTokenProcessing(); + + cy.tick(1000); // logout() call uses setTimeout + cy.get('#loginRoot').should('not.exist'); + }); +}); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 41824d635..7e0a97852 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -24,8 +24,8 @@ afterEach(() => { // - https://github.com/cypress-io/cypress/issues/9170 // - https://github.com/cypress-io/cypress/issues/9362 // - https://github.com/cypress-io/cypress/issues/8926 - cy.clearCookies(); cy.window().then((win) => (win.location.href = 'about:blank')); + cy.clearCookies(); }); Cypress.Commands.add( @@ -81,7 +81,7 @@ Cypress.Commands.add( cy.intercept('GET', config.endpoints.userSessions, { fixture: 'service.users.sessions.askers.json' }); - cy.intercept('GET', config.endpoints.liveservice, { + cy.intercept('GET', `${config.endpoints.liveservice}/**/*`, { fixture: 'service.live.info.json' }); cy.intercept('POST', config.endpoints.rocketchatAccessToken, { @@ -102,6 +102,6 @@ Cypress.Commands.add( }); cy.get('.button__primary').click(); cy.wait('@authToken'); - cy.get('#appRoot'); + cy.get('#appRoot').should('exist'); } ); diff --git a/src/components/app/app.tsx b/src/components/app/app.tsx index 2f360ff10..43a68eaea 100644 --- a/src/components/app/app.tsx +++ b/src/components/app/app.tsx @@ -22,6 +22,7 @@ import { import { ContextProvider } from '../../globalState/state'; import { getUserData } from '../apiWrapper'; import { Loading } from './Loading'; +import { handleTokenRefresh } from '../auth/auth'; import { logout } from '../logout/logout'; import '../../resources/styles/styles'; import './app.styles'; @@ -75,20 +76,22 @@ export const App: React.FC = () => { if (!userDataRequested) { setUserDataRequested(true); - getUserData() - .then((userProfileData: UserDataInterface) => { - // set informal / formal cookie depending on the given userdata - setTokenInCookie( - 'useInformal', - !userProfileData.formalLanguage ? '1' : '' - ); - setUserData(userProfileData); - setAppReady(true); - }) - .catch((error) => { - window.location.href = config.endpoints.logoutRedirect; - console.log(error); - }); + handleTokenRefresh().then(() => { + getUserData() + .then((userProfileData: UserDataInterface) => { + // set informal / formal cookie depending on the given userdata + setTokenInCookie( + 'useInformal', + !userProfileData.formalLanguage ? '1' : '' + ); + setUserData(userProfileData); + setAppReady(true); + }) + .catch((error) => { + window.location.href = config.endpoints.logoutRedirect; + console.log(error); + }); + }); } useEffect(() => { diff --git a/src/components/auth/auth.ts b/src/components/auth/auth.ts new file mode 100644 index 000000000..73ae46fec --- /dev/null +++ b/src/components/auth/auth.ts @@ -0,0 +1,88 @@ +import { logout } from '../logout/logout'; +import { LoginData } from '../registration/autoLogin'; +import { setTokenInCookie } from '../sessionCookie/accessSessionCookie'; +import { + getTokenExpiryFromLocalStorage, + setAccessTokenExpiryInLocalStorage, + setRefreshTokenExpiryInLocalStorage +} from '../sessionCookie/accessSessionLocalStorage'; +import { refreshKeycloakAccessToken } from '../sessionCookie/refreshKeycloakAccessToken'; + +export const RENEW_BEFORE_EXPIRY_TIME = 10 * 1000; // seconds + +export const setTokens = (data: LoginData) => { + if (data.access_token) { + setTokenInCookie('keycloak', data.access_token); + setAccessTokenExpiryInLocalStorage(data.expires_in); + } + if (data.refresh_token) { + setTokenInCookie('refreshToken', data.refresh_token); + setRefreshTokenExpiryInLocalStorage(data.refresh_expires_in); + } +}; + +const refreshTokens = (): Promise => { + const currentTime = new Date().getTime(); + const tokenExpiry = getTokenExpiryFromLocalStorage(); + + if ( + tokenExpiry.refreshValidUntil <= + currentTime - RENEW_BEFORE_EXPIRY_TIME + ) { + logout(true); + return Promise.resolve(); + } + + return refreshKeycloakAccessToken().then((response) => { + setTokens(response); + }); +}; + +const startTimers = ( + tokenValidMs: number, + tokenRefreshExpiredTimeout: number +) => { + const tokenRefreshAccessTokenInterval = + tokenValidMs - RENEW_BEFORE_EXPIRY_TIME; + + let refreshInterval; + if (tokenRefreshAccessTokenInterval > 0) { + refreshInterval = window.setInterval(() => { + refreshTokens(); + }, tokenRefreshAccessTokenInterval); + } + + if (tokenValidMs <= tokenRefreshExpiredTimeout) { + window.setTimeout(() => { + if (refreshInterval) { + window.clearInterval(refreshInterval); + } + + logout(true); + }, tokenRefreshExpiredTimeout); + } +}; + +export const handleTokenRefresh = (): Promise => { + return new Promise((resolve) => { + const currentTime = new Date().getTime(); + const tokenExpiry = getTokenExpiryFromLocalStorage(); + const tokenValidMs = tokenExpiry.validUntil - currentTime; + + const tokenRefreshExpiredTimeout = + tokenExpiry.refreshValidUntil - currentTime; + + if (tokenRefreshExpiredTimeout <= 0) { + logout(true); + resolve(); + } else if (tokenValidMs <= 0) { + refreshTokens().then(() => { + startTimers(tokenValidMs, tokenRefreshExpiredTimeout); + resolve(); + }); + } else { + startTimers(tokenValidMs, tokenRefreshExpiredTimeout); + resolve(); + } + }); +}; diff --git a/src/components/logout/logout.ts b/src/components/logout/logout.ts index 6459ee2ea..18db22176 100644 --- a/src/components/logout/logout.ts +++ b/src/components/logout/logout.ts @@ -2,6 +2,7 @@ import { config } from '../../resources/scripts/config'; import { removeAllCookies } from '../sessionCookie/accessSessionCookie'; import { rocketchatLogout } from '../apiWrapper'; import { keycloakLogout } from '../apiWrapper'; +import { removeTokenExpiryFromLocalStorage } from '../sessionCookie/accessSessionLocalStorage'; let isRequestInProgress = false; export const logout = (withRedirect: boolean = true, redirectUrl?: string) => { @@ -29,6 +30,7 @@ const invalidateCookies = ( redirectUrl?: string ) => { removeAllCookies(); + removeTokenExpiryFromLocalStorage(); if (withRedirect) { redirectAfterLogout(redirectUrl); } diff --git a/src/components/registration/autoLogin.ts b/src/components/registration/autoLogin.ts index 2b421b24d..1d3c4d78f 100644 --- a/src/components/registration/autoLogin.ts +++ b/src/components/registration/autoLogin.ts @@ -4,6 +4,7 @@ import { setTokenInCookie } from '../sessionCookie/accessSessionCookie'; import { config } from '../../resources/scripts/config'; import { generateCsrfToken } from '../../resources/scripts/helpers/generateCsrfToken'; import { encodeUsername } from '../../resources/scripts/helpers/encryptionHelpers'; +import { setTokens } from '../auth/auth'; export interface LoginData { data: { @@ -11,7 +12,9 @@ export interface LoginData { userId?: string; }; access_token?: string; + expires_in?: number; refresh_token?: string; + refresh_expires_in?: number; } export const autoLogin = ( @@ -27,12 +30,7 @@ export const autoLogin = ( encodeURIComponent(password) ) .then((response) => { - if (response.access_token) { - setTokenInCookie('keycloak', response.access_token); - } - if (response.refresh_token) { - setTokenInCookie('refreshToken', response.refresh_token); - } + setTokens(response); getRocketchatAccessToken(userHash, password) .then((response) => { diff --git a/src/components/sessionCookie/accessSessionLocalStorage.ts b/src/components/sessionCookie/accessSessionLocalStorage.ts new file mode 100644 index 000000000..dca8e5c12 --- /dev/null +++ b/src/components/sessionCookie/accessSessionLocalStorage.ts @@ -0,0 +1,41 @@ +export type LocalStorageKey = 'auth.valid_until' | 'auth.refresh_valid_until'; + +export const getLocalStorageItem = (key: LocalStorageKey): string => { + return localStorage.getItem(key); +}; + +export const setLocalStorageItem = ( + key: LocalStorageKey, + value: string +): void => { + localStorage.setItem(key, value); +}; + +export const removeLocalStorageItem = (key: LocalStorageKey): void => { + localStorage.removeItem(key); +}; + +export const setAccessTokenExpiryInLocalStorage = (expiresIn: number) => { + const validUntil = new Date().getTime() + expiresIn * 1000; + setLocalStorageItem('auth.valid_until', validUntil.toString()); +}; + +export const setRefreshTokenExpiryInLocalStorage = ( + refreshExpiresIn: number +) => { + const refreshValidUntil = new Date().getTime() + refreshExpiresIn * 1000; + setLocalStorageItem( + 'auth.refresh_valid_until', + refreshValidUntil.toString() + ); +}; + +export const getTokenExpiryFromLocalStorage = () => ({ + validUntil: parseInt(getLocalStorageItem('auth.valid_until')), + refreshValidUntil: parseInt(getLocalStorageItem('auth.refresh_valid_until')) +}); + +export const removeTokenExpiryFromLocalStorage = () => { + removeLocalStorageItem('auth.valid_until'); + removeLocalStorageItem('auth.refresh_valid_until'); +}; diff --git a/src/components/sessionCookie/refreshKeycloakAccessToken.ts b/src/components/sessionCookie/refreshKeycloakAccessToken.ts new file mode 100644 index 000000000..b9aaeb13a --- /dev/null +++ b/src/components/sessionCookie/refreshKeycloakAccessToken.ts @@ -0,0 +1,35 @@ +import { config } from '../../resources/scripts/config'; +import { LoginData } from '../registration/autoLogin'; +import { getTokenFromCookie } from './accessSessionCookie'; + +export const refreshKeycloakAccessToken = (): Promise => + new Promise((resolve, reject) => { + const refreshToken = getTokenFromCookie('refreshToken'); + const data = + 'refresh_token=' + + refreshToken + + '&client_id=app&grant_type=refresh_token'; + const url = config.endpoints.keycloakAccessToken; + + const req = new Request(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'cache-control': 'no-cache' + }, + body: data + }); + + fetch(req) + .then((response) => { + if (response.status === 200) { + const data = response.json(); + resolve(data); + } else if (response.status === 401) { + reject(new Error('keycloakLogin')); + } + }) + .catch((error) => { + reject(new Error('keycloakLogin')); + }); + }); From b68bcaab22dd682f03004054ee237631c200ce36 Mon Sep 17 00:00:00 2001 From: Johannes Becker Date: Wed, 2 Dec 2020 12:12:32 +0100 Subject: [PATCH 2/2] refactor(tokens): more expressive naming and DRY set localstorage method --- cypress/integration/tokens.spec.ts | 32 +++++++-- src/components/auth/auth.ts | 69 ++++++++++++------- .../accessSessionLocalStorage.ts | 40 +++++------ 3 files changed, 88 insertions(+), 53 deletions(-) diff --git a/cypress/integration/tokens.spec.ts b/cypress/integration/tokens.spec.ts index 35c8cc0f7..b32f21524 100644 --- a/cypress/integration/tokens.spec.ts +++ b/cypress/integration/tokens.spec.ts @@ -1,10 +1,10 @@ -import { RENEW_BEFORE_EXPIRY_TIME } from '../../src/components/auth/auth'; +import { RENEW_BEFORE_EXPIRY_IN_MS } from '../../src/components/auth/auth'; import { getTokenExpiryFromLocalStorage } from '../../src/components/sessionCookie/accessSessionLocalStorage'; const waitForTokenProcessing = () => { // TODO: don't arbitrarily wait for token to be processed, find some // way to cleanly determine that the token was processed instead. - // possible canidates are spying on `localStorage` or `setTimeout` + // possible candidates are spying on `localStorage` or `setTimeout` return cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting }; @@ -24,8 +24,8 @@ describe('Keycloak Tokens', () => { cy.getCookie('refreshToken').should('exist'); const tokenExpiry = getTokenExpiryFromLocalStorage(); - expect(tokenExpiry.validUntil).to.exist; - expect(tokenExpiry.refreshValidUntil).to.exist; + expect(tokenExpiry.accessTokenValidUntilTime).to.exist; + expect(tokenExpiry.refreshTokenValidUntilTime).to.exist; }); }); @@ -36,7 +36,9 @@ describe('Keycloak Tokens', () => { for (let check = 0; check < 3; check++) { waitForTokenProcessing(); - cy.tick(authTokenJson.expires_in * 1000 - RENEW_BEFORE_EXPIRY_TIME); + cy.tick( + authTokenJson.expires_in * 1000 - RENEW_BEFORE_EXPIRY_IN_MS + ); cy.wait('@authToken').then((interception) => { expect(interception.request.body).to.include( 'grant_type=refresh_token' @@ -107,4 +109,24 @@ describe('Keycloak Tokens', () => { cy.tick(1000); // logout() call uses setTimeout cy.get('#loginRoot').should('not.exist'); }); + + it('should not logout if refresh token is expired but access token is still valid when the app loads', () => { + const refreshExpiresIn = 600; + + cy.clock(); + cy.caritasMockedLogin({ + auth: { expires_in: 1800, refresh_expires_in: refreshExpiresIn } + }); + + cy.clock().then((clock) => { + clock.restore(); + }); + cy.clock(refreshExpiresIn * 1000 + 1); + cy.reload(); + cy.get('#appRoot'); + waitForTokenProcessing(); + + cy.tick(1000); // logout() call uses setTimeout + cy.get('#loginRoot').should('not.exist'); + }); }); diff --git a/src/components/auth/auth.ts b/src/components/auth/auth.ts index 73ae46fec..1b58648fb 100644 --- a/src/components/auth/auth.ts +++ b/src/components/auth/auth.ts @@ -3,21 +3,26 @@ import { LoginData } from '../registration/autoLogin'; import { setTokenInCookie } from '../sessionCookie/accessSessionCookie'; import { getTokenExpiryFromLocalStorage, - setAccessTokenExpiryInLocalStorage, - setRefreshTokenExpiryInLocalStorage + setTokenExpiryInLocalStorage } from '../sessionCookie/accessSessionLocalStorage'; import { refreshKeycloakAccessToken } from '../sessionCookie/refreshKeycloakAccessToken'; -export const RENEW_BEFORE_EXPIRY_TIME = 10 * 1000; // seconds +export const RENEW_BEFORE_EXPIRY_IN_MS = 10 * 1000; // seconds export const setTokens = (data: LoginData) => { if (data.access_token) { setTokenInCookie('keycloak', data.access_token); - setAccessTokenExpiryInLocalStorage(data.expires_in); + setTokenExpiryInLocalStorage( + 'auth.access_token_valid_until', + data.expires_in + ); } if (data.refresh_token) { setTokenInCookie('refreshToken', data.refresh_token); - setRefreshTokenExpiryInLocalStorage(data.refresh_expires_in); + setTokenExpiryInLocalStorage( + 'auth.refresh_token_valid_until', + data.refresh_expires_in + ); } }; @@ -26,8 +31,8 @@ const refreshTokens = (): Promise => { const tokenExpiry = getTokenExpiryFromLocalStorage(); if ( - tokenExpiry.refreshValidUntil <= - currentTime - RENEW_BEFORE_EXPIRY_TIME + tokenExpiry.refreshTokenValidUntilTime <= + currentTime - RENEW_BEFORE_EXPIRY_IN_MS ) { logout(true); return Promise.resolve(); @@ -38,28 +43,34 @@ const refreshTokens = (): Promise => { }); }; -const startTimers = ( - tokenValidMs: number, - tokenRefreshExpiredTimeout: number -) => { - const tokenRefreshAccessTokenInterval = - tokenValidMs - RENEW_BEFORE_EXPIRY_TIME; +const startTimers = ({ + accessTokenValidInMs, + refreshTokenValidInMs +}: { + accessTokenValidInMs: number; + refreshTokenValidInMs: number; +}) => { + const accessTokenRefreshIntervalInMs = + accessTokenValidInMs - RENEW_BEFORE_EXPIRY_IN_MS; let refreshInterval; - if (tokenRefreshAccessTokenInterval > 0) { + // just a sanity check so that we don't accidentally register an endless loop + if (accessTokenRefreshIntervalInMs > 0) { refreshInterval = window.setInterval(() => { refreshTokens(); - }, tokenRefreshAccessTokenInterval); + }, accessTokenRefreshIntervalInMs); } - if (tokenValidMs <= tokenRefreshExpiredTimeout) { + if (refreshTokenValidInMs > accessTokenValidInMs) { + // when refresh token is longer valid than access token we need to + // logout if the refresh token expires window.setTimeout(() => { if (refreshInterval) { window.clearInterval(refreshInterval); } logout(true); - }, tokenRefreshExpiredTimeout); + }, refreshTokenValidInMs); } }; @@ -67,21 +78,31 @@ export const handleTokenRefresh = (): Promise => { return new Promise((resolve) => { const currentTime = new Date().getTime(); const tokenExpiry = getTokenExpiryFromLocalStorage(); - const tokenValidMs = tokenExpiry.validUntil - currentTime; + const accessTokenValidInMs = + tokenExpiry.accessTokenValidUntilTime - currentTime; - const tokenRefreshExpiredTimeout = - tokenExpiry.refreshValidUntil - currentTime; + const refreshTokenValidInMs = + tokenExpiry.refreshTokenValidUntilTime - currentTime; - if (tokenRefreshExpiredTimeout <= 0) { + if (refreshTokenValidInMs <= 0 && accessTokenValidInMs <= 0) { + // access token and refresh token no longer valid, logout logout(true); resolve(); - } else if (tokenValidMs <= 0) { + } else if (accessTokenValidInMs <= 0) { + // access token no longer valid but refresh token still valid, refresh tokens refreshTokens().then(() => { - startTimers(tokenValidMs, tokenRefreshExpiredTimeout); + startTimers({ + accessTokenValidInMs, + refreshTokenValidInMs + }); resolve(); }); } else { - startTimers(tokenValidMs, tokenRefreshExpiredTimeout); + // access token and refresh token still valid, just start the timers + startTimers({ + accessTokenValidInMs, + refreshTokenValidInMs + }); resolve(); } }); diff --git a/src/components/sessionCookie/accessSessionLocalStorage.ts b/src/components/sessionCookie/accessSessionLocalStorage.ts index dca8e5c12..f3e0acc05 100644 --- a/src/components/sessionCookie/accessSessionLocalStorage.ts +++ b/src/components/sessionCookie/accessSessionLocalStorage.ts @@ -1,41 +1,33 @@ -export type LocalStorageKey = 'auth.valid_until' | 'auth.refresh_valid_until'; +export type LocalStorageKey = + | 'auth.access_token_valid_until' + | 'auth.refresh_token_valid_until'; export const getLocalStorageItem = (key: LocalStorageKey): string => { return localStorage.getItem(key); }; -export const setLocalStorageItem = ( - key: LocalStorageKey, - value: string -): void => { - localStorage.setItem(key, value); -}; - export const removeLocalStorageItem = (key: LocalStorageKey): void => { localStorage.removeItem(key); }; -export const setAccessTokenExpiryInLocalStorage = (expiresIn: number) => { - const validUntil = new Date().getTime() + expiresIn * 1000; - setLocalStorageItem('auth.valid_until', validUntil.toString()); -}; - -export const setRefreshTokenExpiryInLocalStorage = ( - refreshExpiresIn: number +export const setTokenExpiryInLocalStorage = ( + key: LocalStorageKey, + expiresInMs: number ) => { - const refreshValidUntil = new Date().getTime() + refreshExpiresIn * 1000; - setLocalStorageItem( - 'auth.refresh_valid_until', - refreshValidUntil.toString() - ); + const validUntilTime = new Date().getTime() + expiresInMs * 1000; + localStorage.setItem(key, validUntilTime.toString()); }; export const getTokenExpiryFromLocalStorage = () => ({ - validUntil: parseInt(getLocalStorageItem('auth.valid_until')), - refreshValidUntil: parseInt(getLocalStorageItem('auth.refresh_valid_until')) + accessTokenValidUntilTime: parseInt( + getLocalStorageItem('auth.access_token_valid_until') + ), + refreshTokenValidUntilTime: parseInt( + getLocalStorageItem('auth.refresh_token_valid_until') + ) }); export const removeTokenExpiryFromLocalStorage = () => { - removeLocalStorageItem('auth.valid_until'); - removeLocalStorageItem('auth.refresh_valid_until'); + removeLocalStorageItem('auth.access_token_valid_until'); + removeLocalStorageItem('auth.refresh_token_valid_until'); };