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
- Read the server's message/error appended to the thrown error — validation details (which field) are usually included.
- Fix form values: issuer, endpoints, and tokenEndpointAuthentication must match what the IdP actually exposes (https URLs, supported auth scheme).
- If 403, confirm the user is an org admin and handle ReauthRequiredError via runReauthableAction.
- If 409 on domain, the domain is claimed elsewhere — verify ownership flow or use a different domain.
- 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
- Validate every endpoint URL is https and well-formed before POST.
- Only enable Save for org admins.
- Match tokenEndpointAuthentication to what the IdP actually supports.
- On 409 (domain claimed), stop resubmitting and guide the user to domain verification or a different domain.
- Keep the 20s timeout; SAML metadata fetch can be slow — don't retry aggressively on timeout.
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
- Manual OIDC configuration requires authorization, token, and
- OIDC discovery failed with ${response.status}. Enter manual
- Could not resolve workspace SSO (${response.status}).
- Failed to load SSO settings (${response.status}).
- Failed to delete SSO settings (${response.status}).
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/984bb457d515cbc5.
Report an issue: GitHub.