Configuration#
AuthHero uses environment variables for all configuration. Every variable is validated at startup using Zod — if anything is missing or invalid, the server will fail fast with a clear error message.
The .env File#
Copy the example file and fill in your values:
cp .env.example .envRequired Variables#
These must be set for AuthHero to start:
Server#
| Variable | Default | Description |
|---|---|---|
PORT | 5000 | Server port |
NODE_ENV | development | development | production | test |
Database#
| Variable | Default | Description |
|---|---|---|
DATABASE_URL | — | PostgreSQL connection string. Format: postgresql://USER:PASS@HOST:PORT/DB |
DATABASE_URL=postgresql://postgres:password@localhost:5432/authheroRedis#
| Variable | Default | Description |
|---|---|---|
REDIS_HOST | localhost | Redis server hostname |
REDIS_PORT | 6379 | Redis server port |
JWT Secrets#
| Variable | Min Length | Description |
|---|---|---|
ACCESS_TOKEN_SECRET | 10 chars | Signs access tokens (15-minute JWTs) |
REFRESH_TOKEN_SECRET | 10 chars | Not used for JWT signing — used for session validation context |
Generate each with:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"Never commit your .env file to version control. Add it to .gitignore. In production, use your hosting provider's secrets manager.
MFA Secrets#
| Variable | Format | Description |
|---|---|---|
MFA_ENCRYPTION_KEY | 64-char hex (32 bytes) | AES-256-GCM key for encrypting TOTP secrets at rest |
MFA_TEMP_TOKEN_SECRET | Min 10 chars | Signs the short-lived JWT issued after login when MFA is enabled |
The MFA_ENCRYPTION_KEY must be exactly 64 hex characters (32 bytes). Generate with:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"If you lose the MFA_ENCRYPTION_KEY, all existing MFA TOTP secrets become unrecoverable. Users will need to re-setup MFA. Back up this key securely.
Email (SMTP)#
| Variable | Default | Description |
|---|---|---|
EMAIL_HOST | smtp.gmail.com | SMTP server hostname |
EMAIL_PORT | 465 | SMTP server port (465 = TLS, 587 = STARTTLS) |
EMAIL_USER | — | SMTP username / sender email address |
EMAIL_PASS | — | SMTP password or app-specific password |
URLs#
| Variable | Default | Description |
|---|---|---|
APP_URL | http://localhost:5000 | Backend URL — used in OAuth redirect URIs |
FRONTEND_URL | — | Frontend URL — used for CORS, email verification links, and password reset links |
If not set, APP_URL is used as the fallback for email links and CORS. Set it when your frontend runs on a different domain or port.
OAuth Variables (Optional)#
Only configure the providers you want to use. Unconfigured providers will return a clear error if someone tries to use them.
Google#
| Variable | Description |
|---|---|
GOOGLE_CLIENT_ID | OAuth client ID from Google Cloud Console |
GOOGLE_CLIENT_SECRET | OAuth client secret |
GOOGLE_REDIRECT_URI | Callback URL (e.g. http://localhost:5000/auth/oauth/callback/google) |
GitHub#
| Variable | Description |
|---|---|
GITHUB_CLIENT_ID | OAuth App client ID from GitHub Developer Settings |
GITHUB_CLIENT_SECRET | OAuth App client secret |
GITHUB_REDIRECT_URI | Callback URL (e.g. http://localhost:5000/auth/oauth/callback/github) |
Facebook#
| Variable | Description |
|---|---|
FACEBOOK_CLIENT_ID | App ID from Facebook Developers portal |
FACEBOOK_CLIENT_SECRET | App secret |
FACEBOOK_REDIRECT_URI | Callback URL (e.g. http://localhost:5000/auth/oauth/callback/facebook) |
How Validation Works#
At startup, all environment variables are validated using a Zod schema in src/config/env.ts. If any required variable is missing or malformed, the server crashes immediately with a descriptive error:
const envSchema = z.object({
PORT: z.string().default("5000"),
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
DATABASE_URL: z.string(),
ACCESS_TOKEN_SECRET: z.string().min(10),
REFRESH_TOKEN_SECRET: z.string().min(10),
MFA_ENCRYPTION_KEY: z.string().length(64, "Must be a 64-char hex string"),
MFA_TEMP_TOKEN_SECRET: z.string().min(10),
// ...
});
const parsedEnv = envSchema.safeParse(process.env);
if (!parsedEnv.success) {
const fields = parsedEnv.error.flatten().fieldErrors;
const missing = Object.entries(fields)
.map(([key, msgs]) => ` ${key}: ${msgs?.join(", ")}`)
.join("\n");
throw new Error(`AuthHero — invalid environment variables:\n${missing}`);
}This fail-fast approach means you'll never get a confusing runtime error because of a missing config — you'll see exactly what needs to be fixed immediately.
Full .env.example#
# ─── Server ──────────────────────────────────────────────
PORT=5000
NODE_ENV=development
# ─── Database (PostgreSQL) ────────────────────────────────
DATABASE_URL=postgresql://user:password@localhost:5432/authhero
# ─── Redis ────────────────────────────────────────────────
REDIS_HOST=localhost
REDIS_PORT=6379
# ─── JWT Secrets ──────────────────────────────────────────
# Generate: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
ACCESS_TOKEN_SECRET=
REFRESH_TOKEN_SECRET=
# ─── MFA ──────────────────────────────────────────────────
# 64-char hex string (32 bytes) for AES-256-GCM encryption
MFA_ENCRYPTION_KEY=
MFA_TEMP_TOKEN_SECRET=
# ─── Email (SMTP) ────────────────────────────────────────
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=465
EMAIL_USER=your-email@gmail.com
EMAIL_PASS=your-app-password
# ─── Frontend URLs ───────────────────────────────────────
APP_URL=http://localhost:5000
FRONTEND_URL=http://localhost:3000
# ─── OAuth Providers (optional) ──────────────────────────
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI=http://localhost:5000/auth/oauth/callback/google
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GITHUB_REDIRECT_URI=http://localhost:5000/auth/oauth/callback/github
FACEBOOK_CLIENT_ID=
FACEBOOK_CLIENT_SECRET=
FACEBOOK_REDIRECT_URI=http://localhost:5000/auth/oauth/callback/facebookBuilt-in Constants#
Token lengths, expiry durations, and other magic numbers are centralized in src/config/constants.ts. These are not configurable via environment variables — change them in source code if needed:
| Constant | Value | Description |
|---|---|---|
TOKEN_LENGTH.VERIFICATION | 36 bytes (72 hex chars) | Email verification & password reset tokens |
TOKEN_LENGTH.REFRESH | 40 bytes (80 hex chars) | Refresh tokens |
TOKEN_LENGTH.OAUTH_CODE | 32 bytes (64 hex chars) | OAuth one-time codes |
TOKEN_LENGTH.OAUTH_STATE | 32 bytes (64 hex chars) | CSRF state for OAuth |
TOKEN_EXPIRY.ACCESS_TOKEN | 15 minutes | JWT access token lifetime |
TOKEN_EXPIRY.REFRESH_TOKEN_DAYS | 30 days | Refresh token cookie max-age |
TOKEN_EXPIRY.EMAIL_VERIFICATION_MINUTES | 10 minutes | Verification link lifetime |
TOKEN_EXPIRY.PASSWORD_RESET_MINUTES | 15 minutes | Password reset link lifetime |
TOKEN_EXPIRY.MFA_TEMP_TOKEN | 5 minutes | MFA challenge window |
TOKEN_EXPIRY.OAUTH_CODE_TTL_SECONDS | 120 seconds | OAuth code Redis TTL |
MFA.BACKUP_CODE_COUNT | 8 | Number of backup codes generated |