dotnet/aspnetcore · error · Error
Authentication refreshBeforeExpirationInMilliseconds must…
Error message
Authentication refreshBeforeExpirationInMilliseconds must be a finite number greater than or equal to 0.
What it means
Thrown by HubConnection._validateAuthenticationRefreshOptions when `refreshBeforeExpirationInMilliseconds` is set but is not a finite non-negative number. The guard rejects NaN, Infinity, negatives, and non-number values because they would produce an invalid refresh timer.
Solutions
- Parse the value to a Number and use Number.isFinite before assigning.
- Validate the unit (milliseconds, not seconds) and the sign.
- Omit the field entirely if you want the library default.
Example fix
// before
const opts = { refreshBeforeExpirationInMilliseconds: process.env.REFRESH_MS };
// after
const ms = Number(process.env.REFRESH_MS);
const opts = Number.isFinite(ms) && ms >= 0
? { refreshBeforeExpirationInMilliseconds: ms }
: {}; Defensive patterns
Strategy: validation
Validate before calling
function validRefreshMs(v: unknown): v is number {
return typeof v === "number" && Number.isFinite(v) && v >= 0;
} Type guard
function isValidRefreshMs(v: unknown): v is number {
return typeof v === "number" && Number.isFinite(v) && v >= 0;
} Prevention
- Parse env-provided numbers with Number() and validate.
- Confirm units are milliseconds, not seconds.
- Omit the field to accept the default.
When it happens
Trigger: Passing an IHttpConnectionOptions.authenticationRefreshOptions object whose `refreshBeforeExpirationInMilliseconds` is `NaN`, `Infinity`, a negative number, a string, or any non-number type when building/starting the HubConnection.
Common situations: Reading the value from an env var (string) without parsing, computing it from a division that can be NaN, or copying a config that uses seconds instead of milliseconds.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- A valid url is required.
- Could not load settings from
- The HubConnection url must be a valid url.
- Unknown log level
- Unknown passkey operation
AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11).
Data as JSON: /api/errors/827bec7b9f3dd6c9.
Report an issue: GitHub.
Appendix: source
Thrown at src/SignalR/clients/ts/signalr/src/HubConnection.ts:1033
retryReason,
});
} catch (e) {
this._logger.log(LogLevel.Error, `IRetryPolicy.nextRetryDelayInMilliseconds(${previousRetryCount}, ${elapsedMilliseconds}) threw error '${e}'.`);
return null;
}
}
private _validateAuthenticationRefreshOptions(): void {
if (!this._authenticationRefreshOptions) {
return;
}
const refreshBeforeExpirationInMilliseconds = this._authenticationRefreshOptions.refreshBeforeExpirationInMilliseconds;
if (refreshBeforeExpirationInMilliseconds !== undefined &&
(typeof refreshBeforeExpirationInMilliseconds !== "number" ||
!Number.isFinite(refreshBeforeExpirationInMilliseconds) ||
refreshBeforeExpirationInMilliseconds < 0)) {
throw new Error("Authentication refreshBeforeExpirationInMilliseconds must be a finite number greater than or equal to 0.");
}
}
private _scheduleAuthenticationRefreshIfNeeded(): void {
if (!this._isAutoAuthenticationRefreshEnabled()) {
return;
}
const authenticationRefreshFeature = this.connection.features.authenticationRefresh as IAuthenticationRefreshFeature | undefined;
const initialTokenLifetimeInSeconds = authenticationRefreshFeature?.initialTokenLifetimeInSeconds;
if (isValidAuthenticationTokenLifetime(initialTokenLifetimeInSeconds)) {
this._scheduleAuthenticationRefresh(initialTokenLifetimeInSeconds);
}
}
private _isAutoAuthenticationRefreshEnabled(): boolean {
return !!this._authenticationRefreshOptions && this._authenticationRefreshOptions.enableAutoRefresh !== false;
}View on GitHub (pinned to 3600ca084e)