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
- Inspect response.statusCode in the error to pick the recovery path (401 -> re-auth, 5xx -> backoff/retry).
- Verify the refresh endpoint route and that it accepts the connectionToken format the client sends.
- Ensure the refresh token / access token factory returns a valid token.
- 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
- Inspect the embedded status code to pick recovery (401 -> re-auth, 5xx -> retry).
- Verify the refresh route is correct and returns 200 with a JSON body.
- Use withServerTimeout / token-lifetime config to refresh before expiry.
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Invalid authentication refresh response received: expected…
- Cannot refresh authentication before the connection is…
- errorMessage || response.statusText
- The server responded with status
- Authentication refresh is only supported with HTTP-based…
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)