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

_refreshAuthentication POSTs to a refresh URL and expects HTTP 200. Any other status code means the refresh failed (token endpoint rejected, server error, etc.), and the client throws with the offending status so the caller knows the connection's auth cannot be renewed.

Solutions

  1. Inspect response.statusCode in the error to pick the recovery path (401 -> re-auth, 5xx -> backoff/retry).
  2. Verify the refresh endpoint route and that it accepts the connectionToken format the client sends.
  3. Ensure the refresh token / access token factory returns a valid token.
  4. If transient, retry the refresh with backoff before tearing down the connection.
Defensive patterns

Strategy: retry

Validate before calling

async function probeRefresh(url: string, token: string) {
  const r = await fetch(`${url}/refresh?access=${encodeURIComponent(token)}`, { method: "POST" });
  if (r.status !== 200) throw new Error(`Refresh endpoint returned ${r.status}`);
}

Type guard

function isRefreshFailure(e: unknown): boolean {
  return e instanceof Error && /Unexpected status code returned from authentication refresh/.test(e.message);
}

Try / catch

import { HttpError } from "@microsoft/signalr";
try { await connection.start(); }
catch (e) {
  if (e instanceof Error && /Unexpected status code returned from authentication refresh/.test(e.message)) {
    const m = e.message.match(/'(\d+)'/);
    const status = m ? Number(m[1]) : 0;
    if (status === 401) await reAuthenticateUser();
  }
  throw e;
}

Prevention

When it happens

Trigger: The refresh-token endpoint returns 401 (refresh token invalid/expired), 403 (forbidden), 404 (wrong refresh route), or 5xx. The client surfaces the status verbatim in the error message.

Common situations: Refresh token expired server-side. Refresh URL is wrong (typo, missing controller). Server-side token service down. CORS or auth middleware rejects the refresh request. The connection string changed and the old token cannot be refreshed.

Understand the failure class

Related errors


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

Appendix: 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 3600ca084e)