Skip to content

Commit 80f88e4

Browse files
authored
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.
1 parent 9748dd9 commit 80f88e4

File tree

2 files changed

+35
-18
lines changed

2 files changed

+35
-18
lines changed

src/GoTrueClient.ts

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import GoTrueAdminApi from './GoTrueAdminApi'
2-
import { DEFAULT_HEADERS, EXPIRY_MARGIN, GOTRUE_URL, STORAGE_KEY } from './lib/constants'
2+
import {
3+
DEFAULT_HEADERS,
4+
EXPIRY_MARGIN_MS,
5+
AUTO_REFRESH_TICK_DURATION_MS,
6+
AUTO_REFRESH_TICK_THRESHOLD,
7+
GOTRUE_URL,
8+
STORAGE_KEY,
9+
} from './lib/constants'
310
import {
411
AuthError,
512
AuthImplicitGrantRedirectError,
@@ -109,13 +116,6 @@ const DEFAULT_OPTIONS: Omit<Required<GoTrueClientOptions>, 'fetch' | 'storage' |
109116
hasCustomAuthorizationHeader: false,
110117
}
111118

112-
/** Current session will be checked for refresh at this interval. */
113-
const AUTO_REFRESH_TICK_DURATION = 30 * 1000
114-
115-
/**
116-
* A token refresh will be attempted this many ticks before the current session expires. */
117-
const AUTO_REFRESH_TICK_THRESHOLD = 3
118-
119119
async function lockNoOp<R>(name: string, acquireTimeout: number, fn: () => Promise<R>): Promise<R> {
120120
return await fn()
121121
}
@@ -1107,8 +1107,13 @@ export default class GoTrueClient {
11071107
return { data: { session: null }, error: null }
11081108
}
11091109

1110+
// A session is considered expired before the access token _actually_
1111+
// expires. When the autoRefreshToken option is off (or when the tab is
1112+
// in the background), very eager users of getSession() -- like
1113+
// realtime-js -- might send a valid JWT which will expire by the time it
1114+
// reaches the server.
11101115
const hasExpired = currentSession.expires_at
1111-
? currentSession.expires_at <= Date.now() / 1000
1116+
? currentSession.expires_at * 1000 - Date.now() < EXPIRY_MARGIN_MS
11121117
: false
11131118

11141119
this._debug(
@@ -1503,7 +1508,7 @@ export default class GoTrueClient {
15031508
}
15041509

15051510
const actuallyExpiresIn = expiresAt - timeNow
1506-
if (actuallyExpiresIn * 1000 <= AUTO_REFRESH_TICK_DURATION) {
1511+
if (actuallyExpiresIn * 1000 <= AUTO_REFRESH_TICK_DURATION_MS) {
15071512
console.warn(
15081513
`@supabase/gotrue-js: Session as retrieved from URL expires in ${actuallyExpiresIn}s, should have been closer to ${expiresIn}s`
15091514
)
@@ -1850,7 +1855,7 @@ export default class GoTrueClient {
18501855
error &&
18511856
isAuthRetryableFetchError(error) &&
18521857
// retryable only if the request can be sent before the backoff overflows the tick duration
1853-
Date.now() + nextBackOffInterval - startedAt < AUTO_REFRESH_TICK_DURATION
1858+
Date.now() + nextBackOffInterval - startedAt < AUTO_REFRESH_TICK_DURATION_MS
18541859
)
18551860
}
18561861
)
@@ -1923,12 +1928,12 @@ export default class GoTrueClient {
19231928
return
19241929
}
19251930

1926-
const timeNow = Math.round(Date.now() / 1000)
1927-
const expiresWithMargin = (currentSession.expires_at ?? Infinity) < timeNow + EXPIRY_MARGIN
1931+
const expiresWithMargin =
1932+
(currentSession.expires_at ?? Infinity) * 1000 - Date.now() < EXPIRY_MARGIN_MS
19281933

19291934
this._debug(
19301935
debugName,
1931-
`session has${expiresWithMargin ? '' : ' not'} expired with margin of ${EXPIRY_MARGIN}s`
1936+
`session has${expiresWithMargin ? '' : ' not'} expired with margin of ${EXPIRY_MARGIN_MS}s`
19321937
)
19331938

19341939
if (expiresWithMargin) {
@@ -2101,7 +2106,7 @@ export default class GoTrueClient {
21012106

21022107
this._debug('#_startAutoRefresh()')
21032108

2104-
const ticker = setInterval(() => this._autoRefreshTokenTick(), AUTO_REFRESH_TICK_DURATION)
2109+
const ticker = setInterval(() => this._autoRefreshTokenTick(), AUTO_REFRESH_TICK_DURATION_MS)
21052110
this.autoRefreshTicker = ticker
21062111

21072112
if (ticker && typeof ticker === 'object' && typeof ticker.unref === 'function') {
@@ -2208,12 +2213,12 @@ export default class GoTrueClient {
22082213

22092214
// session will expire in this many ticks (or has already expired if <= 0)
22102215
const expiresInTicks = Math.floor(
2211-
(session.expires_at * 1000 - now) / AUTO_REFRESH_TICK_DURATION
2216+
(session.expires_at * 1000 - now) / AUTO_REFRESH_TICK_DURATION_MS
22122217
)
22132218

22142219
this._debug(
22152220
'#_autoRefreshTokenTick()',
2216-
`access token expires in ${expiresInTicks} ticks, a tick lasts ${AUTO_REFRESH_TICK_DURATION}ms, refresh threshold is ${AUTO_REFRESH_TICK_THRESHOLD} ticks`
2221+
`access token expires in ${expiresInTicks} ticks, a tick lasts ${AUTO_REFRESH_TICK_DURATION_MS}ms, refresh threshold is ${AUTO_REFRESH_TICK_THRESHOLD} ticks`
22172222
)
22182223

22192224
if (expiresInTicks <= AUTO_REFRESH_TICK_THRESHOLD) {

src/lib/constants.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
11
import { version } from './version'
2+
3+
/** Current session will be checked for refresh at this interval. */
4+
export const AUTO_REFRESH_TICK_DURATION_MS = 30 * 1000
5+
6+
/**
7+
* A token refresh will be attempted this many ticks before the current session expires. */
8+
export const AUTO_REFRESH_TICK_THRESHOLD = 3
9+
10+
/*
11+
* Earliest time before an access token expires that the session should be refreshed.
12+
*/
13+
export const EXPIRY_MARGIN_MS = AUTO_REFRESH_TICK_THRESHOLD * AUTO_REFRESH_TICK_DURATION_MS
14+
215
export const GOTRUE_URL = 'http://localhost:9999'
316
export const STORAGE_KEY = 'supabase.auth.token'
417
export const AUDIENCE = ''
518
export const DEFAULT_HEADERS = { 'X-Client-Info': `gotrue-js/${version}` }
6-
export const EXPIRY_MARGIN = 10 // in seconds
719
export const NETWORK_FAILURE = {
820
MAX_RETRIES: 10,
921
RETRY_INTERVAL: 2, // in deciseconds

0 commit comments

Comments
 (0)