{"record":{"id":"827bec7b9f3dd6c9","repo":"dotnet/aspnetcore","slug":"authentication-refreshbeforeexpirationinmillisecon","errorCode":null,"errorMessage":"Authentication refreshBeforeExpirationInMilliseconds must be a finite number greater than or equal to 0.","messagePattern":"Authentication refreshBeforeExpirationInMilliseconds must be a finite number greater than or equal to 0\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/SignalR/clients/ts/signalr/src/HubConnection.ts","lineNumber":1033,"sourceCode":"                retryReason,\n            });\n        } catch (e) {\n            this._logger.log(LogLevel.Error, `IRetryPolicy.nextRetryDelayInMilliseconds(${previousRetryCount}, ${elapsedMilliseconds}) threw error '${e}'.`);\n            return null;\n        }\n    }\n\n    private _validateAuthenticationRefreshOptions(): void {\n        if (!this._authenticationRefreshOptions) {\n            return;\n        }\n\n        const refreshBeforeExpirationInMilliseconds = this._authenticationRefreshOptions.refreshBeforeExpirationInMilliseconds;\n        if (refreshBeforeExpirationInMilliseconds !== undefined &&\n            (typeof refreshBeforeExpirationInMilliseconds !== \"number\" ||\n                !Number.isFinite(refreshBeforeExpirationInMilliseconds) ||\n                refreshBeforeExpirationInMilliseconds < 0)) {\n            throw new Error(\"Authentication refreshBeforeExpirationInMilliseconds must be a finite number greater than or equal to 0.\");\n        }\n    }\n\n    private _scheduleAuthenticationRefreshIfNeeded(): void {\n        if (!this._isAutoAuthenticationRefreshEnabled()) {\n            return;\n        }\n\n        const authenticationRefreshFeature = this.connection.features.authenticationRefresh as IAuthenticationRefreshFeature | undefined;\n        const initialTokenLifetimeInSeconds = authenticationRefreshFeature?.initialTokenLifetimeInSeconds;\n        if (isValidAuthenticationTokenLifetime(initialTokenLifetimeInSeconds)) {\n            this._scheduleAuthenticationRefresh(initialTokenLifetimeInSeconds);\n        }\n    }\n\n    private _isAutoAuthenticationRefreshEnabled(): boolean {\n        return !!this._authenticationRefreshOptions && this._authenticationRefreshOptions.enableAutoRefresh !== false;\n    }","sourceCodeStart":1015,"sourceCodeEnd":1051,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/294cab2f9b2e03af6b953820c7ab497c3c8b7ad9/src/SignalR/clients/ts/signalr/src/HubConnection.ts#L1015-L1051","documentation":"Thrown at HubConnection.ts:1033 by _validateAuthenticationRefreshOptions, called from the HubConnection constructor (line 140). It validates the `refreshBeforeExpirationInMilliseconds` field of IAuthenticationRefreshOptions: it must be either undefined or a finite number >= 0. NaN, Infinity, negative numbers, or non-number types are rejected at construction time.","triggerScenarios":"Building a HubConnection via `HubConnectionBuilder.withAuthenticationRefresh({ refreshBeforeExpirationInMilliseconds: <bad> }).build()` where the value is negative, NaN, Infinity, a string, or null. The constructor runs the validation and throws before the connection is returned.","commonSituations":"Passing a value parsed from JSON/env as a string (e.g. \"300000\"); a math expression that produced NaN; setting Infinity expecting 'never expire'; sign error producing a negative; passing a value in seconds instead of milliseconds and ending up with a tiny or negative number after subtraction in user code.","solutions":["Pass a positive finite number in milliseconds: `withAuthenticationRefresh({ refreshBeforeExpirationInMilliseconds: 5 * 60 * 1000 })`.","If loading from env, parse and validate: `const n = Number(process.env.REFRESH_MS); if (!Number.isFinite(n) || n < 0) throw ...`.","Omit the field to use the default of 5 minutes.","Ensure you pass milliseconds, not seconds."],"exampleFix":"// before\nnew HubConnectionBuilder()\n  .withUrl(url)\n  .withAuthenticationRefresh({ refreshBeforeExpirationInMilliseconds: '300000' })\n  .build(); // throws\n\n// after\nnew HubConnectionBuilder()\n  .withUrl(url)\n  .withAuthenticationRefresh({ refreshBeforeExpirationInMilliseconds: 5 * 60 * 1000 })\n  .build();","handlingStrategy":"validation","validationCode":"function validateRefreshOpts(opts) {\n  const v = opts?.refreshBeforeExpirationInMilliseconds;\n  if (v !== undefined && (typeof v !== 'number' || !Number.isFinite(v) || v < 0)) {\n    throw new Error('refreshBeforeExpirationInMilliseconds must be a finite number >= 0');\n  }\n}\nvalidateRefreshOpts(refreshOptions);\nnew HubConnectionBuilder().withUrl(url).withAuthenticationRefresh(refreshOptions).build();","typeGuard":"function isValidRefreshBeforeMs(v: unknown): v is number | undefined {\n  return v === undefined || (typeof v === 'number' && Number.isFinite(v) && v >= 0);\n}","tryCatchPattern":"// thrown synchronously from build(); wrap construction\ntry { builder.withAuthenticationRefresh(opts).build(); }\ncatch (e) {\n  if (/refreshBeforeExpirationInMilliseconds/.test(String(e))) {\n    opts.refreshBeforeExpirationInMilliseconds = 5 * 60 * 1000;\n    return builder.withAuthenticationRefresh(opts).build();\n  }\n  throw e;\n}","preventionTips":["Always specify the value in milliseconds.","Coerce env/JSON values to Number and validate with Number.isFinite before passing.","Omit the field to accept the 5-minute default."],"tags":["authentication","refresh","validation","configuration","constructor"],"analyzedSha":"294cab2f9b2e03af6b953820c7ab497c3c8b7ad9","analyzedAt":"2026-08-06T20:08:02.189Z","schemaVersion":2},"datasetVersion":"2026-08-06T23:17:07.152Z"}