From 80f88e4bd2809db765a8d103954e827d8473b7db Mon Sep 17 00:00:00 2001 From: Stojan Dimitrovski Date: Tue, 21 Jan 2025 22:27:42 +0100 Subject: [PATCH 1/2] feat: consider session expired with margin on getSession() without auto refresh (#1027) When `autoRefreshToken` is off (or when a tab is in the background) but `getSession()` is called -- such as in an active Realtime channel, `getSession()` might return a JWT which will expire while the message is travelling over the internet. There is one confirmed case of this happening. This PR adjusts this using the established `EXPIRY_MARGIN_MS` constant (which only applies on initial initialization of the client). The constant's value is brought in line with the `autoRefreshToken` ticks which run every 30 seconds and refreshing is attempted 3 ticks prior to the session expiring. This means that JWTs with an expiry value **less than 90s** will always refresh the session; which is acceptable. --- src/GoTrueClient.ts | 39 ++++++++++++++++++++++----------------- src/lib/constants.ts | 14 +++++++++++++- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/GoTrueClient.ts b/src/GoTrueClient.ts index 0b7d5d0d2..74f5f20a3 100644 --- a/src/GoTrueClient.ts +++ b/src/GoTrueClient.ts @@ -1,5 +1,12 @@ import GoTrueAdminApi from './GoTrueAdminApi' -import { DEFAULT_HEADERS, EXPIRY_MARGIN, GOTRUE_URL, STORAGE_KEY } from './lib/constants' +import { + DEFAULT_HEADERS, + EXPIRY_MARGIN_MS, + AUTO_REFRESH_TICK_DURATION_MS, + AUTO_REFRESH_TICK_THRESHOLD, + GOTRUE_URL, + STORAGE_KEY, +} from './lib/constants' import { AuthError, AuthImplicitGrantRedirectError, @@ -109,13 +116,6 @@ const DEFAULT_OPTIONS: Omit, 'fetch' | 'storage' | hasCustomAuthorizationHeader: false, } -/** Current session will be checked for refresh at this interval. */ -const AUTO_REFRESH_TICK_DURATION = 30 * 1000 - -/** - * A token refresh will be attempted this many ticks before the current session expires. */ -const AUTO_REFRESH_TICK_THRESHOLD = 3 - async function lockNoOp(name: string, acquireTimeout: number, fn: () => Promise): Promise { return await fn() } @@ -1107,8 +1107,13 @@ export default class GoTrueClient { return { data: { session: null }, error: null } } + // A session is considered expired before the access token _actually_ + // expires. When the autoRefreshToken option is off (or when the tab is + // in the background), very eager users of getSession() -- like + // realtime-js -- might send a valid JWT which will expire by the time it + // reaches the server. const hasExpired = currentSession.expires_at - ? currentSession.expires_at <= Date.now() / 1000 + ? currentSession.expires_at * 1000 - Date.now() < EXPIRY_MARGIN_MS : false this._debug( @@ -1503,7 +1508,7 @@ export default class GoTrueClient { } const actuallyExpiresIn = expiresAt - timeNow - if (actuallyExpiresIn * 1000 <= AUTO_REFRESH_TICK_DURATION) { + if (actuallyExpiresIn * 1000 <= AUTO_REFRESH_TICK_DURATION_MS) { console.warn( `@supabase/gotrue-js: Session as retrieved from URL expires in ${actuallyExpiresIn}s, should have been closer to ${expiresIn}s` ) @@ -1850,7 +1855,7 @@ export default class GoTrueClient { error && isAuthRetryableFetchError(error) && // retryable only if the request can be sent before the backoff overflows the tick duration - Date.now() + nextBackOffInterval - startedAt < AUTO_REFRESH_TICK_DURATION + Date.now() + nextBackOffInterval - startedAt < AUTO_REFRESH_TICK_DURATION_MS ) } ) @@ -1923,12 +1928,12 @@ export default class GoTrueClient { return } - const timeNow = Math.round(Date.now() / 1000) - const expiresWithMargin = (currentSession.expires_at ?? Infinity) < timeNow + EXPIRY_MARGIN + const expiresWithMargin = + (currentSession.expires_at ?? Infinity) * 1000 - Date.now() < EXPIRY_MARGIN_MS this._debug( debugName, - `session has${expiresWithMargin ? '' : ' not'} expired with margin of ${EXPIRY_MARGIN}s` + `session has${expiresWithMargin ? '' : ' not'} expired with margin of ${EXPIRY_MARGIN_MS}s` ) if (expiresWithMargin) { @@ -2101,7 +2106,7 @@ export default class GoTrueClient { this._debug('#_startAutoRefresh()') - const ticker = setInterval(() => this._autoRefreshTokenTick(), AUTO_REFRESH_TICK_DURATION) + const ticker = setInterval(() => this._autoRefreshTokenTick(), AUTO_REFRESH_TICK_DURATION_MS) this.autoRefreshTicker = ticker if (ticker && typeof ticker === 'object' && typeof ticker.unref === 'function') { @@ -2208,12 +2213,12 @@ export default class GoTrueClient { // session will expire in this many ticks (or has already expired if <= 0) const expiresInTicks = Math.floor( - (session.expires_at * 1000 - now) / AUTO_REFRESH_TICK_DURATION + (session.expires_at * 1000 - now) / AUTO_REFRESH_TICK_DURATION_MS ) this._debug( '#_autoRefreshTokenTick()', - `access token expires in ${expiresInTicks} ticks, a tick lasts ${AUTO_REFRESH_TICK_DURATION}ms, refresh threshold is ${AUTO_REFRESH_TICK_THRESHOLD} ticks` + `access token expires in ${expiresInTicks} ticks, a tick lasts ${AUTO_REFRESH_TICK_DURATION_MS}ms, refresh threshold is ${AUTO_REFRESH_TICK_THRESHOLD} ticks` ) if (expiresInTicks <= AUTO_REFRESH_TICK_THRESHOLD) { diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 6e1b7fffa..92f285cb5 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,9 +1,21 @@ import { version } from './version' + +/** Current session will be checked for refresh at this interval. */ +export const AUTO_REFRESH_TICK_DURATION_MS = 30 * 1000 + +/** + * A token refresh will be attempted this many ticks before the current session expires. */ +export const AUTO_REFRESH_TICK_THRESHOLD = 3 + +/* + * Earliest time before an access token expires that the session should be refreshed. + */ +export const EXPIRY_MARGIN_MS = AUTO_REFRESH_TICK_THRESHOLD * AUTO_REFRESH_TICK_DURATION_MS + export const GOTRUE_URL = 'http://localhost:9999' export const STORAGE_KEY = 'supabase.auth.token' export const AUDIENCE = '' export const DEFAULT_HEADERS = { 'X-Client-Info': `gotrue-js/${version}` } -export const EXPIRY_MARGIN = 10 // in seconds export const NETWORK_FAILURE = { MAX_RETRIES: 10, RETRY_INTERVAL: 2, // in deciseconds From 510654bae10434f071751d2a0c6511017acdd570 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:10:47 +0100 Subject: [PATCH 2/2] chore(master): release 2.68.0 (#1013) :robot: I have created a release *beep* *boop* --- ## [2.68.0](https://github.com/supabase/auth-js/compare/v2.67.3...v2.68.0) (2025-01-21) ### Features * consider session expired with margin on getSession() without auto refresh ([#1027](https://github.com/supabase/auth-js/issues/1027)) ([80f88e4](https://github.com/supabase/auth-js/commit/80f88e4bd2809db765a8d103954e827d8473b7db)) ### Bug Fixes * remove `internal-types.ts` ([#1014](https://github.com/supabase/auth-js/issues/1014)) ([28ead89](https://github.com/supabase/auth-js/commit/28ead89af47bcdaccc6cc2f2c7f013bed8cf3d50)) * update docs to add scrypt ([#1012](https://github.com/supabase/auth-js/issues/1012)) ([1225239](https://github.com/supabase/auth-js/commit/1225239e239bde1b25037a88867d4c484caf8301)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f854f2c8c..d73077599 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [2.68.0](https://github.com/supabase/auth-js/compare/v2.67.3...v2.68.0) (2025-01-21) + + +### Features + +* consider session expired with margin on getSession() without auto refresh ([#1027](https://github.com/supabase/auth-js/issues/1027)) ([80f88e4](https://github.com/supabase/auth-js/commit/80f88e4bd2809db765a8d103954e827d8473b7db)) + + +### Bug Fixes + +* remove `internal-types.ts` ([#1014](https://github.com/supabase/auth-js/issues/1014)) ([28ead89](https://github.com/supabase/auth-js/commit/28ead89af47bcdaccc6cc2f2c7f013bed8cf3d50)) +* update docs to add scrypt ([#1012](https://github.com/supabase/auth-js/issues/1012)) ([1225239](https://github.com/supabase/auth-js/commit/1225239e239bde1b25037a88867d4c484caf8301)) + ## [2.67.3](https://github.com/supabase/auth-js/compare/v2.67.2...v2.67.3) (2024-12-17)