Long sessions
Three things make a sign-in last as long as the task does.
Callbacks instead of polling (ping mode)
Section titled “Callbacks instead of polling (ping mode)”Set a notification endpoint on your agent in the console (or PATCH /v1/dev/clients/:id with notification_endpoint). From then on, each bc-authorize must carry a client_notification_token: a random secret you make per request (BageAgent.notificationToken()).
When the person approves or denies, or an Always grant approves at once, Bage POSTs to your endpoint:
POST /bage/ping HTTP/1.1Authorization: Bearer <client_notification_token>Content-Type: application/json
{ "auth_req_id": "req_…" }Check the bearer (BageAgent.verifyPing), answer 2xx, and call /v1/token once (agent.collect(pending)). Bage retries a failed call after 1 and 4 seconds, and polling keeps working, so a lost callback only costs time. Endpoints must be https and public; Bage doesn’t follow redirects.
Refresh tokens
Section titled “Refresh tokens”Every sign-in comes with a refresh_token. Exchange it at /v1/token with grant_type: "refresh_token" for a new agent token and a new refresh token; the SDK’s session.fetch() does this by itself a minute before the token runs out.
- Each refresh token works once. Using one twice ends the sign-in, since only a copy gets used twice.
- An Allow once sign-in can be refreshed for 8 hours after the person approved. An Always one keeps going until it goes 30 days unused.
- The person revoking the agent, the website cutting it off, or you disabling the agent ends it at once.
- The person sees an ongoing “Allow once” sign-in in their Agents list until it ends.
To continue in another process, store session.save() (it holds the refresh token: keep it like a password) and call agent.resume(saved).
Account links
Section titled “Account links”The first token answer of each sign-in carries account_link (lnk_…), your handle for that person. Store it with their profile and pass it as loginHint next time instead of their email. Links are per agent, so they can’t be used to match people across agents. When the person cuts your agent off, the link stops working (unknown_user_id): ask for their email again.
// Long sessions: Bage calls you back when the person answers (ping mode), the session refreshes itself past// 15 minutes, and the account link replaces the person's email next time.// Set your agent's notification endpoint in the console first, e.g. https://agent.example/bage/ping.import http from "node:http";import { BageAgent } from "@trybage/sdk/agent";
const { issuer, client_id, private_jwk } = JSON.parse(process.env.BAGE_AGENT_KEY ?? "{}");const agent = new BageAgent({ issuer, clientId: client_id, privateJwk: private_jwk });
// Your notification endpoint: Bage POSTs { auth_req_id } here with the token you sent as the bearer.const answered = new Map<string, () => void>();const tokens = new Map<string, string>();http .createServer((request, response) => { let body = ""; request.on("data", (chunk) => (body += chunk)); request.on("end", () => { const { auth_req_id } = JSON.parse(body || "{}"); const expected = tokens.get(auth_req_id); if (!expected || !BageAgent.verifyPing(request.headers.authorization, expected)) return void response.writeHead(401).end(); response.writeHead(204).end(); answered.get(auth_req_id)?.(); }); }) .listen(Number(process.env.CALLBACK_PORT ?? 8080));
// A saved account link signs the person in without their email.const savedLink = process.env.ACCOUNT_LINK;const notificationToken = BageAgent.notificationToken();const pending = await agent.requestSignIn({ site: process.env.SITE ?? "https://shop.example", loginHint: savedLink ?? process.env.PERSON ?? "alex@example.com", scopes: ["browse", "cart"], notificationToken,});tokens.set(pending.authReqId, notificationToken);console.log("Asked the person; waiting for Bage to call back…");await new Promise<void>((resolve) => answered.set(pending.authReqId, resolve));
// One call collects the token: no polling.const session = await agent.collect(pending);console.log("account link:", session.accountLink); // store it with the person's profile in your app
// fetch() refreshes the agent token by itself before it expires. Or refresh by hand:await session.refresh();const products = await session.fetch("/products");console.log(products.status, JSON.stringify(products.body));
// To pick the session up in another process, store session.save() (it holds the refresh token: keep it secret)// and call agent.resume(saved).process.exit(0);