apistockdocs
v0.4 GitHub apistock.dev
Guides/Set up sign-in

Every key and credential

One page with every value a Full app reads: what it is, whether you need it, how you get it, and a complete .env at the end. Nothing here is invented: each name is read by your app's internal/app code or its compose.yaml, and .env.example lists them all.

How each value is obtained#

Label Meaning
Generated by you You create it on your computer with a command
Created by aps aps dev fills it in .env for development
Copied from a dashboard A website (Google, Apple, Resend…) shows it and you copy it
Downloaded file A website gives you a file to keep
Chosen by you Your own domain, address or name; nobody issues it
Built into the app Created by the app while it runs; you never set it
Default is fine Has a working default; change only if needed

Values you never create#

People often look for these. No manually generated key is required for any of them.

Value Why you don't need one
JWT secret The app doesn't use JWTs for sessions. A session is a random token; the database stores only its SHA-256 hash, so there's nothing to sign
Session secret, cookie secret Session cookies hold that random token, not signed data
CSRF secret Cross-site requests are blocked by checking the browser's Origin and Sec-Fetch-Site headers, not with tokens
Passkey server key Each passkey's key pair lives on the person's device; your app stores only public keys. See Passkeys
Apple client secret The app signs a fresh one for 5 minutes each time it talks to Apple, from your .p8 key. See Apple sign-in
Webhook secret The app receives one kind of webhook, Apple's notifications, and checks them with Apple's public keys
OAuth state or nonce keys Random single-use values stored in the database for a few minutes
TOTP secrets Created for each person when they turn on an authenticator app, encrypted with AUTH_ENCRYPTION_KEYS
Database password on your computer compose.yaml sets it to your app's name; it only listens on 127.0.0.1

Required to start#

Variable Development Production Secret? Obtained
DATABASE_URL Yes: already in .env Yes Yes Development: created by aps (copied from .env.example). Production: copied from a dashboard (your database provider)
AUTH_ENCRYPTION_KEYS Yes for 2FA and seed data Yes Yes Development: created by aps. Production: generated by you, see Encryption key
RESEND_API_KEY (or the SMTP_ values) No: Mailpit is used Yes Yes Copied from a dashboard, see Email sending

Generate a production encryption key:

terminal
echo "k1:$(openssl rand -base64 32)"

Sign-in methods#

Variable Needed for Secret? Obtained Guide
WEBAUTHN_RP_ID Passkeys in production No Chosen by you: your domain Passkeys
WEBAUTHN_ORIGINS Passkeys in production No Chosen by you: your https frontends Passkeys
WEBAUTHN_APPLE_APP_IDS Passkeys in iOS apps No Copied from a dashboard: Team ID + bundle ID Mobile passkeys
WEBAUTHN_ANDROID_APPS Passkeys in Android apps No Copied from a dashboard: package name + SHA-256 fingerprints Mobile passkeys
APP_PUBLIC_URL Google or Apple on the web, in production No Chosen by you: your API's https address Google
GOOGLE_CLIENT_ID Google No Copied from a dashboard: Google Cloud Console Google
GOOGLE_CLIENT_SECRET Google Yes Copied from a dashboard, shown when the client is created Google
GOOGLE_IOS_CLIENT_ID Google in iOS apps No Copied from a dashboard Google
GOOGLE_ANDROID_CLIENT_ID Google in Android apps No Copied from a dashboard Google
APPLE_TEAM_ID Apple No Copied from a dashboard: Apple Developer Apple
APPLE_SERVICES_ID Apple on websites No Chosen by you, registered at Apple Apple
APPLE_KEY_ID Apple No Copied from a dashboard Apple
APPLE_PRIVATE_KEY_FILE or APPLE_PRIVATE_KEY Apple Yes Downloaded file (.p8), once Apple
APPLE_BUNDLE_IDS Apple in iOS apps No Copied from Xcode Apple

Email#

Variable Needed when Secret? Obtained
MAIL_DELIVERY Only to try the real provider locally (provider) No Default is fine: mailpit in development, provider in production
RESEND_API_KEY Resend, real email Yes Copied from a dashboard: Resend → API Keys, shown once
SMTP_HOST, SMTP_PORT, SMTP_TLS, SMTP_USERNAME SMTP, after aps add mail --provider smtp No Copied from a dashboard: your provider's SMTP settings
SMTP_PASSWORD SMTP with a username Yes Copied from a dashboard
MAILPIT_SMTP_ADDR Development No Default is fine: 127.0.0.1:1025

Server and infrastructure#

Variable Default Secret? Obtained
APP_ENV development No Chosen by you: production on servers (the Docker image sets it)
APP_ADDR 127.0.0.1:8080 No Default is fine on your computer; 0.0.0.0:8080 in containers (the Docker image sets it)
APP_LOG_LEVEL info No Default is fine
APP_DOCS_ENABLED true No Chosen by you
APP_CORS_ORIGINS Empty No Chosen by you: your web frontends
APP_MAX_BODY_BYTES 1048576 (1 MiB) No Default is fine
APP_DB_MAX_CONNS 10 No Default is fine
APP_JOB_WORKERS 10 No Default is fine
OTEL_EXPORTER_OTLP_ENDPOINT Empty No Copied from a dashboard of your observability service, or set by aps dev --observability
POSTGRES_PORT, MAILPIT_SMTP_PORT, MAILPIT_WEB_PORT, GRAFANA_PORT, OTLP_HTTP_PORT 5432, 1025, 8025, 3000, 4318 No Default is fine: change only when a port is taken on your computer; used by compose.yaml, not the app

