Log In With Vault Credentials

View as Markdown

Put the credential in the vault once - username, password, and the TOTP seed - and automation signs in from then on. Two ways to use it: hand it to an agent by reference, or pull it into your own CDP code.

Store the login

1import { Bctrl } from "@bctrl/sdk";
2
3const bctrl = new Bctrl({ apiKey: process.env.BCTRL_API_KEY! });
4
5await bctrl.vault.upsert("clients/acme/login", {
6 type: "login",
7 username: "[email protected]",
8 password: process.env.ACME_PASSWORD!,
9 totpSecret: process.env.ACME_TOTP!, // the 2FA seed, not a code
10 origins: ["https://app.acme.com"], // where this login may be used
11});

Option A: an agent signs in by reference

Give a hosted agent the vault builtin through a toolset. The agent looks the login up by name; the plaintext never passes through your code.

1const toolset = await bctrl.toolsets.create({
2 name: "acme-login",
3 builtins: ["vault"],
4});
5
6const runtime = await bctrl.runtimes.create({ type: "browser", name: "vault-login" });
7await bctrl.runtimes.start(runtime.id);
8
9const result = await bctrl.runtimes.invocations.createAndWait(
10 runtime.id,
11 {
12 action: "browserUse",
13 instruction:
14 "Go to https://app.acme.com, sign in with the stored Acme login (including the one-time code), and confirm the dashboard loads.",
15 toolsetId: toolset.id,
16 },
17 { timeoutMs: 300_000 }
18);
19
20console.log(result.status);

Option B: your own code signs in

For scripted logins, read the decrypted value and generate the current one-time code at the exact moment the form asks for it:

1const login = await bctrl.vault.value("clients/acme/login");
2if (login.type !== "login") throw new Error("expected a login secret");
3
4await page.goto("https://app.acme.com/signin");
5await page.fill("#email", login.username);
6await page.fill("#password", login.password);
7await page.click("button[type=submit]");
8
9// The 2FA prompt - mint the code now, TOTP codes expire in ~30s.
10const { code } = await bctrl.vault.totp("clients/acme/login");
11await page.fill("#otp", code);
12await page.click("button[type=submit]");

vault.get returns metadata only; only vault.value decrypts. Keep the two separate in your code so listings and dashboards never touch plaintext.

Don’t log in every run

Once the session works, pair this with a profile-backed runtime so the login survives restarts and you only do this dance when the session actually expires.

Next