Skip to content

Portal auth and capability model

Short-lived capability token permission model used by the mikan admin, login, and session portals.

The design goal is to let users conveniently open management, login, and session-view pages while avoiding mixing “read data”, “change settings”, and “write secrets” into one permission.

InterfaceHow users get itWhat it can doToken lifetimeOne-time token?
Admin portal/admin / /pi-adminManage conversations, model, sandbox, auto-reply, workspace previews, and events. Can also generate session/login links.30 minutesNo
Login / vault portal/login / /pi-login, or generated by the admin portalStore API keys or complete built-in OAuth flows, writing credentials into the vault.15 minutesYes, on write
Session viewsession / /session / /pi-session, or generated by the admin portalView the session timeline; when interactive mode is available, also send messages from the web page back to that session.24 hoursNo

In short:

/admin → change settings, view workspace, generate other links
/link → write vault secrets or OAuth credentials
/session → view session; optionally send messages back to the session

These three pages share the same portal shell, but they do not share the same authorization token.

The admin portal is control-plane access. Anyone with an admin link can manage mikan settings and conversation state for a short time.

The admin portal can:

  • view the current user and conversation identity
  • list conversations in the working directory
  • read and update conversation model, thinking level, sandbox mount, auto-reply, and Slack reply mode
  • read and update global model, sandbox defaults, and Slack defaults
  • view limited workspace files, skills, and events metadata/files
  • delete events for the selected conversation
  • generate a session view link or login/vault link for the target conversation

The admin portal does not directly write secret values. Even when it generates a login link, the real secret write still goes through the one-time token flow of the Login / vault portal.

The Login / vault portal is the highest-risk action capability because it can write credentials.

The Login / vault portal can:

  • show the credential or OAuth onboarding form for a specified vault
  • write environment variables into that vault
  • write credential files from a preset or OAuth flow, such as config files required by supported tools
  • complete supported OAuth flows and save access tokens, refresh tokens, or credential files
  • notify the source conversation after a successful write

Important login token behavior:

  • opening the /link page does not consume the token
  • starting OAuth does not consume the token
  • completing a credential POST or OAuth callback consumes the token
  • when the same platform user creates a new login token, the old login token becomes invalid

Additional protections:

  • Credential POST routes require Content-Type: application/json.
  • When LINK_URL / MIKAN_LINK_URL is set, credential POST routes check same-origin Origin or Referer.
  • OAuth state is independent from the login token, has a 10-minute TTL, and uses a PKCE verifier.
  • Secret values are not re-rendered to the browser; existing vault summaries show only secret names and mount targets.

Session view is session content access. It is mainly used to view a structured session timeline.

Session view can:

  • render the session timeline
  • navigate parent/thread session relationships
  • subscribe to live status and timeline updates through SSE
  • when interactive wiring is available, send messages from the web page back to the selected session

Session view is not purely read-only. As long as the /session/message route exists and interactive wiring is available, a session view token can send a session_view event and call the bot handler.

The session view token is anchored to the base session file. When navigating with /session?session=<file.jsonl>, it can only switch to session files in the same directory.

RouteMethodToken sourceValidationNotes
/adminGETquery tokenadminTokenStore.peek()Render admin portal.
/admin/api/*GETquery tokenadminTokenStore.peek()Unauthorized returns 403.
/admin/api/*POSTJSON body tokenadminTokenStore.peek()Unauthorized returns 403.
/linkGETquery tokenlinkTokenStore.peek()Render login/vault page; does not consume token.
/api/link/completePOSTJSON body tokenlinkTokenStore.consume()Write credentials; consumes token.
/api/oauth/startPOSTJSON body tokenlinkTokenStore.peek() + OAuth stateCreate OAuth redirect; does not consume login token yet.
/oauth/callbackGETquery stateOAuth state + linkTokenStore.consume()Complete OAuth; consumes OAuth state and login token.
/sessionGETquery tokensessionViewTokenStore.peek()Render session page.
/session/streamGETquery tokensessionViewTokenStore.peek()Open SSE stream; requires interactive wiring.
/session/messagePOSTJSON body tokensessionViewTokenStore.peek()Send session message; requires interactive wiring.

The three token types have different risks:

  • Admin token: reusable short-lived management permission.
  • Login token: can write secrets, so it has a shorter lifetime and is consumed on write.
  • Session view token: useful for sharing and reviewing sessions, so it lasts longer, but permission is limited to the session view scope.

Even if a full dashboard is added later, these boundaries should remain:

  • Dashboard identity may authorize viewing and settings operations.
  • Secret writes should still require a short-lived one-time capability, or equivalent second confirmation.
  • Standalone session links can remain capability links for session viewing.
FeatureMain code
Portal HTTP serverstartWebServer() in src/web/server.ts
Admin portalsrc/web/admin/portal.ts, src/web/admin/store.ts
Login / vault portalsrc/web/login/portal.ts, src/web/login/store.ts
Session viewsrc/web/session-view/portal.ts, src/web/session-view/store.ts
Shared token storesrc/web/token-store.ts
Shared portal shellsrc/web/portal-shell.ts

startWebServer() dispatch order:

  1. GET /health
  2. Agent event HTTP routes
  3. Admin routes
  4. Session view routes
  5. Login / vault routes
  6. 404

The server starts only when LINK_PORT / MIKAN_LINK_PORT can be parsed as a port. If LINK_URL / MIKAN_LINK_URL is set but no port is configured, mikan uses the default port 8181.

Token stores are currently in-memory and src/main.ts cleans expired tokens every five minutes. A process restart invalidates all unexpired web tokens.

These URLs are bearer capabilities. Query-string tokens can leak through browser history, screenshots, copied URLs, or proxy logs; share them only with the intended user and never publish them in chat channels or issue trackers.