actualbudget/actual · error

error

Error message

error

What it means

When loginWithOpenIdSetup() returns an error, the /login endpoint responds 400 with { status: 'error', reason: error }. This covers OpenID Connect setup failures — provider discovery failing, missing/misconfigured issuer or client credentials, or the setup module rejecting request parameters. The raw setup error string is passed through as the reason.

Source

Thrown at packages/sync-server/src/app-account.js:110

          return;
        }
      }
      break;
    }
    case 'openid': {
      if (!isValidRedirectUrl(req.body.returnUrl)) {
        res
          .status(400)
          .send({ status: 'error', reason: 'Invalid redirect URL' });
        return;
      }

      const { error, url } = await loginWithOpenIdSetup(
        req.body.returnUrl,
        req.body.password,
      );
      if (error) {
        res.status(400).send({ status: 'error', reason: error });
        return;
      }
      res.send({ status: 'ok', data: { returnUrl: url } });
      return;
    }

    default:
      tokenRes = await loginWithPassword(req.body.password);
      break;
  }
  const { error, token } = tokenRes;

  if (error) {
    res.status(400).send({ status: 'error', reason: error });
    return;
  }

  res.send({ status: 'ok', data: { token } });

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Read the 'reason' field of the 400 response — it carries the concrete OpenID error
  2. Verify OPENID_ISSUER, OPENID_CLIENT_ID, OPENID_CLIENT_SECRET (and discovery URL) are correct and reachable from the server
  3. Test the discovery document manually (curl <issuer>/.well-known/openid-configuration) from the server host
  4. Match OPENID_AUTH_METHOD to what the IdP supports and supply the password if required
  5. After fixing config, restart the sync-server so the OpenID client re-initializes

Example fix

// before
const { error, url } = await loginWithOpenIdSetup(req.body.returnUrl, req.body.password);
// error: 'failed to discover issuer'
// after
// OPENID_ISSUER=https://idp.example.com/realms/main (corrected, reachable)
const { error, url } = await loginWithOpenIdSetup(req.body.returnUrl, req.body.password);
if (!error) res.send({ status: 'ok', data: { returnUrl: url } });
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-flight: IdP discovery reachable from server
const cfg = await fetch(`${OPENID_ISSUER}/.well-known/openid-configuration`);
if (!cfg.ok) throw new Error('OpenID discovery unreachable');

Type guard

function isOpenIdSetupError(res, body) {
  return res.status === 400 && body?.status === 'error' && typeof body?.reason === 'string';
}

Try / catch

const res = await request('/login', { method: 'POST', body: { loginMethod: 'openid', returnUrl, password } });
if (res.status === 400) {
  const body = await res.json();
  if (body?.status === 'error') {
    showLoginError(body.reason); // e.g. discovery failed / bad client credentials
    return;
  }
}

Prevention

When it happens

Trigger: POST to /login with loginMethod 'openid' and a valid returnUrl, but loginWithOpenIdSetup fails: unreachable discovery document, invalid client_id/client_secret, unsupported auth method, or bad openid configuration values.

Common situations: Wrong OPENID_ISSUER or OPENID_CLIENT_ID/SECRET env vars; IdP discovery endpoint unreachable from the server (firewall, DNS); IdP metadata lacks required endpoints; auth method unsupported by the provider; password omitted or wrong when OPENID_AUTH_METHOD requires it.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/686c9934f2905269. Report an issue: GitHub.