apistockdocs
v0.4 GitHub apistock.dev
Decisions/Records

Sign-in provider setup

ADR-0045Accepted (2026-09-15)

Status: Accepted (2026-09-15) · Amends: ADR-0024, ADR-0028, ADR-0044 · Amended by: ADR-0046 (APP_PUBLIC_URL, the Google and Apple status lines)

Context#

apistock provides passkeys, Google sign-in and Apple sign-in (v0.3), but it never owns the accounts they depend on. Each app's developer creates their own Apple Developer and Google Cloud resources and gives the app the identifiers, keys and fingerprints. Mobile apps and web frontends are built later; the backend must be ready for them now.

The maintainer's requirement (2026-09-15): developers must be told plainly what they need to provide, where to get it and where to paste it, in the .env file, in the documentation and in the terminal, so every sign-in method works once they have filled it in. ADR-0024 already promised that social providers are "disabled until credentials are configured" and that aps dev "reports their status"; ADR-0028's banner showed Google login: not configured → docs/auth-providers.md. Nothing defines what that report contains or where the instructions live.

Decision#

1. What the developer provides#

Everything is an environment variable (ADR-0031: identifiers and keys of external accounts are infrastructure and secrets, not runtime settings). An empty variable means the method is off; the rest of the app works.

Passkeys (ADR-0044)

Variable Secret Where to get it Example
WEBAUTHN_RP_ID No Your site's domain, the one users see; no account needed example.com (development: localhost)
WEBAUTHN_ORIGINS No The browser origins of your web frontend and API docs https://app.example.com
WEBAUTHN_APPLE_APP_IDS No Apple Developer → Membership details → Team ID, and your app's Bundle ID (Xcode target, or Certificates, Identifiers & Profiles → Identifiers) ABCDE12345.com.example.app
WEBAUTHN_ANDROID_APPS No Your app's package name (applicationId) and the SHA-256 fingerprint of its signing certificate: Play Console → Test and release → App integrity → App signing key certificate, or keytool -list -v -keystore <keystore> for debug builds com.example.app=SHA256:AB:CD:…

Files the backend serves for native apps (no file to upload; generated from the variables above):

