Auth & Sessions¶
Dashboard login — §39.1 #67, designed. SSO-only at MVP (GitHub + Google, FR-005), enforced by the
§13 API binary; permissions are a separate concern
(RBAC §15). (Design record:
specs/2026-07-11-auth-sessions-design.md.)
Login flow¶
- Stardeck sends the user to
GET /auth/{provider}/login→ Starbase redirects to the provider via the §11SSOProvider(AuthURL, withstateCSRF nonce). - Provider redirects back →
ExchangeCodereturns the verified profile. - Find-or-create with auto-linking: match
sso_identitieson(provider, provider_user_id); if none, matchuserson the verified email and link a new identity to that account; if still none, create the user. One person, one account, any provider — both MVP providers verify emails, which is what makes the email match safe. - Mint the session (below), set cookies, redirect to the dashboard.
Token model¶
- Access token: JWT, 15 min, claims kept skinny —
sub(user id),sid(session id),iat/exp. Roles and workspace membership are not in the token: the §15.6 middleware resolves thePermissionContextper request, so role changes and removals take effect immediately. Signed HS256 with a key from the §39.1 #18 catalog (the API is the only issuer and verifier at MVP). - Refresh token: opaque random value, stored hashed in
sessions, rotated on every use — each refresh issues a new token and marks the old rowreplaced_by. Reuse of a rotated-out token revokes the whole session family (stolen-token detection). Lifetime: 30-day sliding window, 90-day absolute cap. - Transport — httpOnly cookies, never JavaScript-readable:
Secure; HttpOnly; SameSite=Lax, scoped to.starform.io(Stardeck onapp., API onapi.— same site, cookies flow on XHR). Customer apps live onstarform.app(DNS & Domains) and can never see these cookies. CSRF:SameSite=Laxplus a required custom header on mutating requests. - Revocation: logout revokes the session row; "log out everywhere" revokes all of the user's rows; both are immediate for refresh and ≤15 min for in-flight access tokens.
Tables¶
Auth tables · sessions + SSO identities · Starbase Postgres
sso_identities (
id UUID PRIMARY KEY,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
provider TEXT NOT NULL CHECK (provider IN ('github', 'google')),
provider_user_id TEXT NOT NULL,
email TEXT NOT NULL, -- as verified by the provider at link time
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE (provider, provider_user_id)
);
sessions (
id UUID PRIMARY KEY, -- the JWT `sid` claim
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
refresh_token_hash TEXT NOT NULL, -- SHA-256; plaintext only in the cookie
family_id UUID NOT NULL, -- rotation chain; reuse ⇒ revoke the family
replaced_by UUID REFERENCES sessions(id),
ip INET,
user_agent TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
last_used_at TIMESTAMPTZ DEFAULT NOW(),
expires_at TIMESTAMPTZ NOT NULL, -- min(last_used + 30 d, created + 90 d)
revoked_at TIMESTAMPTZ
);
Deferred (§39.3 #25)
Password reset (N/A while login is SSO-only) · MFA · additional SSO providers (GitLab/Bitbucket interfaces exist, §11).
Cross-references
SSO provider interface → §11 · enforced in the API binary →
§13 · permission resolution the sid/sub feed →
§15.6 · the rest of the platform schema →
Database Schema · why customer apps can't touch these cookies →
DNS & Domains. Canonical map:
Canonical Sources.