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

  1. Parse the value to a Number and use Number.isFinite before assigning.
  2. Validate the unit (milliseconds, not seconds) and the sign.
  3. 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

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

Related errors


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)