Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions auth/connection-lifecycle.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ After the initial login, every connection moves through this loop:

<Steps>
<Step title="Health check">
On a configurable cadence, Kernel spins up a browser with the profile and verifies the session is still logged in. If it is, nothing else happens until the next check.
On a configurable cadence, Kernel spins up a browser with the profile and verifies the session. A check reaches one of three conclusions: still logged in, definitely logged out, or **inconclusive** — the page didn't prove either way.

A check needs concrete evidence to conclude. If the site returns a challenge page, times out, partially loads, or shows something ambiguous, the result is inconclusive and the connection is left exactly as it was. Nothing happens until the next check.
</Step>
<Step title="Auto-reauth (if eligible)">
If the check finds the session expired and the connection's `can_reauth` is `true`, Kernel runs the saved login flow with the stored credentials in the background. A successful login resets the loop.
If the check finds the session **definitely** expired and the connection's `can_reauth` is `true`, Kernel runs the saved login flow with the stored credentials in the background. A successful login resets the loop.

An inconclusive check never triggers reauth. Kernel would rather check again on the next cycle than log in again unnecessarily — a spurious login can trip risk checks on the site, prompt a device-verification email, or invalidate a working session.
</Step>
<Step title="NEEDS_AUTH (if not eligible, or auto-reauth fails)">
If auto-reauth isn't possible — credentials aren't linked, the saved flow requires human input, or the login keeps failing — the connection's `status` flips to `NEEDS_AUTH` and a new login session is required.
Expand Down Expand Up @@ -127,24 +131,31 @@ if state.Status == kernel.ManagedAuthStatusNeedsAuth {

## When a login fails

If a login attempt fails — whether triggered by a health check, an auto-reauth, or a manual `.login()` — Kernel retries with exponential backoff. After repeated failures the flow is marked failed and the connection surfaces an error code on `flow_status`.
If a login attempt fails — whether triggered by a health check, an auto-reauth, or a manual `.login()` — the flow is marked failed and the connection surfaces an error code on `flow_status`.

How much Kernel retries depends on the failure. A transient site problem (a 5xx page, a maintenance screen) is retried once against the login page before giving up. A conclusive rejection by the site — wrong credentials, a locked account, an unsupported method — is **not** retried, because retrying would burn attempts against a lockout or fail identically.

Common codes:

| Code | Meaning |
|------|---------|
| `credentials_invalid` | The stored or submitted credentials were rejected by the site. |
| `bot_detected` | The login page blocked the session as automated. |
| `captcha_blocked` | A CAPTCHA was presented and couldn't be solved. |
| `unsupported_auth_method` | The site required a method Kernel doesn't currently support (e.g. passkeys). |
| Code | Meaning | Retried |
|------|---------|---------|
| `credentials_invalid` | The stored or submitted credentials were rejected by the site. | No |
| `account_locked` | The site locked or suspended the account. | No |
| `unsupported_auth_method` | The site required a method Kernel doesn't currently support (e.g. passkeys). | No |
| `rate_limited` | The site rate-limited the login attempt. | Yes, after a delay |
| `website_error` | The site returned an error, maintenance, or unavailable page. | Once |
| `bot_detected` | The login page blocked the session as automated. | No |
| `captcha_blocked` | A CAPTCHA was presented and couldn't be solved. | No |

See the [API reference](https://kernel.sh/docs/api-reference/managed-auth/start-login-flow) for the full list.

### Recovering

- **`credentials_invalid`** — Update the linked [credential](/auth/credentials) and call `.login()` to re-run the flow.
- **`credentials_invalid`** — Update the linked [credential](/auth/credentials) and call `.login()` to re-run the flow. During an interactive login, Kernel asks for a corrected value in place rather than failing the whole flow — see [replacing a rejected credential](/auth/programmatic#replacing-a-rejected-credential).
- **`account_locked`** — Unlock the account with the site directly. Calling `.login()` again before that will not help and may extend the lockout.
- **`bot_detected` / `captcha_blocked`** — Pin the connection to a cleaner [proxy](/auth/configuration#custom-proxy) (ISP or custom). For aggressive sites, also enable stealth and review the [bot detection guide](/browsers/bot-detection/overview).
- **`unsupported_auth_method`** — Switch the account to a supported sign-in method (e.g. password + TOTP instead of a passkey) and re-link the credential.
- **`website_error`** — Usually the site, not the connection. Retry later; if it persists, confirm the connection's `login_url` still points at a working login page.

## Debugging a flaky connection

Expand Down
2 changes: 1 addition & 1 deletion auth/faq.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Passkey-based authentication (e.g., Google accounts with passkeys enabled) is no

## What happens if login fails?

Kernel retries with exponential backoff, then surfaces an error code (`credentials_invalid`, `bot_detected`, `captcha_blocked`, etc.). See [Connection Lifecycle](/auth/connection-lifecycle#when-a-login-fails) for the full list and recovery steps.
Kernel surfaces an error code (`credentials_invalid`, `account_locked`, `bot_detected`, `captcha_blocked`, etc.). Transient site failures are retried; a conclusive rejection by the site isn't, so Kernel doesn't burn attempts against a locked account or resubmit credentials the site already refused. See [Connection Lifecycle](/auth/connection-lifecycle#when-a-login-fails) for the full list and recovery steps.

## Can I use Managed Auth with any website?

Expand Down
30 changes: 30 additions & 0 deletions auth/programmatic.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,36 @@ The `discovered_fields` array tells you what the login form needs:
[{ name: 'otp', type: 'code' }]
```

### Replacing a rejected credential

If the site explicitly rejects a submitted value, the flow returns to `AWAITING_INPUT` with the affected field marked `replace_existing: true` instead of failing outright. Treat that as "this specific value was wrong, ask again" — don't resubmit the same value, and don't reuse a cached one:

<CodeGroup>
```typescript TypeScript
if (state.flow_step === 'AWAITING_INPUT') {
const stale = state.discovered_fields?.filter(f => f.replace_existing);

if (stale?.length) {
// The site rejected these. Prompt the user again; a stored value will fail identically.
const corrected = await promptUser(stale);
await kernel.auth.connections.submit(auth.id, { fields: corrected });
}
}
```

```python Python
if state.flow_step == "AWAITING_INPUT":
stale = [f for f in (state.discovered_fields or []) if f.get("replace_existing")]

if stale:
# The site rejected these. Prompt the user again; a stored value will fail identically.
corrected = await prompt_user(stale)
await kernel.auth.connections.submit(auth.id, fields=corrected)
```
</CodeGroup>

This only happens during an interactive login. An unattended re-authentication that hits a rejected credential fails with `credentials_invalid` rather than resubmitting — repeated bad submissions are what lock accounts.

## Complete Example

<CodeGroup>
Expand Down
Loading