different-ai/openwork · error

Failed to save SSO settings (${response.status}).

Error message

Failed to save SSO settings (${response.status}).

What it means

Thrown by handleSave when POST /v1/sso (or the create/update path variant held in `path`) returns non-ok. The body carries the full OIDC/SAML connection definition (issuer, clientId, endpoints, tokenEndpointAuthentication, etc.), so this is typically server-side validation of the SAML/OIDC metadata or a permissions problem. On success the parsed connection and domain verification token are written back into form state.

Source

Thrown at ee/apps/den-web/app/(den)/dashboard/_components/sso-screen.tsx:194

                audience: audience || undefined,
              }
            : {
                issuer,
                domain,
                clientId,
                clientSecret,
                scopes: scopes.split(/\s+/).map((entry) => entry.trim()).filter(Boolean),
                skipDiscovery,
                authorizationEndpoint: authorizationEndpoint || undefined,
                tokenEndpoint: tokenEndpoint || undefined,
                jwksEndpoint: jwksEndpoint || undefined,
                userInfoEndpoint: userInfoEndpoint || undefined,
                tokenEndpointAuthentication: tokenEndpointAuthentication || undefined,
              };

          const { response, payload } = await requestJson(path, { method: "POST", headers: getOrgScopedHeaders(), body: JSON.stringify(body) }, 20000);
          if (!response.ok) {
            throw getRequestError(payload, response, `Failed to save SSO settings (${response.status}).`);
          }

          const parsed = parseOrgSsoPayload(payload);
          setConnection(parsed.connection);
          syncFormFromConnection(parsed.connection);
          setDomainVerificationToken(parsed.domainVerificationToken);
          setEditing(false);
        } finally {
          setSaving(false);
        }
      });
    } catch (nextError) {
      setError(nextError instanceof Error ? nextError.message : "Failed to save SSO settings.");
    }
  }

  async function handleDelete() {
    if (!access.canManageSso) {

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Read the server's message/error appended to the thrown error — validation details (which field) are usually included.
  2. Fix form values: issuer, endpoints, and tokenEndpointAuthentication must match what the IdP actually exposes (https URLs, supported auth scheme).
  3. If 403, confirm the user is an org admin and handle ReauthRequiredError via runReauthableAction.
  4. If 409 on domain, the domain is claimed elsewhere — verify ownership flow or use a different domain.
  5. Check network tab for exact status; re-try after fixing inputs rather than resubmitting identical payload.

Example fix

// before: save whatever the form holds
const body = { issuer, clientId, userInfoEndpoint: userInfoEndpoint || undefined, ... };
await requestJson(path, { method: "POST", headers: getOrgScopedHeaders(), body: JSON.stringify(body) }, 20000);
// after: validate URLs first
const urls = [issuer, authorizationEndpoint, tokenEndpoint, userInfoEndpoint].filter(Boolean);
if (urls.some((u) => !u.startsWith("https://"))) {
  setError("All endpoints must be https:// URLs.");
  return;
}
// ... then POST as before
Defensive patterns

Strategy: validation

Validate before calling

function validateSsoForm(f: {
  issuer: string; clientId: string;
  authorizationEndpoint?: string; tokenEndpoint?: string; userInfoEndpoint?: string;
  tokenEndpointAuthentication?: string;
}): string | null {
  if (!f.issuer.trim()) return "Issuer is required.";
  if (!f.clientId.trim()) return "Client ID is required.";
  const urls = [f.issuer, f.authorizationEndpoint, f.tokenEndpoint, f.userInfoEndpoint].filter(Boolean);
  if (urls.some((u) => !/^https:\/\//.test(u))) return "All endpoints must be https URLs.";
  if (f.tokenEndpointAuthentication && !["client_secret_basic","client_secret_post"].includes(f.tokenEndpointAuthentication))
    return "Unsupported token endpoint authentication method.";
  return null;
}
const problem = validateSsoForm(form); if (problem) { setError(problem); return; }

Type guard

function isHttpUrl(v: string): boolean {
  try { const u = new URL(v); return u.protocol === "https:"; } catch { return false; }
}

Try / catch

try {
  await saveSso(body);
} catch (err) {
  if (isReauthRequiredError(err)) { promptSignIn(); return; }
  // server validation messages land here — surface them next to the form
  setFieldErrorsFromServer(err.message);
}

Prevention

When it happens

Trigger: POST /v1/sso with org-scoped headers and the connection body returns 400 (invalid IdP metadata, malformed URLs, bad tokenEndpointAuthentication value), 401 (expired session), 403 (not org admin / reauth challenge), 409 (conflicting existing connection or already-verified domain), 422 (schema validation), 429, or 5xx. 20s timeout (longest of the SSO calls).

Common situations: Typo in issuer/issuer URL or endpoints (must be https); pasting metadata from an IdP with unsupported auth method like client_secret_jwt; non-admin tries to save; domain already claimed by another org.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/984bb457d515cbc5. Report an issue: GitHub.