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
45 changes: 45 additions & 0 deletions auth/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,50 @@ _ = login
```
</CodeGroup>

## Human verification for CAPTCHAs

Set `hitl: true` to let a person finish a CAPTCHA that the login browser can't solve on its own. Without it, the login fails with `captcha_blocked`. With it, the login pauses at `AWAITING_HUMAN_INTERVENTION` so the person can complete the check in the login browser's live view, then confirm. KERNEL resumes the login from the page they left.

<CodeGroup>
```typescript TypeScript
const auth = await kernel.auth.connections.create({
domain: 'example.com',
profile_name: 'my-profile',
hitl: true,
});
```

```python Python
auth = await kernel.auth.connections.create(
domain="example.com",
profile_name="my-profile",
hitl=True,
)
```

```go Go
auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{
ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{
Domain: "example.com",
ProfileName: "my-profile",
Hitl: kernel.Bool(true),
},
})
if err != nil {
panic(err)
}
_ = auth
```
</CodeGroup>

The [Hosted UI](/auth/hosted-ui) and the [React component](/auth/react) embed the live view and show a **Continue** button, so you don't need extra code. In a [programmatic flow](/auth/programmatic#human-verification), you show the live view and submit the confirmation yourself.

How the pause behaves:

- It applies to interactive logins, including CAPTCHAs that appear before the login form. Automatic reauthentication never pauses, because nobody is there to solve the challenge; it still fails with `captcha_blocked`.
- Each pause waits up to 10 minutes, or until the login session expires if that's sooner. If nobody confirms in time, the login fails with `captcha_blocked` and an `error_message` saying a person was asked and didn't finish in time.
- If the site asks for another check after the person continues, the login pauses again. From the second pause on, the confirmation choice's `description` says the site is asking to verify again.

## Record sessions for debugging

Set `record_session: true` to capture a [replay](/browsers/replays) of every browser session tied to the connection — initial logins, background health checks, and automatic re-authentications. The entire browser session is recorded.
Expand Down Expand Up @@ -918,6 +962,7 @@ After creating a connection, you can update its configuration with `auth.connect
| `auto_reauth` | Whether a scheduled health check that confirms a logged-out session may trigger an eligible automatic reauthentication attempt |
| `save_credentials` | Whether to save credentials on successful login |
| `record_session` | Record a [replay](/browsers/replays) of every auth browser session for this connection (logins, health checks, and reauths) |
| `hitl` | Pause interactive logins on an unsolvable CAPTCHA so a person can complete it. See [human verification for CAPTCHAs](#human-verification-for-captchas) |
| `browser.region` | Region for login, health-check, and reauth browsers. Takes effect on the next browser created for the connection |
| `proxy` | Pin login, health-check, and reauth sessions to a proxy. Takes effect on the next health check or reauth |

Expand Down
18 changes: 12 additions & 6 deletions auth/connection-lifecycle.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Call `.login()` immediately before work that requires an authenticated session.

For a connection with a previous successful login and saved auth check URL, `.login()` runs the verifier first. If the profile is still signed in, the flow reaches `SUCCESS` without submitting anything. If it isn't, KERNEL starts the normal login flow and uses saved credentials when available.

Follow the connection until the flow reaches `SUCCESS`, then create the browser or continue the task. If the flow reaches `AWAITING_INPUT` or `AWAITING_EXTERNAL_ACTION`, stop the pre-task helper and send the user to the `hosted_url` returned by this `.login()` call. Continue that login flow; don't call `.login()` again, because a new call cancels the flow already in progress.
Follow the connection until the flow reaches `SUCCESS`, then create the browser or continue the task. If the flow reaches `AWAITING_INPUT`, `AWAITING_EXTERNAL_ACTION`, or `AWAITING_HUMAN_INTERVENTION`, stop the pre-task helper and send the user to the `hosted_url` returned by this `.login()` call. Continue that login flow; don't call `.login()` again, because a new call cancels the flow already in progress.

Use this pattern whether periodic health checks are enabled or disabled:

Expand All @@ -114,7 +114,8 @@ async function ensureAuthenticated(connectionId: string) {

if (
event.flow_step === 'AWAITING_INPUT' ||
event.flow_step === 'AWAITING_EXTERNAL_ACTION'
event.flow_step === 'AWAITING_EXTERNAL_ACTION' ||
event.flow_step === 'AWAITING_HUMAN_INTERVENTION'
) {
throw new Error(`user action required at ${login.hosted_url}`);
}
Expand Down Expand Up @@ -142,7 +143,11 @@ async def ensure_authenticated(connection_id: str):
if event.flow_status == "SUCCESS":
return

if event.flow_step in {"AWAITING_INPUT", "AWAITING_EXTERNAL_ACTION"}:
if event.flow_step in {
"AWAITING_INPUT",
"AWAITING_EXTERNAL_ACTION",
"AWAITING_HUMAN_INTERVENTION",
}:
raise RuntimeError(f"user action required at {login.hosted_url}")

raise RuntimeError("authentication did not complete successfully")
Expand Down Expand Up @@ -174,7 +179,8 @@ for events.Next() {
break
}

if event.FlowStep == "AWAITING_INPUT" || event.FlowStep == "AWAITING_EXTERNAL_ACTION" {
switch event.FlowStep {
case "AWAITING_INPUT", "AWAITING_EXTERNAL_ACTION", "AWAITING_HUMAN_INTERVENTION":
panic("user action required at " + login.HostedURL)
}
}
Expand Down Expand Up @@ -220,7 +226,7 @@ Common codes:
| `rate_limited` | The site rate-limited the login attempt. | No; retry later |
| `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 |
| `captcha_blocked` | A CAPTCHA was presented and couldn't be solved, or a person was asked to solve it and didn't finish in time. | No |

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

Expand All @@ -232,7 +238,7 @@ See the [API reference](https://kernel.sh/docs/api-reference/managed-auth/start-
- **`account_choice_required` / `customer_input_required` / `external_action_required`** — Start an interactive login and complete the choice, field, or external approval. Kernel does not guess an identity or trigger notification-producing steps during unattended reauth.
- **`account_locked`** — Unlock the account with the site directly. Calling `.login()` again before that will not help and may extend the lockout.
- **`rate_limited`** — Wait for the site's retry window before starting another login.
- **`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).
- **`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). For `captcha_blocked` on interactive logins, enable [`hitl`](/auth/configuration#human-verification-for-captchas) so a person can complete the CAPTCHA in the live view instead of the login failing.
- **`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.

Expand Down
1 change: 1 addition & 0 deletions auth/hosted-ui.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ The user will:
1. See the login page for the target website
2. Enter their credentials
3. Complete 2FA or another verification step if needed
4. If the connection has [`hitl`](/auth/configuration#human-verification-for-captchas) enabled and the site shows a CAPTCHA the login browser can't solve, complete it in the embedded browser and click **Continue**

<Info>
KERNEL can attempt automatic reauthentication for eligible flows using stored credentials, including totp codes generated from an available secret. completing this interactive login doesn't guarantee unattended reauthentication. supplying an email or sms code doesn't give KERNEL access to future codes. when the connection needs user input again, start a new login and direct the user to its `hosted_url`. see [connection recovery](/auth/connection-lifecycle#flows-that-need-input-a-choice-or-approval).
Expand Down
69 changes: 69 additions & 0 deletions auth/programmatic.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ All selectable auth routes use the same shape. `choice.type` identifies the cate
- `auth_method`
- `identifier_method`
- `account`
- `human_intervention`, used only at [human verification](#human-verification) pauses
- `other`

Render the visible `label`, optional `description`, and optional `masked_destination`. Submit the exact `choice.id` returned by the event:
Expand Down Expand Up @@ -312,13 +313,81 @@ if (
}
```

## Human verification

When the connection has [`hitl`](/auth/configuration#human-verification-for-captchas) enabled and the login browser hits a CAPTCHA it can't solve, `flow_step` becomes `AWAITING_HUMAN_INTERVENTION`. The event carries one choice with `type: 'human_intervention'` and id `human_intervention_done`.

Show the person the event's `live_view_url`, either in a new tab or embedded in an iframe. After they complete the check in the live view, submit the choice with the current `interaction_id`. KERNEL resumes the login from the page they left.

<CodeGroup>
```typescript TypeScript
if (state.flow_step === 'AWAITING_HUMAN_INTERVENTION' && state.interaction_id) {
const done = state.choices?.find(choice => choice.type === 'human_intervention');
if (done) {
await showLiveViewUntilConfirmed(state.live_view_url, done.description);

await kernel.auth.connections.submit(auth.id, {
interaction_id: state.interaction_id,
selected_choice_id: done.id,
});
}
}
```

```python Python
if state.flow_step == "AWAITING_HUMAN_INTERVENTION" and state.interaction_id:
done = next(
(choice for choice in (state.choices or []) if choice.type == "human_intervention"),
None,
)
if done:
await show_live_view_until_confirmed(state.live_view_url, done.description)

await kernel.auth.connections.submit(
auth.id,
interaction_id=state.interaction_id,
selected_choice_id=done.id,
)
```

```go Go
if state.FlowStep == "AWAITING_HUMAN_INTERVENTION" && state.InteractionID != "" {
for _, choice := range state.Choices {
if choice.Type != "human_intervention" {
continue
}
showLiveViewUntilConfirmed(state.LiveViewURL, choice.Description)

_, err := client.Auth.Connections.Submit(ctx, auth.ID, kernel.AuthConnectionSubmitParams{
SubmitFieldsRequest: kernel.SubmitFieldsRequestParam{
InteractionID: kernel.String(state.InteractionID),
SelectedChoiceID: kernel.String(choice.ID),
},
})
if err != nil {
panic(err)
}
break
}
}
```
</CodeGroup>

`showLiveViewUntilConfirmed` represents your application's ui: it shows the live view and a button the person clicks when they're done.

- At this step the submit endpoint accepts only the `human_intervention` choice. Field values and other choices return a 400.
- KERNEL can't tell whether a submit came after the check was solved. If the site still shows a challenge, or asks again, the login pauses again with a new `interaction_id`. From the second pause on, the choice's `description` says the site is asking to verify again; show it so the person knows why they're back.
- Each pause waits up to 10 minutes, or until the login session expires if that's sooner. If nobody submits in time, the flow fails with `captcha_blocked`.
- If your page sets a Content-Security-Policy, allow the origin of `live_view_url` in `frame-src`, or the browser blocks the embedded live view.

## Step reference

| Step | Description |
|---|---|
| `DISCOVERING` | Finding and inspecting the login surface. |
| `AWAITING_INPUT` | Waiting for canonical fields or choices. |
| `AWAITING_EXTERNAL_ACTION` | Waiting for an out-of-browser action; canonical fallbacks may also be present. |
| `AWAITING_HUMAN_INTERVENTION` | Waiting for a person to complete a CAPTCHA in the live view and submit the `human_intervention` choice. Only on connections with `hitl` enabled. |
| `SUBMITTING` | Processing the submitted interaction. |
| `COMPLETED` | The flow has finished. |

Expand Down
9 changes: 9 additions & 0 deletions auth/react.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ rendering the component for the initial login doesn't provide ongoing automatic

call `auth.connections.login()` on the existing connection from your backend, then bring the user back to your login route. pass the new login response's `id` as `sessionId` and its fresh `handoff_code` as `handoffCode`. don't reuse the previous handoff code: it is single-use. wait for successful authentication before resuming work that requires the account. see [connection recovery](/auth/connection-lifecycle#flows-that-need-input-a-choice-or-approval).

## Human verification

When the connection has [`hitl`](/auth/configuration#human-verification-for-captchas) enabled and the login hits a CAPTCHA the browser can't solve, the component embeds the login browser's live view so the person can complete the check, then click **Continue**. If the site asks for another check, the component shows a note explaining why the person is back.

If your page sets a Content-Security-Policy, allow the origin of the connection's `live_view_url` in `frame-src`. Otherwise the browser blocks the embedded live view and the person can't complete the check.

This requires `@onkernel/managed-auth-react` 0.6.0 or later.

## Backend connectivity

By default the component talks directly to `https://api.onkernel.com`. That works out of the box; nothing else to configure.
Expand Down Expand Up @@ -262,6 +270,7 @@ import {
StepExpired,
LoadingState,
ExternalActionWaiting,
HumanInterventionStep,
UnifiedAuthForm,
AppearanceProvider,
LocalizationProvider,
Expand Down
Loading