Repository navigation
[bug] v2 self-host: password-created owner has no UI path to connect SSO #2291
Description
Executor version
2.0.0-beta.15
How do you run Executor?
Self-host (Docker)
Operating system
Linux
Integration involved
SSO / OIDC (Google; also reproduced with a synthetic HTTPS OIDC provider)
What happened
In Executor v2 self-host 2.0.0-beta.15, an owner created through password setup cannot subsequently sign in through same-email OIDC/Google SSO. The callback returns account_not_linked even when the identity provider supplies a verified email. The Security page has no action to connect that identity, and local email verification is unavailable.
The protection against implicitly linking an unverified local account appears intentional. The bug is the missing supported user-facing path for the existing owner to connect SSO. This report is not requesting that protection be disabled.
SSO redirects to the login page with error=account_not_linked. The page tells the user to contact an administrator. There is no account-linking control on the self-host Security page.
What you expected
Provide a supported browser flow for a password-created owner to connect an SSO identity while authenticated, with appropriate confirmation and provider checks. The login error should direct users to that flow. A supported local email-verification flow could also address the blocked implicit-linking path, but neither exists in beta.15.
Preserve protection against silently linking an unverified local account based only on matching email addresses.
Steps to reproduce
- Configure self-host OIDC using
SSO_DISCOVERY_URL,SSO_CLIENT_ID,SSO_CLIENT_SECRET, andSSO_ALLOWED_DOMAINSfor a provider that returns a verified email. - Create the initial owner using password setup. Use the same email as the OIDC identity, in the allowed domain.
- Sign out, select SSO, and complete provider authentication.
- After the error, sign in using the password and inspect Account → Security for a way to connect the SSO identity or verify the local email.
Diagnostics / logs
Environment and reproduction evidence
- Executor self-host
2.0.0-beta.15. - Published image:
ghcr.io/usefulsoftwareco/executor-selfhost@sha256:f060373a0ffd6f993bc619ea935e3410e0d68180eeda382fbdd56020a84ba91a. - Reproduced twice using the unchanged upstream image, a fresh disposable data volume each time, and a synthetic HTTPS OIDC provider.
- The provider supplied a signed RS256 ID token with matching email,
email_verified: true, expected issuer/audience, and the generated nonce. Executor fetched the token endpoint and JWKS successfully. - Also observed with an existing password owner and Google OIDC in a real self-host deployment.
Observed responses
The setup owner's session reports emailVerified: false. An authenticated request to the verification endpoint also confirms that this is not an available recovery path:
POST /api/auth/send-verification-email
Content-Type: application/json
{"email":"owner@example.test","callbackURL":"https://executor.example.test/account/security"}{"status":400,"code":"VERIFICATION_EMAIL_NOT_ENABLED"}Selected observations from both isolated upstream runs:
Password owner created: HTTP 200; emailVerified=false
Local verification request: HTTP 400; VERIFICATION_EMAIL_NOT_ENABLED
Same-email verified OIDC login: HTTP 302; account_not_linked
Authenticated explicit linking: HTTP 302; no error
OIDC login after linking: HTTP 302; same owner; emailVerified=true
Root-cause evidence
- Password setup explicitly creates the local user with
emailVerified: false. - Shared auth options and self-host auth options do not configure a verification-email sender or override the local-email linking protection.
- Better Auth 1.7.5 requires local email verification by default for implicit linking. A verified provider email does not satisfy this separate local-account check.
- The self-host Security route supplies password management, without an SSO-linking action.
SSO-first setup is not an available workaround in beta.15
We also attempted SSO sign-in before initial setup on a fresh volume using the unchanged upstream beta.15 image and a synthetic OIDC provider with a verified email. The callback returned HTTP 403:
The instance must have exactly one organization. Contact its administrator.
No account or session was created, and the instance still reported setup: true. SSO registration requires an existing organization and assigns new users the member role; it does not bootstrap the initial owner and organization. Starting with SSO therefore cannot avoid the password-owner linking problem in beta.15.
Workaround confirmed
- Sign in as the existing owner using the original password.
- Open the browser developer console on Executor and paste the following JavaScript. This is the console flow used in our deployment, with only the deployment URL and owner email replaced by placeholders. Set those two values to your deployment and existing owner before running it.
(async () => {
const base = "https://executor.example.test";
if (location.origin !== base) throw new Error("Open Executor first.");
const sessionResponse = await fetch("/api/auth/get-session", {credentials: "same-origin"});
const session = await sessionResponse.json();
if (!sessionResponse.ok || session?.user?.email !== "owner@example.test") {
throw new Error("Sign in with your original owner email and password first.");
}
const response = await fetch("/api/auth/link-social", {
method: "POST",
credentials: "same-origin",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({provider: "sso", callbackURL: base + "/account/security"})
});
const result = await response.json();
if (!response.ok || !result.url) throw new Error("SSO linking could not start.");
const target = new URL(result.url);
if (target.protocol !== "https:" || target.hostname !== "accounts.google.com") {
throw new Error("Unexpected authorization destination.");
}
location.assign(target.href);
})();- The snippet checks the current session, starts authenticated explicit linking, checks that the returned URL points to Google, and redirects the browser. Choose the matching Google identity and complete consent. The provider ID is
sso, because self-host uses generic OIDC. - After returning to Executor, sign out and verify Google SSO reaches the existing owner and organization.
Explicit linking succeeded in both isolated upstream runs and the real Google deployment. The console snippet above was used for Google; the synthetic runs exercised the linking endpoint directly. The Google workaround currently requires a developer-console request rather than a product UI action. It does not require changing the database or disabling verification protections.
Related work checked
Checked public all-state issues/PRs, relevant comments, all open PR titles, and current v2 authentication source on October 14, 2026. No matching owner-linking UI report or open implementation was found.
- #1972 is related but concerns v1 Okta ID tokens missing
email_verified. This reproduction explicitly suppliesemail_verified: trueand is blocked by the unverified local owner. - #2041 was closed unmerged during pre-v2 backlog cleanup; it is not a released fix for this case.
- Relevant setup, self-host/shared authentication, Security UI, and Better Auth patch contents remain unchanged at inspected
v2commit2868b6e. Current-v2 runtime was not tested.
Source links above use public beta.15 tag commit e5910a9.
Before you submit
- I searched the open issues for a duplicate.
- I removed all keys, tokens, and credentials from this report.
Activity