dotnet/aspnetcore · error · Error

Authentication refreshBeforeExpirationInMilliseconds must be

Error message

Authentication refreshBeforeExpirationInMilliseconds must be a finite number greater than or equal to 0.

What it means

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.

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 294cab2f9b)

Solutions

  1. Pass a positive finite number in milliseconds: `withAuthenticationRefresh({ refreshBeforeExpirationInMilliseconds: 5 * 60 * 1000 })`.
  2. If loading from env, parse and validate: `const n = Number(process.env.REFRESH_MS); if (!Number.isFinite(n) || n < 0) throw ...`.
  3. Omit the field to use the default of 5 minutes.
  4. Ensure you pass milliseconds, not seconds.

Example fix

// before
new HubConnectionBuilder()
  .withUrl(url)
  .withAuthenticationRefresh({ refreshBeforeExpirationInMilliseconds: '300000' })
  .build(); // throws

// after
new HubConnectionBuilder()
  .withUrl(url)
  .withAuthenticationRefresh({ refreshBeforeExpirationInMilliseconds: 5 * 60 * 1000 })
  .build();
Defensive patterns

Strategy: validation

Validate before calling

function validateRefreshOpts(opts) {
  const v = opts?.refreshBeforeExpirationInMilliseconds;
  if (v !== undefined && (typeof v !== 'number' || !Number.isFinite(v) || v < 0)) {
    throw new Error('refreshBeforeExpirationInMilliseconds must be a finite number >= 0');
  }
}
validateRefreshOpts(refreshOptions);
new HubConnectionBuilder().withUrl(url).withAuthenticationRefresh(refreshOptions).build();

Type guard

function isValidRefreshBeforeMs(v: unknown): v is number | undefined {
  return v === undefined || (typeof v === 'number' && Number.isFinite(v) && v >= 0);
}

Try / catch

// thrown synchronously from build(); wrap construction
try { builder.withAuthenticationRefresh(opts).build(); }
catch (e) {
  if (/refreshBeforeExpirationInMilliseconds/.test(String(e))) {
    opts.refreshBeforeExpirationInMilliseconds = 5 * 60 * 1000;
    return builder.withAuthenticationRefresh(opts).build();
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06). Data as JSON: /api/errors/827bec7b9f3dd6c9. Report an issue: GitHub.