> For the complete documentation index, see [llms.txt](https://docs.bitbadges.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.bitbadges.io/for-developers/cli/auth-commands.md).

# Auth Commands

The `bb auth` subcommand authenticates the CLI against the BitBadges indexer using the standard SIWBB (SIWE / EIP-4361) challenge → verify flow. It stores the resulting session cookie under `~/.bitbadges/auth.json` (mode `0600`, multi-account, multi-network), and `bb api --with-session` attaches that cookie on subsequent calls.

This is what an agent or operator runs once before hitting any indexer endpoint gated by `Full Access` — anything that mutates an account, manages keys, publishes signed data, etc. The plain API key is necessary on every call but not sufficient for those routes; the API key is the *app* scope, the session cookie is the *user* scope.

## Wallet-agnostic by design

The CLI **never touches a private key**. Every `auth login` requires a signature produced by something else:

* `bb sign-arbitrary` — the chain binary's offline ADR-36 signer. Recommended for fully headless agents.
* **A browser wallet (MetaMask, Keplr) via `auth login --browser`** — opens `/sign` in your default browser, signs there, captures the signature on a local loopback listener. See [Sign Bridge](/for-developers/cli/sign-bridge.md) for the full reference.
* A hardware wallet, custodial signer, HSM — anything that can produce an ADR-36 / EIP-191 signature over the challenge message. For programmatic / agentic signers that need pre-built SignDoc bytes, see [`gen-tx-payload`](/for-developers/cli/sign-bridge.md#gen-tx-payload).

That separation is what makes the same `auth` surface work for both an agent on a headless box and a developer signing on their laptop.

## Subcommands

| Command                             | Purpose                                                          |
| ----------------------------------- | ---------------------------------------------------------------- |
| [`auth login`](#auth-login)         | One-shot: fetch challenge, post your signature, store the cookie |
| [`auth challenge`](#auth-challenge) | Print a challenge for two-step / external-signer flows           |
| [`auth verify`](#auth-verify)       | Two-step counterpart to `login`                                  |
| [`auth status`](#auth-status)       | List stored sessions; `--check` revalidates server-side          |
| [`auth use`](#auth-use)             | Set the active address for a network                             |
| [`auth whoami`](#auth-whoami)       | Print the active address for the resolved network                |
| [`auth logout`](#auth-logout)       | Sign out and remove the local record                             |
| [`auth path`](#auth-path)           | Print the path of the auth store                                 |

## Common flags

Every `auth` subcommand accepts the same network resolution flags as `bb api`:

| Flag              | Description                                |
| ----------------- | ------------------------------------------ |
| `--testnet`       | Use testnet API                            |
| `--local`         | Use local API (`localhost:3001`)           |
| `--url <url>`     | Custom indexer base URL                    |
| `--api-key <key>` | Override `BITBADGES_API_KEY` for this call |

Networks are isolated in the store — a mainnet and a testnet session for the same address coexist independently.

## Headless flow (recommended for agents)

The canonical agentic flow is three commands. Each step is independent — the only state that crosses them is the `~/.bitbadges/auth.json` file.

```bash
# 1. Fetch a challenge. Saves the nonce cookie locally so step 3 can replay it.
MSG=$(bb auth challenge --address bb1... | jq -r .data.message)

# 2. Sign the challenge with whatever signer you have. Headless Cosmos example:
SIG_JSON=$(bb sign-arbitrary mykey "$MSG")

# 3. Post the signature. Stores the session cookie on success.
bb auth login \
  --address    "$(echo "$SIG_JSON" | jq -r .address)" \
  --signature  "$(echo "$SIG_JSON" | jq -r .signature)" \
  --public-key "$(echo "$SIG_JSON" | jq -r .pubKey)" \
  --message    "$MSG"

# 4. Make a Full Access request. --with-session attaches the cookie.
bb api accounts get-account --body '{"address":"bb1..."}' --with-session
```

> **Why three steps and not one?** The indexer binds each challenge nonce to the response cookie it set on `getChallenge`. Without persisting that cookie locally, a follow-up `auth verify` would hit `No sign-in request found`. `auth challenge` writes a pending entry (5-minute TTL) that `auth login` consumes.

For an interactive flow with an in-process signer, `auth login` will fetch a fresh challenge inline if no pending entry exists and `--message` is not passed.

## Browser-wallet flow

The recommended path for browser-only wallets is the **sign bridge**:

```bash
bb auth login --browser --address bb1...
```

The CLI opens `/sign` in your default browser; you sign with Keplr/MetaMask; the resulting Full Access session lands in `~/.bitbadges/auth.json` automatically. No copy-paste, no `--public-key` to manage. See [Sign Bridge](/for-developers/cli/sign-bridge.md#auth-login-browser) for the full flag reference, the threat model, and SSH-tunneled dev setups.

### Manual paste-in (legacy)

If you want to do it without the bridge — useful for paranoid environments or if the browser auto-launch is undesirable — the same three-step flow works with a paste-in signature:

```bash
# 1. Fetch and print the challenge
bb auth challenge --address 0x...

# 2. Open MetaMask → "Sign Message" → paste the printed message → copy the signature

# 3. Post it back
bb auth login \
  --address 0x... \
  --signature 0x...   # ETH addresses do not require --public-key
```

ETH addresses are detected by the `0x` prefix; Cosmos addresses by `bb1`. `--public-key` is required for Cosmos signatures (the indexer's CosmosDriver verifier needs it) and ignored for ETH.

## auth login

One-shot: fetches a challenge (or reuses a pending one), POSTs the signature, persists the cookie, and marks the address active for the network.

```bash
bb auth login \
  --address <addr> \
  --signature <sig> \
  [--public-key <b64>] \
  [--message <text> | --message-file <path>] \
  [network flags]
```

| Flag                    | Description                                                                                                                                      |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `--address <addr>`      | Native address (`bb1...` for Cosmos, `0x...` for ETH). Required.                                                                                 |
| `--signature <sig>`     | Hex or base64 signature over the challenge message. Required unless `--browser` is set.                                                          |
| `--browser`             | Use the [sign bridge](/for-developers/cli/sign-bridge.md) — opens the browser, signs in the wallet there. Mutually exclusive with `--signature`. |
| `--public-key <b64>`    | Compressed pubkey, base64. Required for Cosmos when using `--signature`; auto-captured from the wallet when using `--browser`.                   |
| `--message <text>`      | Exact challenge message. Defaults to the saved pending entry.                                                                                    |
| `--message-file <path>` | Read the challenge message from a file (use `-` for stdin).                                                                                      |
| `--frontend-url <url>`  | With `--browser`: override the frontend base URL.                                                                                                |
| `--no-open`             | With `--browser`: print the sign URL instead of auto-launching the browser.                                                                      |
| `--port <n>`            | With `--browser`: pin the loopback listener port (useful for SSH-tunneled dev).                                                                  |
| `--timeout <seconds>`   | With `--browser`: how long to wait for the wallet (default 300, max 1800).                                                                       |

**Exit codes:**

* `0` — logged in, cookie stored
* `1` — signature rejected, network error, or other failure

## auth challenge

Fetch a SIWBB challenge for an address. Emits the message your signer must sign (at `data.message`) and (by default) persists the response cookie locally so a follow-up `auth login` / `auth verify` can replay it.

```bash
bb auth challenge --address bb1... [--no-save-pending]
# {
#   "ok": true,
#   "data": { "message": "...SIWBB challenge text...", "nonce": "..." },
#   "warnings": [],
#   "error": null
# }

# Extract just the message for a downstream signer:
MSG=$(bb auth challenge --address bb1... | jq -r .data.message)
```

| Flag                | Description                                                                                                                         |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `--address <addr>`  | Native address (`bb1...` or `0x...`). Required.                                                                                     |
| `--no-save-pending` | Do not persist the challenge cookie locally. Advanced — use only if you intend to re-fetch the challenge yourself before verifying. |

## auth verify

Two-step counterpart to `auth login`. Identical surface; kept as a separate subcommand for muscle-memory parity with the `challenge → verify` flow.

```bash
bb auth verify \
  --address <addr> \
  --signature <sig> \
  [--public-key <b64>] \
  [--message <text> | --message-file <path>]
```

## auth status

List stored sessions for the resolved network (or all networks with `--all`). With `--check`, additionally hits `/api/v0/auth/status` to confirm the cookie is still valid server-side.

```bash
bb auth status              # active session on mainnet
bb auth status --all        # every stored session, every network
bb auth status --check      # also revalidate server-side
```

Output lines look like:

```
mainnet   bb1abc...   Cosmos   expires=2026-05-10T03:14:15.000Z (valid) [server: signed-in]
```

## auth use

Set the active address for a network. `bb api --with-session` (without `--as-address`) uses whichever address is active for the resolved network.

```bash
bb auth use bb1abc...
bb auth use bb1xyz... --testnet
```

## auth whoami

Print the active address for the resolved network. Exits non-zero if no session is stored.

```bash
bb auth whoami
bb auth whoami --testnet
```

## auth logout

Sign out and remove the local record. Hits `/api/v0/auth/logout` server-side and deletes the entry from `auth.json`. If the server call fails (network down, cookie already expired), the local record is removed anyway.

```bash
bb auth logout                    # active session on mainnet
bb auth logout --address bb1...   # specific address
bb auth logout --all              # every session, every network
```

## auth path

Print the absolute path of the auth store. Useful for backups, audits, and shipping a session from a laptop to a headless agent box.

```bash
bb auth path
# /home/you/.bitbadges/auth.json
```

## Storage layout

`~/.bitbadges/auth.json` (mode `0600`):

```jsonc
{
  "version": 1,
  "networks": {
    "mainnet": {
      "active": "bb1abc...",
      "sessions": {
        "bb1abc...": {
          "address": "bb1abc...",
          "nativeAddress": "bb1abc...",
          "chain": "Cosmos",
          "cookieName": "bitbadges",
          "cookieValue": "...",
          "scopes": [{ "scopeName": "Full Access" }],
          "createdAt": 1746240000000,
          "expiresAt": 1746844800000,
          "indexerUrl": "https://api.bitbadges.io/api/v0"
        }
      },
      "pending": { /* short-lived challenge state, 5-min TTL */ }
    },
    "testnet": { /* ... */ },
    "local":   { /* ... */ }
  }
}
```

The store is multi-account by design. You can hold sessions for many addresses simultaneously and switch between them with `auth use`, or scope a single call with `bb api ... --as-address <addr>`.

## Using a stored session — `api --with-session`

`bb api` only attaches the cookie when you explicitly ask it to (silent session injection is a footgun):

```bash
# Use the active address for the resolved network
bb api accounts update-account-info \
  --body '{"username":"new"}' \
  --with-session

# Override which stored address to use
bb api accounts update-account-info \
  --body '{"username":"new"}' \
  --with-session --as-address bb1xyz...
```

If the resolved address has no stored session, the call exits 1 with a message pointing you at `auth login`.

## Notes and limitations

* **Scope is `Full Access`.** The indexer's `getChallenge` currently hard-codes the scope set; narrower-scope sessions (e.g. `Manage Claims` only) need a small indexer change. Tracked in the backlog.
* **Sessions auto-refresh on use.** The indexer runs `express-session` with `rolling: true` and a 7-day window — every authenticated request resets the cookie's expiry to "now + 7 days" server-side. The CLI captures the rolled `Set-Cookie` from each `--with-session` response and writes the new `expiresAt` back to `auth.json`, so a long-lived agent that makes any request inside the rolling window keeps its session indefinitely without re-logging in. Re-run `auth login` only after a stretch of full inactivity longer than the rolling window (or after explicit `auth logout`).
* **The pending challenge has a 5-minute TTL.** If you wait too long between `auth challenge` and `auth login`, the entry is stale and `login` will fetch a fresh challenge (which will not match the message you signed).
* **Shipping `auth.json` between machines is fine** — the cookie is just an HTTP cookie. Move the file with `scp` or equivalent; the receiving box can immediately `bb api --with-session`. Keep it `0600`.

## See also

* [Chain Commands — `sign-arbitrary`](/for-developers/cli/chain-commands.md) — the recommended headless signer that pairs with `auth login`.
* [API Commands](/for-developers/cli/api-commands.md) — every indexer route, including `--with-session` / `--as-address`.
* [CLI for AI Agents](/for-developers/cli/for-ai-agents.md) — end-to-end agent workflow.
