Appearance
Session security
How LivePane authenticates browser sessions, how that differs from developer PATs, and what residual risk remains when a token is stored in the page.
SPA session model
The product web app and the admin SPA authenticate API calls with a Sanctum personal access token sent as:
http
Authorization: Bearer <plain-text-token>After login or register, the API returns a plain-text token. The SPA stores it in localStorage and attaches it on subsequent requests.
| App | localStorage key |
|---|---|
Product web (packages/web) | livepane.sanctum_token |
Admin SPA (packages/admin) | livepane.admin.sanctum_token |
This is not an httpOnly cookie session. Do not assume the browser hides the token from JavaScript — any script that runs in the SPA origin can read the same key.
SPA login tokens are created with full session abilities (*). That is intentional: the browser session is the user, not a scoped capability profile.
Token lifetime
SPA session tokens are issued with a configurable TTL:
| Setting | Env | Default |
|---|---|---|
| SPA token TTL | SANCTUM_SPA_TOKEN_TTL_MINUTES | 14 days (20 160 minutes) |
When the TTL elapses, the server rejects the token. The SPA then treats the user as logged out (clear local storage and send them to login).
Shorter TTL reduces the window if a token is stolen. Operators can lower SANCTUM_SPA_TOKEN_TTL_MINUTES in environments that prefer tighter sessions.
Logout and revoke-all
| Action | Endpoint | Effect |
|---|---|---|
| Logout | POST /api/logout | Revokes the current SPA token on the server, then the client clears localStorage session keys |
| Revoke all | POST /api/tokens/revoke-all | Revokes all of the user’s personal access tokens (SPA sessions and developer PATs) |
Use Sign out for a normal end of session. Use revoke-all when you believe a token may have leaked (shared machine, XSS concern, lost laptop) and you want every outstanding bearer credential invalidated at once.
The client still clears local session keys after a successful logout so a stale token is not reused from storage.
XSS residual risk
Because the SPA holds the bearer token in localStorage, any successful XSS in the SPA origin can read the token and call the API as that user (full session abilities). That is residual risk of the current model — not a separate bug class to “patch once.”
| Mitigation | Role |
|---|---|
SPA token TTL (SANCTUM_SPA_TOKEN_TTL_MINUTES) | Bounds how long a stolen token remains valid |
POST /api/tokens/revoke-all | Immediate kill-switch for all user tokens after suspected compromise |
| Future CSP | Content-Security-Policy (and related hardening) to reduce XSS surface — not claimed as shipped here |
Mitigations shrink impact; they do not make a stolen live token harmless. Treat XSS in the SPA as account compromise for the duration of the token (or until revoke-all).
SPA session tokens vs developer PATs
| SPA session token | Developer PAT | |
|---|---|---|
| How you get it | Login / register in the web or admin SPA | Settings → Developer (or admin equivalent) |
| Storage | localStorage keys above | Operator-controlled (env, secret store, MCP config) — not the SPA session keys |
| Abilities | Full session (*) | Scoped profiles (livepane.read, livepane.control, livepane.admin, …) |
| Use for | Browser UI | CLI, MCP, automation against the capability bus |
| Token management | Issued by login/register; logout / revoke-all | List / mint / delete under /api/tokens (session token required to manage) |
Capability HTTP and MCP surfaces expect a PAT Bearer token. See MCP & CLI.
Scoped PATs cannot manage tokens (list/mint/revoke) — that stays SPA/session-only so a narrow token cannot escalate by minting a broader one.
Browser / PWA + pure native iOS (no Capacitor shell)
Product clients that talk to the API:
| Client | Auth model | CORS origin |
|---|---|---|
Product SPA / installable PWA (livepane.io) | Bearer Sanctum + localStorage | Real browser origin (e.g. https://livepane.io) — list in CORS_ALLOWED_ORIGINS |
Admin SPA / PWA (admin.livepane.io) | Same Bearer model | Admin origin in CORS_ALLOWED_ORIGINS |
Pure SwiftUI iOS (packages/ios) | Bearer in Keychain; URLSession (no browser Origin) | N/A — not a CORS client |
There is no Capacitor WebView shell. Non-browser schemes (capacitor://localhost, ionic://localhost, bare https://localhost / http://localhost WebView hosts) are not merged into config/cors.php. Operators must list only real browser SPA origins. statefulApi() stays off; SANCTUM_STATEFUL_DOMAINS is not required for the token-only contract.
Production browser SPA on https://livepane.io still belongs in CORS_ALLOWED_ORIGINS when the API is on a different host (e.g. api.livepane.io).
What this page does not claim
- No
httpOnlycookie session is documented as implemented for the product or admin SPA. Auth is Bearer +localStorageas above. - CSP is listed as a future mitigation, not a current guarantee.
- Cookie-based Sanctum SPA (CSRF + session cookie) is a different Laravel pattern; LivePane’s browser apps use the token-in-storage model on this page.
Related
Pen-test schedule: claim & authz — quarterly adversarial review of claim and authorization
Getting started — account and first machine
MCP & CLI — PAT-authenticated capability surfaces
Agent residual risk & least privilege — freeform agent command surface, OS least privilege, deferred MFA and path allowlist