Path Needed by Generated from
/.well-known/apple-app-site-association iOS passkeys (the app's Associated Domains entitlement webcredentials:<RP ID>) WEBAUTHN_APPLE_APP_IDS
/.well-known/assetlinks.json Android passkeys (Credential Manager) WEBAUTHN_ANDROID_APPS

Google sign-in (implemented with its own ADR; variable names are fixed here)

Variable Secret Where to get it
GOOGLE_CLIENT_ID No Google Cloud Console → APIs & Services → Credentials → Create credentials → OAuth client ID → Web application; first configure the OAuth consent screen
GOOGLE_CLIENT_SECRET Yes (GOOGLE_CLIENT_SECRET_FILE also works) Shown with the web client ID
GOOGLE_IOS_CLIENT_ID No Same page → OAuth client ID → iOS, with the bundle ID (for the native flow's ID tokens)
GOOGLE_ANDROID_CLIENT_ID No Same page → OAuth client ID → Android, with the package name and SHA-1 fingerprint

To register in Google Cloud: authorized redirect URI https://<your API>/v1/auth/google/callback (development: http://localhost:8080/v1/auth/google/callback).

Apple sign-in (implemented with its own ADR; variable names are fixed here)

Variable Secret Where to get it
APPLE_TEAM_ID No Apple Developer → Membership details → Team ID
APPLE_SERVICES_ID No Certificates, Identifiers & Profiles → Identifiers → Services IDs → new, with Sign in with Apple enabled (the web client ID)
APPLE_KEY_ID No Keys → new key with Sign in with Apple → Key ID
APPLE_PRIVATE_KEY_FILE Yes (or APPLE_PRIVATE_KEY with the contents) The .p8 file downloaded once when the key is created; keep it outside the repository
APPLE_BUNDLE_IDS No Your iOS apps' bundle IDs, for the native flow's ID tokens

To register at Apple: the Services ID's domain and return URL https://<your API>/v1/auth/apple/callback. Apple doesn't accept localhost for the web flow; test it through a tunnel or use the native flow (ADR-0024).

2. Where it is written#

Place Content
.env.example A commented block per method: each variable, whether it is a secret, where to get it in one line, the URLs to register, and a pointer to AUTH_PROVIDERS.md. Empty by default
AUTH_PROVIDERS.md in every Full app (next to AGENTS.md) Step-by-step setup per method with the console paths above, what to paste where, the URLs to register, how to check it works, and a "when you build the web frontend / iOS app / Android app" checklist (entitlements, Credential Manager, callback handling) for later stages
docs/guides/auth-providers.md (apistock) The same guide for the repository
AGENTS.md One row: configure sign-in methods in .env following AUTH_PROVIDERS.md; never commit the values

3. How the developer is told#

Where Behaviour
App start, development Prints a Sign-in methods block: each method ✓ on or – off, and for each off method the exact variables to set and the AUTH_PROVIDERS.md section. aps dev shows it as the app starts
App start, production Logs one line per method at info (method, configured), never values
Partial configuration Fails at start in every environment, naming the missing variables (for example GOOGLE_CLIENT_ID without GOOGLE_CLIENT_SECRET), so a half-configured provider can't silently stay off
go run ./cmd/api auth-providers Prints the same block and exits non-zero when a configured method is invalid, for CI and deploy checks
GET /ops/auth/providers Each method's status and missing variable names, never values, for the future dashboard; permission ops.auth.read (in ops_viewer and platform_admin)
aps new --preset full Next steps mention AUTH_PROVIDERS.md for passkeys, Google and Apple

Example in development:

output
Sign-in methods
  ✓ Email and password
  ✓ Two-factor authentication (authenticator apps)
  ✓ Passkeys in browsers          RP ID localhost, origins http://localhost:8080, http://localhost:3000
  – Passkeys in iOS apps          set WEBAUTHN_APPLE_APP_IDS in .env        AUTH_PROVIDERS.md#passkeys-in-ios-apps
  – Passkeys in Android apps      set WEBAUTHN_ANDROID_APPS in .env         AUTH_PROVIDERS.md#passkeys-in-android-apps
  – Google sign-in                set GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET AUTH_PROVIDERS.md#google
  – Apple sign-in                 set APPLE_TEAM_ID, APPLE_SERVICES_ID, APPLE_KEY_ID, APPLE_PRIVATE_KEY_FILE  AUTH_PROVIDERS.md#apple

Methods appear in the block once they are implemented; Google and Apple join with their ADR.

4. Frontend and mobile, later#

The backend ships everything a client needs now: the endpoints, the generated association files, and the provider callbacks when Google and Apple land. AUTH_PROVIDERS.md records what the web frontend and mobile apps must do when they are built (call navigator.credentials with the returned options, add the Associated Domains entitlement, handle the Google and Apple native SDK tokens), so those stages start from a checklist rather than a search.

Secrets#

Only GOOGLE_CLIENT_SECRET and the Apple private key are secrets: config.Secret, *_FILE supported, never printed, logged, returned by /ops or accepted as flags or runtime settings (threat 19).

Why#

  • Developers can't guess console paths and fingerprint formats; one table per method, repeated where they look (.env, docs, terminal), removes the guesswork.
  • A status block at start answers "why doesn't Google sign-in work?" before anyone asks.
  • Failing on partial configuration catches the common mistake of pasting half the credentials.
  • A status endpoint and command give the future dashboard and CI the same information without exposing values.

Trade-offs#

  • Console paths change; the guide needs occasional updates.
  • One more permission (ops.auth.read) and endpoint.
  • Printing configuration status at every development start adds a few lines of output.

Consequences#

  • ADR-0044 uses this for passkeys: its variables, generated files, status lines, AUTH_PROVIDERS.md sections and .env.example block ship with it.
  • The Google and Apple ADR adopts the variable names and status behaviour above.
  • The Full preset gains AUTH_PROVIDERS.md, the status block, auth-providers command and GET /ops/auth/providers.

Implementation notes (2026-09-15)#

  • The status comes from one place, Config.signInMethods in internal/app/providers.go: method key and name, enabled, a detail for enabled methods (relying party ID and origins, app IDs, Android packages), and the missing variables and guide section for the others. The start block, the command and the endpoint all use it.
  • Partial configuration is refused by LoadConfig: WEBAUTHN_ORIGINS or native app IDs without WEBAUTHN_RP_ID, an RP ID without origins, http origins in production, origins off the RP ID, and malformed Apple app IDs or Android fingerprints, each naming the variable and AUTH_PROVIDERS.md.
  • aps new --preset full prints AUTH_PROVIDERS.md in its next steps, and the docs URL on localhost, which passkeys need.
  • Tests: the block's lines for enabled and missing methods; the endpoint for ops_viewer, never containing the encryption key, and 403 without an ops role; every configuration error above.
esc
↑↓ move↵ openesc close