**Note** from Bead: Egp Missions · [canonical source](https://redfish.acequia.io/guerin/.agents/35e928bc-200c-4eb7-8248-e7bca9b07859/2026-06-21/notes/01-headless-auth-options.md) · session 2026-06-21 · discussion: Talk: Egp Missions
> Stephen (2026-06-21): "currently we log into EGP in the browser then run the bookmarklet. Is there a > more direct API to EGP with tokens? Or can you log in with Puppeteer and maintain a cookie/token? > Every so often I might have to use an authenticator — is there a way to give an `.mjs` a limited > authenticator? I'm exploring all ways to do this." > > This note surveys every path from "human-in-the-browser" to "fully headless `.mjs`", with the > security/credential framing each one implies. **It is a design survey, not yet a build.**
## 0. Compliance caveat (read first) EGP / WildFireSA is a **federal system** (USFS, **FedRAMP** + USDA-compliant, auth via **FAMAuth**). Automating credential entry, storing a TOTP seed, or scripting the OAuth flow may be governed by the system's **Rules of Behavior / Acceptable Use** and the Login.gov / USDA-eAuth terms. Everything below is technically feasible for the legitimate account holder automating *their own* authorized access; **confirm it's permitted for your account/role before deploying** — especially storing a second-factor seed. When in doubt, ask the EGP/FAMAuth admins whether a service account or sanctioned API path exists (Option A) before building Option B/C. Per feedback_every-constraint-is-soft this is a soft constraint to weigh, not a hard stop — but it's a *governance* constraint, so surface it to a human, don't silently route around it.
## 1. How EGP auth actually works (the ground truth) - EGP login goes through **FAMAuth** — the Wildland Fire Application Portal SSO (`famauth.wildfire.gov`) — which federates two identity providers: - **Login.gov** — public users (this is almost certainly your path). - **USDA eAuth / PIV card** — government/eAuth credential holders. - FAMAuth is **OAuth 2.0**. Per FAMAuth release notes: the **access token expires after 30 minutes**; FAMAuth issues a **refresh token that expires after 12 hours**; tokens no longer carry PII. 2FA is mandated (Login.gov TOTP authenticator app, SMS, or security key) + RBAC. - The bookmarklet today rides your **already-authenticated EGP session cookies** to call `egpapi.wildfire.gov` (`/v1/sa/missionscombined/{id}`). It never sees your password or token directly — it borrows the live session. That's why "log in, then click" is the current dance. **The key lever:** the **12-hour refresh token**. If we can obtain one, an `.mjs` can mint fresh 30-minute access tokens for ~12 hours with *no* browser and *no* second factor, calling `egpapi` directly. Re-driving the full login (with 2FA) then happens only ~twice a day, not per-forage.
## 2. The options ### Option A — a sanctioned direct API / service token (best if it exists) - **What:** ask FAMAuth/EGP admins whether there is a **non-interactive OAuth client** (client-credentials grant), an **API key program**, or a **service account** for `egpapi.wildfire.gov`. Precedent exists in the ecosystem: the **FEMS API** published external-use guidance (March 2026), so wildfire.gov *does* expose sanctioned APIs. - **Pros:** no browser, no 2FA gymnastics, no ToS gray area, stable, revocable by the issuer. - **Cons:** may not exist for EGP/`egpapi`; requires a human request + possibly an agreement. - **Verdict:** **pursue first.** It's the only path with no compliance ambiguity. Everything else is a workaround for the absence of this. ### Option B — headless browser login (Puppeteer/Playwright) + persist session - **What:** drive the FAMAuth → Login.gov OAuth flow with **Playwright** (preferred over raw Puppeteer for its `storageState` API) in a real (optionally headful-first) browser: 1. Navigate to the EGP login, submit username/password to Login.gov. 2. Handle the 2FA challenge (see Option C for the TOTP seed). 3. After redirect back to EGP, **persist `context.storageState()`** (cookies + localStorage) to an encrypted file, and/or scrape the **refresh token** from the OAuth response. 4. The `.mjs` then either (a) replays `storageState` into a headless context and calls `egpapi`, or (b) uses the refresh token to mint access tokens directly (lighter — no browser after first login). - **Refresh cadence:** access token 30 min → refresh silently; refresh token 12 h → re-run the browser login ~2×/day (this is when the authenticator is needed). - **Pros:** works without any cooperation from EGP; mirrors exactly what the bookmarklet does; the persisted session is the same artifact the human uses. - **Cons:** brittle to login-page changes; storing session/refresh tokens = real blast radius; closest to the ToS line. CAPTCHA/anomaly detection on Login.gov could intervene. - **Reuse:** the bookmarklet already has a **backend-agnostic `storagePut/storageMkdir/storageList`** layer (note 00 §4). A headless `.mjs` forager should call the **same** EGP endpoints and write through the **same** storage abstraction (WebDAV mode) into `incidents/<incident>/egp-missions/` — i.e. port the app's data layer to Node, swap the cookie source from "live browser session" to "persisted session/refresh token." ### Option C — a "limited authenticator" the `.mjs` can use (the 2FA seed) This is the direct answer to "give an `.mjs` a limited authenticator." - **If Login.gov 2FA = authenticator app (TOTP):** when you enroll an authenticator, Login.gov shows a **base32 secret** (the thing behind the QR code). Save that secret once. In Node, generate the 6-digit code on demand: ```js import { authenticator } from 'otplib'; // RFC 6238: 30s step, 6 digits, SHA-1 const code = authenticator.generate(process.env.EGP_TOTP_SECRET); // feed to the login form ``` That *is* the limited authenticator: a scoped TOTP seed the script holds, equivalent to a dedicated authenticator app living inside the `.mjs`. - **"Limited" = make it a dedicated, low-blast-radius factor:** - If Login.gov permits **multiple authenticators**, enroll a **separate** one just for automation, so revoking it (remove that authenticator in Login.gov account settings) kills the bot without disturbing your phone. - Keep the seed in a **node, not the commons** (feedback_browser-is-a-parciante-node): credential placement = scope × blast-radius × revocability, uniform across runtimes. A TOTP seed for a federal account is **high blast radius** → encrypt at rest, env-var or OS keychain, never in the bead tree, never synced. (Same rule the bead-handler note `fe7fbaf5` applies to API keys; the acequia `acequia.keys` custody idea — capability objects not key strings — is the eventual home.) - **If 2FA = SMS or a security key:** TOTP automation doesn't apply. SMS would need an SMS-receiving API (more moving parts, more blast radius); a hardware security key (WebAuthn) is deliberately **not** automatable — that's its point. So Option C's clean path depends on Login.gov offering the **authenticator-app** factor. Enroll TOTP if you haven't.
## 3. Recommended posture (hybrid, refresh-token-centric) 1. **Ask EGP/FAMAuth for Option A** (service/API access). If granted, stop — use it. 2. **Until then, Option B + C:** a one-time (or ~2×/day) Playwright login that uses the **TOTP seed (C)** to clear 2FA, captures the **12-hour refresh token**, and hands it to a lightweight `.mjs` forager that mints 30-min access tokens and calls `egpapi` directly — writing through the bookmarklet's storage layer into the incident folder. 3. **Wire to the ignition trigger:** once headless, a `wildcad`/FIRMS ignition record (the [`5b518c35` wildfire-forager](https://redfish.acequia.io/guerin/.agents/5b518c35-2ce7-474a-a5a8-ec83e2fd0e82/) pipeline) can drive EGP-mission lookup + forage with no human in the loop for up to 12 h between re-logins.
## 4. Decision matrix | | Browser? | 2FA handling | Token lifetime in play | Compliance | Blast radius | Effort | |---|---|---|---|---|---|---| | **A. Sanctioned API/service token** | none | none | issuer-defined | clean | issuer-revocable | low (if it exists) — needs a human ask | | **B. Headless browser + persist** | first login (then refresh-token) | once per 12 h | 30-min access / 12-h refresh | gray area | session/refresh token stored | medium | | **C. TOTP seed in `.mjs`** | (feeds B) | automated via `otplib` | n/a | depends on ToS | **high** (2nd factor seed) — isolate + dedicate | low to add |
## 5. Open questions / next steps 1. **Does a sanctioned EGP/`egpapi` API or service account exist?** (Option A — ask FAMAuth/EGP admins; check FEMS-API precedent.) Gates everything. 2. **What 2FA factor is on your Login.gov account?** TOTP (Option C works cleanly), SMS, or security key (not automatable)? If not TOTP, can you add a dedicated authenticator? 3. **Where does the refresh token / session live?** Confirm credential placement: OS keychain vs encrypted env file on the forager node; never the bead tree. Tie into `acequia.keys` custody (`fe7fbaf5`). 4. **Headful vs headless first run?** Login.gov may anomaly-flag pure-headless; a headful-first-then-headless pattern is usually more robust. 5. **Is automating this authorized?** Confirm against EGP Rules of Behavior before deploying (§0).
## Sources - FAMAuth: https://www.wildfire.gov/application/famauth · https://famauth.wildfire.gov/ · release notes (token lifetimes): https://famauth.wildfire.gov/release-notes.html - EGP login / FAMAuth account guide: https://egp.wildfire.gov/egp/login/ · https://egp.wildfire.gov/egp/Assets/PDFs/EGP-FAMAuth_Account_User_Guide.pdf - EGP app: https://www.wildfire.gov/application/egp · WildFireSA modernization (ICF): https://www.icf.com/clients/disaster-management/enterprise-geospatial-portal-usfs-wildfiresa - FEMS API (sanctioned-API precedent): https://www.wildfire.gov/page/fems-api - otplib (TOTP in Node): https://github.com/yeojz/otplib · https://www.npmjs.com/package/otplib - Playwright storageState (session persistence): https://playwright.dev/docs/auth
## References (bead cross-links) - Bead: Wildfire Forager · [canonical](https://redfish.acequia.io/guerin/.agents/5b518c35-2ce7-474a-a5a8-ec83e2fd0e82/)