Secrets from files#

For any secret, you can set NAME_FILE to a file's path instead of NAME, and the app reads the value from the file: DATABASE_URL_FILE, AUTH_ENCRYPTION_KEYS_FILE, GOOGLE_CLIENT_SECRET_FILE, APPLE_PRIVATE_KEY_FILE, RESEND_API_KEY_FILE, SMTP_PASSWORD_FILE. Set one or the other: if both are set, the app refuses to start.

A complete development .env#

What aps dev writes for an app named acme-api, with Google and Apple added. Values in <…> are yours.

terminal
# ── Server ─────────────────────────────── default is fine
APP_ENV=development
APP_ADDR=127.0.0.1:8080
APP_LOG_LEVEL=info
APP_DOCS_ENABLED=true
APP_CORS_ORIGINS=                        # chosen by you: e.g. http://localhost:3000 for a local frontend
APP_MAX_BODY_BYTES=1048576
OTEL_EXPORTER_OTLP_ENDPOINT=             # optional

# ── Database ───────────────────────────── created by aps (from compose.yaml)
DATABASE_URL=postgres://acme-api:acme-api@127.0.0.1:5432/acme-api?sslmode=disable
POSTGRES_PORT=5432                       # change both this and DATABASE_URL if 5432 is taken
APP_DB_MAX_CONNS=10
APP_JOB_WORKERS=10

# ── Two-factor authentication ──────────── created by aps
AUTH_ENCRYPTION_KEYS=k1:<44 characters of base64>

# ── Passkeys ───────────────────────────── empty: localhost works
WEBAUTHN_RP_ID=
WEBAUTHN_ORIGINS=
WEBAUTHN_APPLE_APP_IDS=                  # optional: copied from Apple Developer + Xcode
WEBAUTHN_ANDROID_APPS=                   # optional: copied from Play Console / keytool

# ── Google and Apple ───────────────────── optional
APP_PUBLIC_URL=                          # empty means http://localhost:8080
GOOGLE_CLIENT_ID=<id>.apps.googleusercontent.com          # copied from Google Cloud Console
GOOGLE_CLIENT_SECRET=GOCSPX-<secret>                      # copied from Google Cloud Console (secret)
GOOGLE_IOS_CLIENT_ID=                                     # optional
GOOGLE_ANDROID_CLIENT_ID=                                 # optional
APPLE_TEAM_ID=<10 characters>                             # copied from Apple Developer
APPLE_SERVICES_ID=com.example.web                         # chosen by you, registered at Apple
APPLE_KEY_ID=<10 characters>                              # copied from Apple Developer
APPLE_PRIVATE_KEY_FILE=/Users/you/.config/acme-api/AuthKey_<KEY ID>.p8   # downloaded file (secret)
APPLE_BUNDLE_IDS=                                         # optional: your iOS app

# ── Email ──────────────────────────────── Mailpit, nothing to set
MAIL_DELIVERY=
MAILPIT_SMTP_ADDR=127.0.0.1:1025
MAILPIT_SMTP_PORT=1025
MAILPIT_WEB_PORT=8025
GRAFANA_PORT=3000
OTLP_HTTP_PORT=4318
RESEND_API_KEY=                          # only with MAIL_DELIVERY=provider

Web sign-in with Apple needs an https address, so on your computer it only works through a tunnel (Apple sign-in, step 8).

A complete production environment#

Set these in your hosting provider's secret settings, never in a file in your repository.

terminal
APP_ENV=production                        # the Docker image sets it
APP_ADDR=0.0.0.0:8080                     # the Docker image sets it
APP_CORS_ORIGINS=https://app.example.com  # chosen by you
APP_DOCS_ENABLED=false                    # chosen by you

DATABASE_URL=postgres://<user>:<password>@<host>:5432/<db>?sslmode=require   # copied from your database provider (secret)
AUTH_ENCRYPTION_KEYS=k1:<new key>         # generated by you (secret)
RESEND_API_KEY=re_<key>                   # copied from Resend (secret)

WEBAUTHN_RP_ID=example.com                # chosen by you
WEBAUTHN_ORIGINS=https://app.example.com  # chosen by you

APP_PUBLIC_URL=https://api.example.com    # chosen by you
GOOGLE_CLIENT_ID=<id>.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET_FILE=/run/secrets/google_client_secret   # copied from Google (secret, as a file)
APPLE_TEAM_ID=<10 characters>
APPLE_SERVICES_ID=com.example.web
APPLE_KEY_ID=<10 characters>
APPLE_PRIVATE_KEY_FILE=/run/secrets/apple_private_key          # downloaded .p8 (secret, as a file)

Then set the sender address, which is a runtime setting rather than a variable: PUT /ops/settings/mail.from_email. Before launch, go through the go-live checklist.

esc
↑↓ move↵ openesc close