dotnet/aspnetcore · error · Error

Unexpected status code returned from authentication refresh

Error message

Unexpected status code returned from authentication refresh '${response.statusCode}'

What it means

Thrown at HttpConnection.ts:436 when the POST to the `/refresh` endpoint returns any HTTP status other than 200. The refresh protocol expects a 200 with a JSON body containing accessToken and tokenLifetimeSeconds; any other status is treated as a failed refresh and the new token is not applied.

Source

Thrown at src/SignalR/clients/ts/signalr/src/HttpConnection.ts:436

        const connectionGeneration = this._connectionGeneration;
        const headers: {[k: string]: string} = {};
        const [name, value] = getUserAgentHeader();
        headers[name] = value;

        const refreshUrl = this._createRefreshUrl(this._connectionUrl, this._connectionToken);
        this._logger.log(LogLevel.Debug, `Sending authentication refresh request: ${refreshUrl}.`);

        const request: HttpRequest = {
            content: "",
            headers: { ...headers, ...this._options.headers },
            timeout: this._options.timeout,
            withCredentials: this._options.withCredentials,
        };
        this._httpClient.markAuthenticationRefreshRequest(request);
        const response = await this._httpClient.post(refreshUrl, request);

        if (response.statusCode !== 200) {
            throw new Error(`Unexpected status code returned from authentication refresh '${response.statusCode}'`);
        }

        if (typeof response.content !== "string") {
            throw new Error("Invalid authentication refresh response received: expected JSON content.");
        }

        if (connectionGeneration !== this._connectionGeneration) {
            return undefined;
        }

        const refreshResponse = JSON.parse(response.content) as { accessToken?: unknown, tokenLifetimeSeconds?: unknown };
        if (typeof refreshResponse.accessToken === "string" && refreshResponse.accessToken) {
            // Redirecting servers can return a transport token that should replace the current cached token.
            this._setTransportAccessToken(refreshResponse.accessToken);
        } else if (!this._transportAccessTokenFromServer) {
            // Without a server-provided transport token, reuse the app token that successfully authenticated refresh.
            const refreshRequestToken = this._httpClient.getRefreshRequestToken(response);
            if (refreshRequestToken) {

View on GitHub (pinned to 294cab2f9b)

Solutions

  1. Verify the server is running a version of ASP.NET Core SignalR that implements the authentication refresh endpoint.
  2. Check server logs for the failing refresh request to see the real status code and reason.
  3. Ensure the connection token (`id=` query param) is preserved by any proxy.
  4. Handle the thrown error in your refreshAuthentication catch and trigger a full reconnect with a fresh app token instead of relying on refresh.

Example fix

// before
await hub.refreshAuthentication();

// after
try {
  await hub.refreshAuthentication();
} catch (e) {
  console.warn('refresh failed, full reconnect', e);
  await hub.stop();
  // re-acquire app token, then start again
  await hub.start();
}
Defensive patterns

Strategy: try-catch

Validate before calling

// cannot fully pre-check (status depends on server), but you can verify
// the server implements the refresh endpoint with a HEAD/GET probe.

Try / catch

try { await hub.refreshAuthentication(); }
catch (e) {
  const m = /status code returned from authentication refresh '(\d+)'/.exec(String(e));
  if (m) {
    const code = Number(m[1]);
    if (code === 401 || code === 403) {
      // app token fully expired - full reconnect with fresh token
      await hub.stop(); await hub.start();
    } else if (code === 404) {
      throw new Error('server does not implement the refresh endpoint');
    }
  }
  throw e;
}

Prevention

When it happens

Trigger: The auth refresh token has fully expired server-side (401); the connection token is unknown to the server (404); a server error (500); a proxy returning 403/502; the refresh endpoint not being implemented on the server (404) when using an older server version.

Common situations: Server clock skew causing the refresh to arrive after expiration; reverse proxy stripping the `id=` query param; ASP.NET Core server too old to implement the refresh endpoint (added in a later version); app token revoked out-of-band.

Understand the failure class

Related errors


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