🌐 US-Proxy
class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
Skip to content

Bug: Clicking one external auth button disables all other auth buttons on workspace creation page #22420

Description

@blinkagent

Description

When a template requires multiple coder_external_auth providers (e.g., GitLab and an OIDC provider), clicking the "Login with..." button for one provider causes all other auth buttons to show a loading spinner and become disabled. The user cannot interact with the second auth button until they refresh the page after completing the first authentication.

Environment

  • Coder version: 2.29.6
  • Browser: Chrome (likely affects all browsers)

Steps to Reproduce

  1. Configure two or more external auth providers in Coder (e.g., GitLab and an OIDC/PING provider)
  2. Create a template that requires both providers using coder_external_auth data sources:
    data "coder_external_auth" "provider_a" {
      id = "provider-a"
    }
    
    data "coder_external_auth" "provider_b" {
      id = "provider-b"
    }
  3. Navigate to the "Create Workspace" page for that template
  4. Observe two auth buttons under "External Authentication"
  5. Click the "Login with..." button for the first provider

Expected Behavior

Only the clicked provider's button should show a loading spinner. The second provider's button should remain clickable so the user can authenticate with both providers without a page refresh.

Actual Behavior

Both buttons immediately show a loading spinner and become disabled. The second button cannot be clicked until the page is refreshed after the first auth completes.

Current Workaround

Users must:

  1. Click the first auth button and complete authentication
  2. Refresh the page
  3. Click the second auth button

This works because after refresh, the first provider is already authenticated, leaving only one unauthenticated button.

Root Cause Analysis

The externalAuthPollingState is a single shared state for all external auth providers. When any button is clicked, startPollingExternalAuth() sets the global state to "polling", and every ExternalAuthButton receives the same isLoading prop:

CreateWorkspacePageView.tsx (~line 536):

{externalAuth.map((auth) => (
  <ExternalAuthButton
    key={auth.id}
    auth={auth}
    isLoading={externalAuthPollingState === "polling"}
    onStartPolling={startPollingExternalAuth}
    displayRetry={externalAuthPollingState === "abandoned"}
  />
))}

CreateWorkspacePage.tsx (~line 365):

const useExternalAuth = (versionId: string | undefined) => {
  const [externalAuthPollingState, setExternalAuthPollingState] =
    useState<ExternalAuthPollingState>("idle");
  // ... single state shared across ALL providers
};

Additionally, polling stops only when allSignedIn is true (every provider is authenticated), so even after the first auth succeeds, the polling state won't revert to "idle" until the second provider also authenticates — which the user can't trigger because its button is disabled.

Suggested Fix

Make the polling state per-provider instead of global, e.g., using a Map<string, ExternalAuthPollingState> keyed by auth provider ID, so each button independently tracks its own polling/loading/abandoned state.


Created on behalf of a user.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions