Skip to content

fix(login): fail fast with a clear error on slow/failed sign-in submit - #371

Merged
izzywdev merged 1 commit into
masterfrom
fix/login-fast-fail
Jul 23, 2026
Merged

fix(login): fail fast with a clear error on slow/failed sign-in submit#371
izzywdev merged 1 commit into
masterfrom
fix/login-fast-fail

Conversation

@izzywdev

Copy link
Copy Markdown
Owner

Summary

  • Bounds the login/signup submit request to a dedicated, shorter timeout (LOGIN_TIMEOUT_MS, env-overridable, default 15s) instead of riding the shared 30s axios timeout — a slow auth-chain hop now fails fast instead of hanging.
  • Login submit errors now distinguish a timeout/abort ("Sign-in is taking longer than expected...") from a 401 (worded so it doesn't wrongly accuse the user of a typo, since a slow-auth abort can also surface as a 401) from other failures.
  • The submit button always un-sticks (pending resets in finally on every exit path); a quiet "still working..." hint appears after 8s for a slow-but-succeeding attempt.
  • Added LoginPage.submit-resilience.test.tsx covering both error paths and the button un-sticking.

Context

Prod bug: an intermittent slow Authentik auth-chain hop (16-30s, later diagnosed as DNS stalls to CoreDNS — infra, separate/out of scope) left the login submit silently stuck on "Signing in..." with zero feedback, sometimes 401-ing after ~19s with the user never told why.

Test plan

  • npx vitest run — 11/11 passing (2 new + 9 existing regression, incl. LoginPage.google-signin.test.tsx)
  • npx tsc --noEmit — clean for both edited files (repo has pre-existing unrelated errors in sibling packages not touched by this change)
  • Chrome DevTools MCP, real browser against a mock auth endpoint on the actual /login route:
    • Fast success path: login resolves, redirects to /dashboard
    • Slow path (mocked to exceed a shortened LOGIN_TIMEOUT_MS): shows "Sign-in is taking longer than expected — the service may be busy. Please try again." within the bounded window, button returns to "Sign In" (not stuck)
    • 401 path: shows "Incorrect email or password, or the sign-in service is temporarily unavailable. Please try again.", button un-stuck
    • Console clean aside from pre-existing, unrelated mock-server 404s (/apps not implemented in the mock)

Out of scope

  • Backend intermittent-401 / Authentik-CoreDNS latency (infra, separate work)
  • app-registry 401
  • Deploy wiring

Prod is deploy-on-push master — not merging this myself; leaving for the orchestrator to merge in a deploy window.

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

Prod: an intermittent slow auth-chain hop (16-30s) left the login submit
button stuck on "Signing in..." with zero feedback, sometimes 401-ing
after ~19s with the user never told why. That 30s window came from the
shared axios instance timeout, which the login/signup POST rode along
with everything else.

- services/api.ts: bound authAPI.login()/signup() to a dedicated,
  shorter LOGIN_TIMEOUT_MS (env-overridable, default 15s) via a
  per-request axios timeout override, instead of the shared 30s.
- pages/LoginPage.tsx: distinguish a timeout/abort ("taking longer than
  expected") from a 401 (worded so it doesn't wrongly accuse the user of
  a typo, since a slow-auth abort can also surface as a 401) from other
  failures; always reset `pending` in the submit's finally so the button
  never stays stuck; show a quiet "still working..." hint after 8s for a
  slow-but-succeeding attempt.
- Added LoginPage.submit-resilience.test.tsx covering both error paths
  and the button un-sticking.

Verified in a real browser (Chrome DevTools MCP) against a mock auth
endpoint: a >timeout response shows the distinct timeout message within
the bounded window and re-enables the button; a 401 shows the ambiguous-
401 message; console clean aside from pre-existing unrelated mock-server
404s. Backend intermittent-401/Authentik latency is separate, out of
scope infra work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session-Id: f636c22e-1cd7-401e-8843-97e3e3a4ba01
@github-actions
github-actions Bot enabled auto-merge (squash) July 23, 2026 19:18
@izzywdev izzywdev added the auto-merge Enable squash auto-merge once CI passes label Jul 23, 2026
@izzywdev
izzywdev merged commit 27e05f6 into master Jul 23, 2026
50 checks passed
@izzywdev
izzywdev deleted the fix/login-fast-fail branch July 23, 2026 21:02
@izzywdev
izzywdev restored the fix/login-fast-fail branch July 27, 2026 11:46
@izzywdev
izzywdev deleted the fix/login-fast-fail branch July 27, 2026 18:27
@izzywdev
izzywdev restored the fix/login-fast-fail branch July 29, 2026 05:10
izzywdev pushed a commit that referenced this pull request Jul 29, 2026
…ideal one

The client bound was 15s, chosen (#371) to fail FAST on a slow attempt
rather than leave the submit button stuck on "Signing in…". That traded
the wrong way. The documented slow path is 16-30s, so a 15s bound did not
fail slow sign-ins fast — it failed sign-ins that would otherwise have
SUCCEEDED. A bound below the known worst case is not a safety net, it is
an outage, and that is what users are hitting.

Client 15s -> 45s (VITE_LOGIN_TIMEOUT_MS), server budget 12s -> 40s
(AUTHENTIK_LOGIN_DEADLINE_MS). 45s covers the documented worst case with
real margin, and the server budget stays UNDER the client's so the server
still answers first with a labelled error rather than being raced to an
anonymous abort — that ordering is the invariant to preserve if either is
retuned later.

The wait is not pleasant. LoginPage already shows a "still working…" hint
at 8s, so a slow-but-succeeding attempt does not look frozen. Both values
stay env-overridable so the pair can be tightened again — without a
rebuild — once the underlying slow hop is actually fixed, which is the
real cure this is buying time for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QhfKNASYBD9aS3Wu6RUPfZ
izzywdev pushed a commit that referenced this pull request Jul 29, 2026
Task 3 of the sign-in investigation: find the slowness. The blocker was
that the evidence has never existed when it mattered.

Per-hop timings were already instrumented — at `logger.debug`. LOG_LEVEL
defaults to `info` and is not set anywhere in the chart, so in production
that detail has always been OFF. Answering "which hop is slow?" needed a
config change or a redeploy, during an incident, on a path that only
misbehaves intermittently. That is why this module has collected two
timeout band-aids (#362, #371) and no diagnosis.

Adds a slow-hop threshold (the slow-query-log pattern): every hop is
still debug on the fast path, but a hop over AUTHENTIK_SLOW_HOP_WARN_MS
(1s) is logged at WARN with its stage label, status and elapsed time. No
LOG_LEVEL change, no redeploy, no spam on healthy logins. The
token-exchange stage is now timed the same way — it is two openid-client
round-trips and just as able to be the slow one, so it must not be the
one stage missing from the breakdown.

Also reports a slow SUCCESS. A login that succeeds at 25s is exactly the
failure mode that broke sign-in: it never errors, so nothing alerts, and
it only became visible once a client bound tripped underneath it. Over
AUTHENTIK_LOGIN_WARN_MS (8s, above the ~5.5s fast path) that now logs at
WARN, and the per-hop WARNs say which stage owned the time.

Leading hypothesis, written into the code so the logs can refute it
rather than confirm a guess: every hop targets the same in-cluster
origin, and this pod's own dnsConfig documents CoreDNS "intermittently
stalls lookups in 5s/10s retry multiples" (capped ~2s by timeout:1/
attempts:2) with the note that this service "resolves authentik-server on
every auth flow". DNS resolves per NEW CONNECTION, and the leaked
response bodies fixed earlier in this PR forced a new connection per hop
— so the stall was multiplied by hop count. ~6 hops x ~2s accounts for
most of the observed 16-30s, and draining bodies lets undici reuse one
keep-alive socket per origin, collapsing it to at most one. If the
labelled elapsedMs still shows connect/DNS dominating, the next step is
an explicit keep-alive dispatcher pinned to the Authentik origin.

This is instrumentation, not a proven cure: it is what turns the next
slow sign-in into a named stage instead of another guess.

Tests: 1 new case — a slow hop on a login that SUCCEEDS emits exactly one
WARN carrying the stage label and elapsed time.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QhfKNASYBD9aS3Wu6RUPfZ
izzywdev pushed a commit that referenced this pull request Aug 2, 2026
…ver-side login

Every hop in the server-brokered password-login chain (flow-executor stages,
authorize hops, admin-API set_password) lands on the same in-cluster Authentik
origin, several times per request. The default fetch dispatcher's keep-alive
window is tuned for general traffic and can lapse between hops when CoreDNS's
documented multi-second stalls (see the recordHop comments in
authentikPassword.ts) space them out, forcing a fresh connection — and a fresh
DNS lookup — per hop. A dedicated undici Agent with a longer keep-alive holds
one socket open across the whole chain, so only the first hop pays for
DNS+connect. This was the last documented, unapplied mitigation in that file's
own incident notes (#362, #371) for the intermittent 16-30s+ sign-in stalls
that show up as an outright client-side timeout for some accounts.
izzywdev added a commit that referenced this pull request Aug 2, 2026
…ver-side login (#494)

Every hop in the server-brokered password-login chain (flow-executor stages,
authorize hops, admin-API set_password) lands on the same in-cluster Authentik
origin, several times per request. The default fetch dispatcher's keep-alive
window is tuned for general traffic and can lapse between hops when CoreDNS's
documented multi-second stalls (see the recordHop comments in
authentikPassword.ts) space them out, forcing a fresh connection — and a fresh
DNS lookup — per hop. A dedicated undici Agent with a longer keep-alive holds
one socket open across the whole chain, so only the first hop pays for
DNS+connect. This was the last documented, unapplied mitigation in that file's
own incident notes (#362, #371) for the intermittent 16-30s+ sign-in stalls
that show up as an outright client-side timeout for some accounts.

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-merge Enable squash auto-merge once CI passes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant