From 4dd1af1d8465597423ae92f723bc9f9f63c7633d Mon Sep 17 00:00:00 2001 From: Behroz Saadat Date: Tue, 22 Sep 2026 16:51:21 -0500 Subject: [PATCH 1/2] docs(auth): add Fill from Managed Auth how-to Adds a page under Fill from Vault that walks through reusing a credential collected by Managed Auth with the vault fill operation: log in through Managed Auth, create a credential item that references the connection, attach the vault to a browser, and fill. Cross-links it from the Fill from Vault overview. Co-Authored-By: Claude Fable 5.1 --- auth/fill-from-managed-auth.mdx | 129 ++++++++++++++++++ auth/fill-from-vault.mdx | 2 +- docs.json | 3 +- .../check-managed-auth-credential-saved.mdx | 16 +++ .../create-managed-auth-credential-item.mdx | 32 +++++ snippets/fill-managed-auth-credential.mdx | 48 +++++++ .../managed-auth-credential-item-response.mdx | 30 ++++ snippets/managed-auth-login-for-fill.mdx | 29 ++++ snippets/navigate-github-login.mdx | 16 +++ 9 files changed, 303 insertions(+), 2 deletions(-) create mode 100644 auth/fill-from-managed-auth.mdx create mode 100644 snippets/check-managed-auth-credential-saved.mdx create mode 100644 snippets/create-managed-auth-credential-item.mdx create mode 100644 snippets/fill-managed-auth-credential.mdx create mode 100644 snippets/managed-auth-credential-item-response.mdx create mode 100644 snippets/managed-auth-login-for-fill.mdx create mode 100644 snippets/navigate-github-login.mdx diff --git a/auth/fill-from-managed-auth.mdx b/auth/fill-from-managed-auth.mdx new file mode 100644 index 00000000..627d06e9 --- /dev/null +++ b/auth/fill-from-managed-auth.mdx @@ -0,0 +1,129 @@ +--- +title: "Fill from Managed Auth" +description: "Reuse a credential collected through Managed Auth to fill a login form in a browser your application controls" +--- + +import ManagedAuthLoginForFill from "/snippets/managed-auth-login-for-fill.mdx"; +import CheckManagedAuthCredentialSaved from "/snippets/check-managed-auth-credential-saved.mdx"; +import CreateManagedAuthCredentialItem from "/snippets/create-managed-auth-credential-item.mdx"; +import ManagedAuthCredentialItemResponse from "/snippets/managed-auth-credential-item-response.mdx"; +import AttachCredentialVault from "/snippets/attach-credential-vault.mdx"; +import NavigateGitHubLogin from "/snippets/navigate-github-login.mdx"; +import FillManagedAuthCredential from "/snippets/fill-managed-auth-credential.mdx"; + +Managed Auth saves the durable fields a user enters during login, such as an email and password, so it can attempt eligible reauthentication later. A credential item backed by a Managed Auth connection lets your application reuse that saved credential with [`fill`](/vaults/fill): your application or agent drives the login form in its own browser session, and Kernel writes the values, without collecting the credential a second time or returning it to your application. + +The item stores no values of its own. It reads the connection's saved credential each time you read the item or invoke `fill`, so a password updated through Managed Auth is used immediately. + + + `fill` only writes stored values into fields you select. It doesn't discover fields, navigate, submit forms, verify authentication, monitor the session, or reauthenticate. Your application or agent owns each of those steps. + + +## When to use it + +Fill from Managed Auth works best when: + +- your users already log in through Managed Auth with the [Hosted UI](/auth/hosted-ui), the [React component](/auth/react), or the [programmatic flow](/auth/programmatic). +- a login prompt appears in a browser session that your application or agent controls, and you want to reuse the credential Managed Auth already holds. +- you want one credential per user, maintained by Managed Auth, instead of a second copy collected into a vault. + +Choose [Fill from Vault](/auth/fill-from-vault) with a Kernel credential item when the credential isn't collected through Managed Auth. Choose [Managed Auth](/auth/managed-auth) on its own when you want Kernel to run the login for you. + +## Requirements + +- The connection saves credentials. That's the default; a connection created with `save_credentials: false` has nothing to reuse. +- The connection's credential is stored in Kernel. Connections backed by [1Password](/integrations/1password) keep their values in 1Password and can't back an item. +- The connection, the vault, and the browser belong to the same project. + +## How it works + +These examples continue in order, using GitHub as the login destination. Set `KERNEL_API_KEY` in your trusted backend environment. All examples use the default project. + + + + Create a connection and complete a login with the [Hosted UI](/auth/hosted-ui), the [React component](/auth/react), or the [programmatic flow](/auth/programmatic). Managed Auth saves the fields the user entered once the login succeeds. + + + + Before continuing, confirm the connection holds a saved Kernel credential. The connection's `credential` field names it once it's saved: + + + + Wait for the saved credential, not for the connection's status. A connection that's `NEEDS_AUTH` still qualifies as long as it holds a saved credential. + + + Create a [vault](/vaults/overview) for the user, then create a credential item whose `spec` names the connection. The item is created `ready`, and `state.fields` lists the field names you can fill. + + + + The request returns `404` when no connection has that id, and `409` when the connection hasn't saved a credential yet, belongs to another project, or is backed by an external provider such as 1Password. The `409` error names the condition. Fix the connection through the Managed Auth API, for example by finishing the login, then create the item again. Repeating the same request for an existing key returns the existing item. + + + Attach the vault when you create the browser. The attachment can't change during the session and grants access to every item in the vault. + + + + + Your application or agent navigates to the login page and picks a selector for each field it wants filled. The selectors in the next step target GitHub's login form; inspect the page and recheck them if it changes. + + + + + Retrieve the item, require `ready` and an advertised `fill` operation, then invoke [`fill`](/vaults/fill) with field names from `state.fields` and your selectors. Your application must authorize the destination before filling: `fill` doesn't compare the page with the connection's domain. + + + + `completed` means the selected fields were filled, not that login succeeded. If `fill` fails, returns an uncertain outcome, or loses its response, stop and [inspect the outcome](/vaults/fill#handle-the-outcome) rather than retrying automatically. + + + After `fill` completes, your application or agent submits the login form once and verifies the site's response. If the site asks for an authenticator code and `state.fields` includes `totp`, navigate to that prompt and invoke `fill` again with a single `totp` binding, for example `{ "field": "totp", "selector": "#app_totp" }` on `https://github.com/sessions/two-factor/app`. Kernel generates the code at fill time. Handle any other prompt through your own interface. + + Delete the demo browser when finished. Deleting the item later doesn't affect the connection or its credential. + + + +## What the item exposes + +Reading the item describes the credential without returning any values: + + + +Field names come from the login itself, so read them from `state.fields` instead of assuming them. Each entry carries a `type` of `text`, `email`, `password`, or `totp`. A `totp` entry appears when the credential includes a TOTP secret; `fill` writes a freshly generated code for it and never the secret. Entries change when the connection's credential changes, and the item's `version` stays at `1`, so compare `state.fields` rather than `version` to observe changes. + +## Item status + +Kernel computes the item's status each time you read it, from the connection it points at. + +| `state.status` | `state.status_reason` | Meaning | +| --- | --- | --- | +| `ready` | | The connection's saved credential can be read and `fill` is advertised. It doesn't mean a login succeeded. | +| `unavailable` | `no_credential` | The credential was deleted or emptied after the item was created. | +| `unavailable` | `connection_not_found` | The connection was deleted. | +| `unavailable` | `external_credential` | The connection now uses an external credential provider. | + +An unavailable item advertises no operations. Managed Auth owns the connection and its credential, so recover through the Managed Auth API, for example by starting a new login that saves the credential again. Once the connection holds a saved Kernel credential again, the item reads it and reports `ready`. You don't need to recreate it. + +## How it differs from a Kernel credential item + +- There's no collection form and no `collect` operation. Managed Auth collects the login. +- The item can't be edited. Change the credential through Managed Auth, by logging in again or updating the linked [credential](/auth/credentials), and the item picks the change up. +- Field names and types mirror the saved credential instead of a field list you declare. `wait` doesn't apply, because the item is created ready. +- Deleting the item leaves the connection and its credential in place. Deleting the connection or its credential makes the item unavailable. + +## Security boundary + +`fill` writes real values into the browser. Page scripts, extensions, developer tools, and an agent with unrestricted browser access can read them afterwards. Attaching a vault grants a browser access to all of its items, so attach it only to sessions authorized to use every item, and keep `fill` requests in your trusted backend. `fill` doesn't restrict credential items to the connection's domain, so authorize the destination in your application before invoking it. + +## Next steps + + + + Map credential fields to browser inputs and handle completed, failed, or unknown outcomes. + + + How Managed Auth saves credentials during login and how to update them. + + + Health checks, reauthentication, and recovering a connection that needs input. + + diff --git a/auth/fill-from-vault.mdx b/auth/fill-from-vault.mdx index f81bbf4c..ef3064ad 100644 --- a/auth/fill-from-vault.mdx +++ b/auth/fill-from-vault.mdx @@ -68,7 +68,7 @@ these examples continue in order, using hacker news as the login destination. se ## Credential sources -you can collect values from an end user with KERNEL's hosted collection form or [copy them from an existing credential vault](/vaults/existing-credential-vault) that your trusted backend can read. both paths produce a ready credential item and use the same `fill` operation. +you can collect values from an end user with KERNEL's hosted collection form or [copy them from an existing credential vault](/vaults/existing-credential-vault) that your trusted backend can read. if your users already log in through Managed Auth, [reuse that saved credential](/auth/fill-from-managed-auth) instead of collecting it again. each path produces a ready credential item and uses the same `fill` operation. today, copying values stores an encrypted KERNEL copy. `fill` doesn't accept raw values or a third-party vault reference in its request. diff --git a/docs.json b/docs.json index 047ebb58..a3a597e1 100644 --- a/docs.json +++ b/docs.json @@ -152,7 +152,8 @@ { "group": "Fill from Vault", "pages": [ - "auth/fill-from-vault" + "auth/fill-from-vault", + "auth/fill-from-managed-auth" ] }, { diff --git a/snippets/check-managed-auth-credential-saved.mdx b/snippets/check-managed-auth-credential-saved.mdx new file mode 100644 index 00000000..63e3731a --- /dev/null +++ b/snippets/check-managed-auth-credential-saved.mdx @@ -0,0 +1,16 @@ + + +```typescript TypeScript +const connection = await kernel.auth.connections.retrieve(auth.id); +if (!connection.credential?.name) { + throw new Error("managed auth hasn't saved a credential for this connection yet"); +} +``` + +```python Python +connection = kernel.auth.connections.retrieve(auth.id) +if not (connection.credential and connection.credential.name): + raise RuntimeError("managed auth hasn't saved a credential for this connection yet") +``` + + diff --git a/snippets/create-managed-auth-credential-item.mdx b/snippets/create-managed-auth-credential-item.mdx new file mode 100644 index 00000000..38f4a84d --- /dev/null +++ b/snippets/create-managed-auth-credential-item.mdx @@ -0,0 +1,32 @@ + + +```typescript TypeScript +const vault = await kernel.vaults.upsert({ name: "user-12345" }); +const item = await kernel.vaults.items.upsert("github-login", { + id_or_name: vault.id, + type: "credential", + spec: { provider: "managed_auth", connection_id: auth.id }, +}); +if (item.type !== "credential") throw new Error("expected a credential item"); +``` + +```python Python +vault = kernel.vaults.upsert(name="user-12345") +item = kernel.vaults.items.upsert( + "github-login", + id_or_name=vault.id, + type="credential", + spec={"provider": "managed_auth", "connection_id": auth.id}, +) +if item.type != "credential": + raise RuntimeError("expected a credential item") +``` + +```bash cURL +curl -X PUT "https://api.onkernel.com/vaults/$VAULT_ID/items/github-login" \ + -H "Authorization: Bearer $KERNEL_API_KEY" \ + -H "Content-Type: application/json" \ + -d "{\"type\":\"credential\",\"spec\":{\"provider\":\"managed_auth\",\"connection_id\":\"$CONNECTION_ID\"}}" +``` + + diff --git a/snippets/fill-managed-auth-credential.mdx b/snippets/fill-managed-auth-credential.mdx new file mode 100644 index 00000000..f8de778a --- /dev/null +++ b/snippets/fill-managed-auth-credential.mdx @@ -0,0 +1,48 @@ + + +```typescript TypeScript +const current = await kernel.vaults.items.retrieve(item.key, { id_or_name: vault.id }); +if (current.id !== item.id || current.type !== "credential" || + current.state.status !== "ready" || + !current.available_operations.some((operation) => operation.type === "fill")) { + throw new Error("credential is not ready to fill"); +} +// Field names come from current.state.fields, not from a list you declared. +const result = await kernel.vaults.items.performOperation(item.key, { + id_or_name: vault.id, + type: "fill", + browser_id: browser.session_id, + page_url: "https://github.com/login", + fields: [ + { field: "email", selector: "#login_field" }, + { field: "password", selector: "#password" }, + ], +}); +if (result.type !== "fill" || result.status !== "completed") { + throw new Error("stop and reconcile the fill outcome"); +} +``` + +```python Python +current = kernel.vaults.items.retrieve(item.key, id_or_name=vault.id) +if (current.id != item.id or current.type != "credential" or + current.state.status != "ready" or + not any(operation.type == "fill" for operation in current.available_operations)): + raise RuntimeError("credential is not ready to fill") +# Field names come from current.state.fields, not from a list you declared. +result = kernel.vaults.items.perform_operation( + item.key, + id_or_name=vault.id, + type="fill", + browser_id=browser.session_id, + page_url="https://github.com/login", + fields=[ + {"field": "email", "selector": "#login_field"}, + {"field": "password", "selector": "#password"}, + ], +) +if result.type != "fill" or result.status != "completed": + raise RuntimeError("stop and reconcile the fill outcome") +``` + + diff --git a/snippets/managed-auth-credential-item-response.mdx b/snippets/managed-auth-credential-item-response.mdx new file mode 100644 index 00000000..cff81ec1 --- /dev/null +++ b/snippets/managed-auth-credential-item-response.mdx @@ -0,0 +1,30 @@ +```json +{ + "id": "credential_managed_auth_example", + "key": "github-login", + "type": "credential", + "version": 1, + "spec": { + "provider": "managed_auth", + "connection_id": "ma_abc123xyz" + }, + "state": { + "provider": "managed_auth", + "status": "ready", + "fields": { + "email": { "type": "email" }, + "password": { "type": "password" }, + "totp": { "type": "totp" } + } + }, + "available_operations": [ + { + "type": "fill", + "description": "Fill selected fields from the connection's saved credential into a browser linked to this vault, without submitting." + } + ], + "available_expansions": [], + "created_at": "2026-01-01T12:00:00Z", + "updated_at": "2026-01-01T12:00:00Z" +} +``` diff --git a/snippets/managed-auth-login-for-fill.mdx b/snippets/managed-auth-login-for-fill.mdx new file mode 100644 index 00000000..5cfb1145 --- /dev/null +++ b/snippets/managed-auth-login-for-fill.mdx @@ -0,0 +1,29 @@ + + +```typescript TypeScript +import Kernel from "@onkernel/sdk"; + +const kernel = new Kernel(); +const auth = await kernel.auth.connections.create({ + domain: "github.com", + profile_name: "user-12345-github", +}); +const login = await kernel.auth.connections.login(auth.id); +// Hand login.hosted_url, or login.id and login.handoff_code, to the user +// and wait for the login to finish before continuing. +``` + +```python Python +from kernel import Kernel + +kernel = Kernel() +auth = kernel.auth.connections.create( + domain="github.com", + profile_name="user-12345-github", +) +login = kernel.auth.connections.login(auth.id) +# Hand login.hosted_url, or login.id and login.handoff_code, to the user +# and wait for the login to finish before continuing. +``` + + diff --git a/snippets/navigate-github-login.mdx b/snippets/navigate-github-login.mdx new file mode 100644 index 00000000..eea4e4ad --- /dev/null +++ b/snippets/navigate-github-login.mdx @@ -0,0 +1,16 @@ + + +```typescript TypeScript +await kernel.browsers.playwright.execute(browser.session_id, { + code: "await page.goto('https://github.com/login'); return await page.title();", +}); +``` + +```python Python +kernel.browsers.playwright.execute( + browser.session_id, + code="await page.goto('https://github.com/login'); return await page.title();", +) +``` + + From d200cc1b6a7f27bb284f93ff3ad1db533269f69c Mon Sep 17 00:00:00 2001 From: Behroz Saadat Date: Tue, 22 Sep 2026 16:59:14 -0500 Subject: [PATCH 2/2] docs(auth): tighten accuracy of the Fill from Managed Auth page Pre-linked credentials qualify even with save_credentials off, the 409 covers a credential with no values, a deleted connection cannot recover, the fill example narrows to the managed auth state before reading field names, and the TOTP step notes sites that auto-submit the code. Co-Authored-By: Claude Fable 5.1 --- auth/fill-from-managed-auth.mdx | 8 ++++---- snippets/fill-managed-auth-credential.mdx | 9 +++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/auth/fill-from-managed-auth.mdx b/auth/fill-from-managed-auth.mdx index 627d06e9..07b904c8 100644 --- a/auth/fill-from-managed-auth.mdx +++ b/auth/fill-from-managed-auth.mdx @@ -31,7 +31,7 @@ Choose [Fill from Vault](/auth/fill-from-vault) with a Kernel credential item wh ## Requirements -- The connection saves credentials. That's the default; a connection created with `save_credentials: false` has nothing to reuse. +- The connection holds a Kernel credential. Managed Auth saves one by default after a successful login. A connection created with `save_credentials: false` qualifies only if you linked a [pre-stored credential](/auth/credentials#pre-store-credentials) when creating it. - The connection's credential is stored in Kernel. Connections backed by [1Password](/integrations/1password) keep their values in 1Password and can't back an item. - The connection, the vault, and the browser belong to the same project. @@ -56,7 +56,7 @@ These examples continue in order, using GitHub as the login destination. Set `KE - The request returns `404` when no connection has that id, and `409` when the connection hasn't saved a credential yet, belongs to another project, or is backed by an external provider such as 1Password. The `409` error names the condition. Fix the connection through the Managed Auth API, for example by finishing the login, then create the item again. Repeating the same request for an existing key returns the existing item. + The request returns `404` when no connection has that id, and `409` when the connection hasn't saved any credential values yet, belongs to another project, or is backed by an external provider such as 1Password. The `409` error names the condition. Fix the connection through the Managed Auth API, for example by finishing the login, then create the item again. Repeating the same request for an existing key returns the existing item. Attach the vault when you create the browser. The attachment can't change during the session and grants access to every item in the vault. @@ -76,7 +76,7 @@ These examples continue in order, using GitHub as the login destination. Set `KE `completed` means the selected fields were filled, not that login succeeded. If `fill` fails, returns an uncertain outcome, or loses its response, stop and [inspect the outcome](/vaults/fill#handle-the-outcome) rather than retrying automatically. - After `fill` completes, your application or agent submits the login form once and verifies the site's response. If the site asks for an authenticator code and `state.fields` includes `totp`, navigate to that prompt and invoke `fill` again with a single `totp` binding, for example `{ "field": "totp", "selector": "#app_totp" }` on `https://github.com/sessions/two-factor/app`. Kernel generates the code at fill time. Handle any other prompt through your own interface. + After `fill` completes, your application or agent submits the login form once and verifies the site's response. If the site asks for an authenticator code and `state.fields` includes `totp`, navigate to that prompt and invoke `fill` again with a single `totp` binding, for example `{ "field": "totp", "selector": "#app_totp" }` on `https://github.com/sessions/two-factor/app`. Kernel generates the code at fill time. Some sites submit the code as soon as it's entered, so check the page state after the fill. Handle any other prompt through your own interface. Delete the demo browser when finished. Deleting the item later doesn't affect the connection or its credential. @@ -101,7 +101,7 @@ Kernel computes the item's status each time you read it, from the connection it | `unavailable` | `connection_not_found` | The connection was deleted. | | `unavailable` | `external_credential` | The connection now uses an external credential provider. | -An unavailable item advertises no operations. Managed Auth owns the connection and its credential, so recover through the Managed Auth API, for example by starting a new login that saves the credential again. Once the connection holds a saved Kernel credential again, the item reads it and reports `ready`. You don't need to recreate it. +An unavailable item advertises no operations. Managed Auth owns the connection and its credential, so recover through the Managed Auth API. For `no_credential` and `external_credential`, start a new login or relink a Kernel credential; once the connection holds a saved Kernel credential again, the item reads it and reports `ready` without being recreated. For `connection_not_found`, the item can't recover, because its `connection_id` is fixed: delete it and create a new item for the replacement connection. ## How it differs from a Kernel credential item diff --git a/snippets/fill-managed-auth-credential.mdx b/snippets/fill-managed-auth-credential.mdx index f8de778a..b1019fea 100644 --- a/snippets/fill-managed-auth-credential.mdx +++ b/snippets/fill-managed-auth-credential.mdx @@ -7,7 +7,9 @@ if (current.id !== item.id || current.type !== "credential" || !current.available_operations.some((operation) => operation.type === "fill")) { throw new Error("credential is not ready to fill"); } -// Field names come from current.state.fields, not from a list you declared. +if (current.state.provider !== "managed_auth") throw new Error("expected a managed auth credential"); +// Bind by the names Managed Auth saved, for example ["email", "password", "totp"]. +console.log(Object.keys(current.state.fields)); const result = await kernel.vaults.items.performOperation(item.key, { id_or_name: vault.id, type: "fill", @@ -29,7 +31,10 @@ if (current.id != item.id or current.type != "credential" or current.state.status != "ready" or not any(operation.type == "fill" for operation in current.available_operations)): raise RuntimeError("credential is not ready to fill") -# Field names come from current.state.fields, not from a list you declared. +if current.state.provider != "managed_auth": + raise RuntimeError("expected a managed auth credential") +# Bind by the names Managed Auth saved, for example ["email", "password", "totp"]. +print(list(current.state.fields)) result = kernel.vaults.items.perform_operation( item.key, id_or_name=vault.id,