gethomepage/homepage · error · Error

Duplicati login response did not include an access token

Error message

Duplicati login response did not include an access token

What it means

Thrown after a successful (200) Duplicati login when the response body parses as JSON but does not contain an AccessToken field. The HTTP layer succeeded but the contract is broken: Duplicati's login endpoint is expected to return { AccessToken: "..." }, and without it the subsequent API calls have no bearer token.

Source

Thrown at src/widgets/duplicati/proxy.js:67

  const loginUrl = new URL(formatApiCall(widgets[widget.type].api, { endpoint: "auth/login", ...widget }));
  const [status, , data] = await httpProxy(loginUrl, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      Password: String(widget.password),
      RememberMe: true,
    }),
  });

  if (status !== 200) {
    throw new Error(`Unable to login to Duplicati (status ${status})`);
  }

  const body = asJson(data);
  if (!body?.AccessToken) {
    throw new Error("Duplicati login response did not include an access token");
  }

  return body.AccessToken;
}

async function apiGet(widget, endpoint, accessToken) {
  const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));
  const [status, , data] = await httpProxy(url, {
    method: "GET",
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  });

  if (status !== 200) {
    throw new Error(`Duplicati request failed for ${endpoint}`);
  }

View on GitHub (pinned to b6dca1ae03)

Solutions

  1. Verify the widget's URL targets the Duplicati API base, not the web UI root.
  2. Reproduce the login POST with curl from the Homepage host and inspect the response JSON to confirm AccessToken is present.
  3. Check the Duplicati version against the one Homepage's widget was written for; update Homepage or align versions.
  4. Ensure no reverse proxy rewrites the login path or serves a static page on it.
  5. Temporarily log asJson(data) to see exactly what Duplicati returned and adjust the parsing expectation.

Example fix

// before
if (!body?.AccessToken) {
  throw new Error("Duplicati login response did not include an access token");
}
// after
if (!body?.AccessToken) {
  throw new Error(`Duplicati login response missing AccessToken; got: ${JSON.stringify(body).slice(0, 200)}`);
}
Defensive patterns

Strategy: type-guard

Type guard

function isDuplicatiLoginBody(v) {
  return typeof v === "object" && v !== null && typeof v.AccessToken === "string" && v.AccessToken.length > 0;
}

Try / catch

const body = asJson(data);
if (!isDuplicatiLoginBody(body)) {
  // Surface what Duplicati actually returned so the misconfigured proxy is obvious.
  throw new Error(`Unexpected Duplicati login body: ${JSON.stringify(body).slice(0, 200)}`);
}

Prevention

When it happens

Trigger: Duplicati returns 200 with an unexpected body shape (an HTML page from a reverse proxy, an empty object, an error envelope), an API version change renamed the field, or the login endpoint was redirected to a UI page that still returned 200.

Common situations: Reverse proxy (e.g. a custom 200 error/landing page) intercepting the login path; Duplicati upgrade that changed the auth response schema; base URL pointing at the web UI root instead of the API; a captive/redirecting network returning 200 HTML instead of the API JSON.

Related errors


AI-assisted analysis of gethomepage/homepage@b6dca1ae03 (2026-08-13). Data as JSON: /api/errors/65782ecdeafa8fda. Report an issue: GitHub.