Sign-in provider setup
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:
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#appleMethods 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.mdsections and.env.exampleblock 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-providerscommand andGET /ops/auth/providers.
Implementation notes (2026-09-15)#
- The status comes from one place,
Config.signInMethodsininternal/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_ORIGINSor native app IDs withoutWEBAUTHN_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 andAUTH_PROVIDERS.md. aps new --preset fullprintsAUTH_PROVIDERS.mdin its next steps, and the docs URL onlocalhost, 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.