my-login.app

A self-hosted identity service for the people who sign up for Innesta's apps. One account and one sign-in for every app a person connects, OpenID Connect for the apps, and OAuth for AI assistants reaching each app's tools.

What it does

People

Sign up with an email code or Google. Secure the account with a passkey (up to 10) or an authenticator app with 5 single-use recovery codes. One profile holds names, email, picture, passkeys, connections, a data export and account deletion. Signing out anywhere signs out everywhere.

Apps

Standard OpenID Connect with a stable per-pool identifier, the verified email, names and picture. Sign-in pages carry the app's own look. Each app has an allow list of exact emails and domains, changed without a deploy. Signed notices arrive when an email changes or an account is deleted.

AI clients

One-click MCP connecting with no pre-registration. The person signs in and approves the app name, redirect host and scopes. A hub at mcp.my-login.app relays the tools of every connected app, named by app.

Operator

One Worker serves every pool. A new app is configuration, not code. Alerts reach privacy@my-login.app.

Out of scope: passwords, SMS codes, "trust this device", personal API keys, an operator console, native mobile apps, and offering the service to other companies.

High-level architecture

Person's browser sign-in · profile App OpenID Connect AI client MCP + OAuth Cloudflare my-login.app auth.my-login.app · mcp.my-login.app Router host name → pool, else 404 Better Auth 1.7.17 one instance per pool Pages, consent, profile · hub relaying app tools hub mints a 5-minute token per call; never forwards the client's D1 one database per pool EU jurisdiction Limiter Durable Object, EU every limit fails closed Cron notices every 5 min cleanup hourly Outside providers Resend email codes, notices Google Identity Google sign-in Turnstile bot check
  • Pools. A pool is a set of accounts plus the apps registered in it. The Worker picks the pool from the host name alone, never from a header, path or query. An unknown host gets 404 and touches no database. Each pool has its own database, library secret, signing keys, Google client and Turnstile widget.
  • Tokens. Access tokens are ES256 JWTs that last one hour and are bound to one resource (aud). Refresh tokens last 30 days by default, configurable per app, and rotate on every use.
  • Email. Resend sends the codes and notices. The service keeps its own daily budget of 90 code emails, shared equally between pools. When it runs out, passkey sign-in still works.
  • Failure behaviour. If Google is down, codes and passkeys still work. If the Limiter is down, no code is sent. If D1 is down, sign-in shows a plain error and writes nothing partial. Notices retry for 7 days.

Security

Pool isolationEach pool has its own database, library secret, signing keys and issuer. The registry refuses a resource or app domain that two pools share.
Sign-inEmail codes are 6 digits, allow 3 attempts and last 5 minutes. Turnstile guards the email step. Send limits are checked in the Limiter and fail closed, and responses do not reveal whether an address has an account. Passkeys are bound to the app domain and use single-use 5-minute challenges. An account with an authenticator app needs its code after the email code. Authenticator keys are encrypted with AES-GCM, and recovery codes are stored only as HMACs.
OAuth / OIDCPKCE S256, exact redirect match, and single-use 10-minute codes. iss is in every response. Refresh-token reuse after 30 seconds revokes the whole family. Notices are ES256-signed. Keys rotate every 90 days. Only a fixed list of library endpoints is reachable from outside.
SessionsThe session is read from the database on every request, with no cookie cache, so a revocation takes effect at once. The cookie is host-only, Secure, HttpOnly and SameSite=Lax. Email change, passkeys, the authenticator, the data export and deletion need a sign-in within the last hour.
WebStrict CSP with no third-party script except Turnstile, frame-ancestors 'none', HSTS, an origin check on every form, and return paths confined to the sign-in host.
HubThe hub never forwards a client's token. Each call gets a fresh 5-minute token carrying only the scopes the person granted. Tool groups start at Read, or None for personal details. The hub allows 120 requests a minute per network and per account.
PrivacyGDPR and CCPA compliant: a record of processing, a privacy notice on every page, an EU representative, self-service data export and deletion, and breach reporting within 72 hours. All data is in the EU. Logs hold no personal data, and query strings are redacted. Alerts carry counts only. Ended sessions, codes and tokens are removed within a day, and an inactive account is deleted after one year, with a warning first. Secrets live only as Worker secrets. D1 Time Travel can restore 7 days back.
RisksGoogle's verified email is trusted. On cold code paths, CPU time measured above the 10 ms per-request limit.