Skip to content

[Feature]: Google / OIDC sign-in for the admin #285

Description

@ryantthomas

Problem

There's no way to sign in to the admin except email + password. For a single-author install that's fine. It gets awkward for the case the README points Postgres mode at — "Multiple authors, managed backups, room to grow" — because onboarding a team means every author gets a separate Instatic-specific password to choose, store, and rotate, on top of the SSO their organization already has.

It also puts weight on password recovery: #166 (password reset) is still open, and for orgs already on Google Workspace, SSO sidesteps that class of problem rather than solving it.

I couldn't find an existing request for this. The OAuth already in the tree (#122, #245, server/ai/mcp/oauth/) points outward at AI providers, or makes Instatic an authorization server for MCP clients. This is the opposite direction: Instatic as an OIDC relying party.

Proposed solution

Smallest useful version: Google sign-in as an additional login method, never a replacement. Password login keeps working exactly as it does today.

  1. An existing user links a Google identity from Account → Security.
  2. The login form grows a "Sign in with Google" button that authenticates a user who has linked one.
  3. No auto-provisioning in v1 — an unrecognized Google account is rejected, not silently turned into a user.

That keeps v1 additive: no existing flow changes, and the blast radius is a new identities table plus one route pair.

Two structural questions I'd want your call on before writing anything:

1. Where the identity lives. users.password_hash is text not null (migrations-sqlite.ts:78). An SSO-only user eventually has no password, and SQLite can't drop a NOT NULL without a table rebuild, which CLAUDE.md rules out. So a sentinel hash, or a separate additive user_identities table (user_id, provider, subject, linked_at)? I'd lean to the separate table — purely additive on both dialects, leaves users untouched, and generalizes to a second provider for free.

2. Client secret storage. Reuse server/secrets/masterKey.ts — the same encryption-at-rest path that protects TOTP seeds — rather than a bare env var?

There are follow-on questions I've thought through but won't front-load here (whether TOTP should still apply after an SSO login, what the lockout counter means when there's no password step, and whether this is worth doing as generic OIDC instead of Google-specific — the flow is provider-agnostic, so Microsoft/Okta/Authentik come nearly free). Happy to go into any of them if the idea is of interest.

Alternatives considered

Put an auth proxy in front. Self-hosters can get this today with Caddy forward_auth plus oauth2-proxy or Authelia, terminating SSO before traffic reaches Instatic. It works and needs no code — probably worth documenting in docs/deployment/ regardless of whether this lands. The gaps: it authenticates at the edge with no notion of which Instatic user arrived, so capabilities and the audit log can't distinguish authors; it's an extra service to run; and it doesn't help the Railway one-click path.

Do nothing and just document the proxy route. Entirely reasonable if SSO isn't somewhere you want core to go — I noticed auth isn't in the README's roadmap, so I'd rather ask than assume.

Happy to implement whichever shape you'd accept. Not starting anything until you weigh in, since this touches auth and I'd rather not guess at the design.

Area

Other (auth / access)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions