Configuration
By default, Authwall does its best to configure flows, mailers, and other integrations from whatever settings are present. But when you explicitly ask for something — a specific mailer, a specific sign-in flow, a specific cookie mode — Authwall refuses to start unless that request is fully satisfied.
This is intentional: it prevents the situation where you believe a setting took effect the way you wanted, but Authwall silently fell back to a different option.
Overview
| Varname | Short description |
|---|---|
LISTEN |
Bind address for the HTTP server |
PORT |
HTTP listen port |
AUTHWALL_SECRET |
Root secret for sessions and CSRF protection |
AUTHWALL_LOGGER |
Log destination |
AUTHWALL_PASSWORD_MIN |
Minimum password length for new passwords |
AUTHWALL_BCRYPT_ROUNDS |
bcrypt cost for new password hashes |
AUTHWALL_RATE_LIMITING |
Enables or disables in-memory rate limiting |
AUTHWALL_SENTRY_DSN |
Sentry DSN for error reporting |
AUTHWALL_SENTRY_ENVIRONMENT |
Sentry environment name |
AUTHWALL_SENTRY_TRACES_SAMPLE_RATE |
Optional Sentry tracing sample rate |
AUTHWALL_PERSONAL_ACCESS_TOKENS |
Enables bearer tokens for API clients |
AUTHWALL_WEBSOCKETS |
Proxies WebSocket connections to the upstream |
AUTHWALL_PUBLIC_URL |
Public base URL used for redirects and generated links |
AUTHWALL_PUBLIC_PATHS |
Public upstream paths that bypass sign-in |
AUTHWALL_OPTIONAL_AUTH_PATHS |
Public paths that receive auth headers when signed in |
AUTHWALL_UPSTREAM_URL |
Upstream application URL |
AUTHWALL_UPSTREAM_MODE |
Upstream proxy behavior mode |
AUTHWALL_SET_HEADERS |
Headers to add to upstream requests |
AUTHWALL_UNSET_HEADERS |
Headers to remove from upstream requests |
AUTHWALL_DB |
Database connection URI |
AUTHWALL_SEED |
Bootstrap users created at startup |
AUTHWALL_COOKIE_DOMAIN |
Session cookie domain |
AUTHWALL_COOKIE_PATH |
Session cookie path |
AUTHWALL_COOKIE_SAMESITE |
SameSite value for the session cookie |
AUTHWALL_COOKIE_SECURE |
Whether session cookies require HTTPS |
AUTHWALL_ALLOWED_EMAILS |
Exact email addresses allowed to sign in |
AUTHWALL_ALLOWED_DOMAINS |
Email domains allowed to sign in |
AUTHWALL_DENIED_EMAILS |
Exact email addresses denied sign-in |
AUTHWALL_DENIED_DOMAINS |
Email domains denied sign-in |
AUTHWALL_CONFIRM_EMAIL_REQUIRED |
Whether a confirmed email is required for access |
AUTHWALL_CONFIRM_EMAIL |
Email-confirmation channel: link, code, or both |
AUTHWALL_MAILER |
Mailer provider selection |
AUTHWALL_RESEND_KEY |
Resend API key |
AUTHWALL_RESEND_FROM |
Resend sender address |
AUTHWALL_MAILJET_KEY |
Mailjet API key |
AUTHWALL_MAILJET_SECRET |
Mailjet API secret |
AUTHWALL_MAILJET_FROM |
Mailjet sender address |
AUTHWALL_SES_KEY |
AWS access key id for SES |
AUTHWALL_SES_SECRET |
AWS secret access key for SES |
AUTHWALL_SES_REGION |
AWS SES region |
AUTHWALL_SES_SESSION_TOKEN |
Optional AWS session token for SES |
AUTHWALL_SES_FROM |
AWS SES sender address |
AUTHWALL_FLOWS |
Enabled sign-in flows |
AUTHWALL_MAGIC_LINK |
Magic-link and magic-code mode |
AUTHWALL_GOOGLE_CLIENT_ID |
Google OAuth client id |
AUTHWALL_GOOGLE_CLIENT_SECRET |
Google OAuth client secret |
AUTHWALL_GOOGLE_REDIRECT_URL |
Google OAuth redirect URL |
AUTHWALL_GITHUB_CLIENT_ID |
GitHub OAuth client id |
AUTHWALL_GITHUB_CLIENT_SECRET |
GitHub OAuth client secret |
AUTHWALL_GITHUB_REDIRECT_URL |
GitHub OAuth redirect URL |
AUTHWALL_FACEBOOK_CLIENT_ID |
Facebook OAuth client id |
AUTHWALL_FACEBOOK_CLIENT_SECRET |
Facebook OAuth client secret |
AUTHWALL_FACEBOOK_REDIRECT_URL |
Facebook OAuth redirect URL |
AUTHWALL_MICROSOFT_CLIENT_ID |
Microsoft OAuth client id |
AUTHWALL_MICROSOFT_CLIENT_SECRET |
Microsoft OAuth client secret |
AUTHWALL_MICROSOFT_REDIRECT_URL |
Microsoft OAuth redirect URL |
AUTHWALL_TWITTER_CLIENT_ID |
X OAuth client id |
AUTHWALL_TWITTER_CLIENT_SECRET |
X OAuth client secret |
AUTHWALL_TWITTER_REDIRECT_URL |
X OAuth redirect URL |
AUTHWALL_DISCORD_CLIENT_ID |
Discord OAuth client id |
AUTHWALL_DISCORD_CLIENT_SECRET |
Discord OAuth client secret |
AUTHWALL_DISCORD_REDIRECT_URL |
Discord OAuth redirect URL |
Server
Where the Authwall HTTP server binds.
LISTEN— bind address. Default:127.0.0.1when running from source; the published Docker image bakes inLISTEN=0.0.0.0so the container is reachable on every interface. Override to a specific address to bind to one interface.PORT— TCP port. Default:3000.
These configure the local listener only; the externally visible URL is set separately via AUTHWALL_PUBLIC_URL.
Example:
LISTEN=0.0.0.0
PORT=8000AUTHWALL_SECRET
Root secret used to derive Authwall's session secret.
- Type: string
- Default: generated automatically and stored in
data/secret.key - Validation: must be at least 32 characters when set
Set this explicitly when secrets are managed by the runtime, orchestrator, or an external secret store.
If it is not set, Authwall loads data/secret.key;
if that file does not exist, Authwall generates a random secret and writes it there.
Rotating this value invalidates existing sessions and CSRF tokens.
If the value (env var or
data/secret.key) is shorter than 32 characters, Authwall refuses to start.
Example:
AUTHWALL_SECRET=$(bin/random-secret)AUTHWALL_LOGGER
Where Authwall writes its log output.
- Type: enum
- Values:
daily,stdout - Default:
dailywhen running from source; the published Docker image bakes inAUTHWALL_LOGGER=stdout
Use daily to write to a date-stamped file under data/logs/, named app-YYYY-MM-DD.log and rotated automatically when the date changes.
Use stdout to write to standard output, which is the right choice for containerized deployments where a process supervisor or log collector picks up stdout.
Example:
AUTHWALL_LOGGER=stdout
Passwords
Controls how Authwall accepts and stores passwords.
AUTHWALL_PASSWORD_MIN— minimum length for new passwords (sign-up, password change, password reset). Type: integer in[4, 32]. Default:8. Existing shorter hashes continue to work on sign-in; the limit is only enforced when a password is set.AUTHWALL_BCRYPT_ROUNDS— bcrypt cost factor for new password hashes and magic-code hashes. Type: integer in[4, 31]. Default:12. Each step roughly doubles hashing time; raising this hardens hashes against offline attacks but slows every sign-in proportionally.
If either value is out of range, Authwall refuses to start.
Example:
AUTHWALL_PASSWORD_MIN=12
AUTHWALL_BCRYPT_ROUNDS=13AUTHWALL_RATE_LIMITING
Toggles Authwall's built-in per-IP rate limiting.
- Type: string flag
- Values:
0to disable; any other value (or unset) leaves it enabled - Default: enabled
When enabled, the following endpoints are rate-limited per client IP:
- Sign-in — 10 requests per 15 minutes.
- Sign-up — 5 requests per hour.
- Password reset — 5 requests per hour.
- Magic-link request — 5 requests per hour.
- Personal access token creation — 5 requests per hour.
- Failed bearer-token validation — 20 requests per 15 minutes (returns
429withRetry-After).
Counts are tracked in memory only, so they do not persist across restarts and are not shared between processes. Disable rate limiting only in environments where requests are throttled by an upstream proxy or load balancer, or in tests where the limits would interfere.
Example:
AUTHWALL_RATE_LIMITING=0
Sentry
Configures Sentry error reporting for the Node/Express process.
AUTHWALL_SENTRY_DSN— enables Sentry when set. Leave unset to disable Sentry.AUTHWALL_SENTRY_ENVIRONMENT— optional environment label, such asproductionorstaging.AUTHWALL_SENTRY_TRACES_SAMPLE_RATE— optional performance tracing sample rate in[0, 1]. Leave unset to disable tracing.
Authwall registers Sentry's Express error handler before its own redirecting error handler, so exceptions are reported while users still receive Authwall's normal error redirect behavior.
Authwall does not enable sendDefaultPii, strips cookies and authorization headers, drops request bodies, and redacts OAuth-style code, state, and token query parameters before events are sent.
Example:
AUTHWALL_SENTRY_DSN=https://public@example.ingest.sentry.io/1
AUTHWALL_SENTRY_ENVIRONMENT=production
AUTHWALL_SENTRY_TRACES_SAMPLE_RATE=0.05
AUTHWALL_PERSONAL_ACCESS_TOKENS
Enables personal access tokens for API clients.
- Type: boolean flag
- Values:
yes,no,true,false,on,off - Default:
false
When disabled, Authwall does not expose the token management UI, does not register the token management routes, and does not accept bearer tokens for upstream authentication.
When enabled, signed-in users can create tokens from the profile page. The raw token is shown once, only a SHA-256 hash is stored, and API clients send it as:
Authorization: Bearer awp_...Valid bearer tokens authenticate proxied upstream requests and Authwall forwards
the same trusted X-Auth-User header it uses for browser sessions. Authwall
removes the bearer Authorization header before proxying the request.
Bearer tokens also work against GET /auth/status, so an API client can
introspect the signed-in identity without holding a browser session.
What bearer tokens cannot do
Bearer tokens are intentionally not accepted by the /auth/* account
management endpoints — creating or revoking personal access tokens, revoking
browser sessions, changing email or password, deleting the account, and so on.
Those flows require an active browser session and a CSRF token. A leaked PAT
can act on the upstream app as its owner, but it cannot escalate by reshaping
the owner's authwall account.
Usage from an API client
Once a user has minted a token from the profile page, an API client sends it
as the Authorization header on every request:
curl -H 'Authorization: Bearer awp_…' https://authwall.example.com/api/thingsThe same token works against /auth/status for identity introspection:
curl -H 'Authorization: Bearer awp_…' https://authwall.example.com/auth/statusTo rotate a token, revoke the old one from the profile page and create a new one. Authwall does not expose a regenerate endpoint by design; the explicit revoke + create steps keep the audit log clear about what happened.
Example:
AUTHWALL_PERSONAL_ACCESS_TOKENS=true
AUTHWALL_WEBSOCKETS
Enables proxying of WebSocket connections to the upstream app.
- Type: boolean flag
- Values:
yes,no,true,false,on,off - Default:
false
When disabled, Authwall does not handle the HTTP Upgrade event, and any
Upgrade: websocket request falls through as a normal HTTP request — which
the upstream will typically reject.
When enabled, Authwall accepts WebSocket upgrades on any path that isn't
under /auth/, authenticates the upgrade with a personal access token, sets
the same trusted X-Auth-User header it uses for HTTP requests, and forwards
the upgrade to the upstream.
Authenticating a WebSocket upgrade
The upgrade is authenticated with the Authorization header, so this
requires AUTHWALL_PERSONAL_ACCESS_TOKENS
to be enabled. The client sets the header on the handshake:
new WebSocket('wss://app.example.com/realtime', {
headers: {Authorization: 'Bearer awp_…'},
});Authwall validates the token, strips the Authorization header before
forwarding the upgrade, and shares the same failed-attempt rate limiter
used by HTTP bearer authentication.
The browser WebSocket API cannot set the Authorization header, so this
path is intended for non-browser clients such as a desktop app. There is no
browser/cookie-based WebSocket authentication yet.
Example:
AUTHWALL_WEBSOCKETS=trueAUTHWALL_PUBLIC_URL
Public base URL for Authwall. Authwall uses this value when building redirects and generated links that must point back to the Authwall service.
- Type: URL string
- Default:
http://127.0.0.1:3000
Set this to the externally visible URL users and OAuth providers use to reach Authwall. For production, this should usually be an HTTPS URL.
This value also drives the default of AUTHWALL_COOKIE_SECURE: when AUTHWALL_PUBLIC_URL starts
with https://, the session cookie's Secure attribute defaults to true
otherwise to false.
Example:
AUTHWALL_PUBLIC_URL=https://myapp.testAUTHWALL_PUBLIC_PATHS
Public upstream paths that bypass sign-in. These paths are proxied to AUTHWALL_UPSTREAM_URL with or without a session and never receive the X-Auth-User header.
- Type: list of path strings
- Default: the
public_pathslist inconfig/settings.yaml - Delimiters: comma, semicolon, or newline
Entries may be exact paths or prefix entries ending in /*. For example, /lib/* matches /lib/app.js and /lib/vendor/react.js.
When AUTHWALL_PUBLIC_PATHS is set, it replaces the public_paths list from config/settings.yaml.
Example:
AUTHWALL_PUBLIC_PATHS="/favicon.ico,/robots.txt,/lib/*,/designs/*"AUTHWALL_OPTIONAL_AUTH_PATHS
Public upstream paths that bypass sign-in for guests, but behave like private paths when a user is signed in. Anonymous requests are proxied without X-Auth-User; signed-in requests are proxied with X-Auth-User.
- Type: list of path strings
- Default: the
optional_auth_pathslist inconfig/settings.yaml - Delimiters: comma, semicolon, or newline
Entries may be exact paths or prefix entries ending in /*. This is useful for a landing page that should render guest content for anonymous users and signed-in content for authenticated users at the same URL.
When AUTHWALL_OPTIONAL_AUTH_PATHS is set, it replaces the optional_auth_paths list from config/settings.yaml.
Example:
AUTHWALL_OPTIONAL_AUTH_PATHS="/,/landing/*"AUTHWALL_UPSTREAM_URL
URL of the upstream application protected by Authwall.
Every request whose path is not under /auth is proxied here.
- Type: URL string
- Default:
http://127.0.0.1:8080
Use the URL that Authwall can reach from its own runtime environment.
In Docker Compose, this is usually a service URL such as http://echo-server:8080;
outside Docker it is often a loopback URL.
How requests reach the upstream:
- Authenticated requests — Authwall adds
X-Auth-User: <user_uid>to the proxied request. - Public paths are always proxied, with or without a session, and never receive the
X-Auth-Userheader. - Optional auth paths are proxied without requiring sign-in. Anonymous requests receive no
X-Auth-User; signed-in requests receiveX-Auth-Userand follow the same email-verification checks as private paths. - Other paths without a session — the user is redirected to the sign-in page; no upstream request is made.
Example:
AUTHWALL_UPSTREAM_URL=https://internal-service:8080AUTHWALL_UPSTREAM_MODE
Controls how Authwall rewrites requests when forwarding them to its single
upstream, AUTHWALL_UPSTREAM_URL. Choose the mode by how many domains sit behind
Authwall.
- Type: enum
- Values:
direct,proxy - Default:
direct
Authwall always forwards to exactly one upstream and cannot route by domain on its own. The mode decides whether that upstream is the app itself or a reverse proxy that fans out to several apps:
direct client → authwall → app
proxy client → authwall → reverse proxy → appsflowchart LR
subgraph direct
direction LR
dc[client] --> da[authwall] --> dapp[app]
end
subgraph proxy
direction LR
pc[client] --> pa[authwall] --> prp["reverse proxy"] --> papps[apps]
endUse direct when one app sits behind Authwall. Authwall rewrites the
Host header to the domain of AUTHWALL_UPSTREAM_URL, so the app receives the
request as if it had been sent straight to it.
Use proxy when several domains sit behind Authwall. The upstream is a
reverse proxy (such as nginx or Caddy) that routes each domain to its own app.
Authwall preserves the client's original Host header so that proxy can tell
the domains apart, and additionally sends X-Forwarded-For, X-Forwarded-Host,
and X-Forwarded-Proto.
Example:
AUTHWALL_UPSTREAM_MODE=proxyAUTHWALL_SET_HEADERS
Headers to add to requests before Authwall forwards them to AUTHWALL_UPSTREAM_URL.
- Type: semicolon-separated
Header-Name=valueentries - Default: none
- Validation: each header name and value must be valid for Node's HTTP client
Use this for static headers the upstream expects on every proxied request. Header values may be empty.
Outgoing headers are assembled in this order, so later steps override earlier ones:
- Authwall adds
X-Auth-Userfor authenticated, non-public-path requests. AUTHWALL_SET_HEADERSentries are applied (and may overwriteX-Auth-User).AUTHWALL_UNSET_HEADERSentries are removed.
Example:
AUTHWALL_SET_HEADERS='X-Team=notes;Authorization=Basic abc:def==;X-Empty='AUTHWALL_UNSET_HEADERS
Headers to remove from requests before Authwall forwards them to AUTHWALL_UPSTREAM_URL.
Removal happens last, after Authwall's own headers and AUTHWALL_SET_HEADERS have been
applied — see the order in AUTHWALL_SET_HEADERS.
- Type: semicolon-separated header names
- Default: none
- Validation: each header name must be valid for Node's HTTP client
Use this to drop headers the upstream should not see — typically session cookies or upstream-trusted
authorization headers leaking through from the client.
Authwall already strips every x-auth-* header from incoming requests in middleware,
so client-supplied X-Auth-User cannot reach the proxy in any case.
Example:
AUTHWALL_UNSET_HEADERS='X-Auth-User;X-Forwarded-User'AUTHWALL_DB
Database connection URI.
- Type: connection URI
- Default: SQLite database at
data/db.sqlite3 - Values: unset,
mysql://...,postgres://...,postgresql://...
Leave this unset for the default local SQLite database. Set it when Authwall should use MySQL or PostgreSQL instead.
If the URI uses any other scheme, Authwall refuses to start.
Examples:
AUTHWALL_DB=mysql://authwall:authwall@mysql/authwall
AUTHWALL_DB=postgres://authwall:authwall@postgres/authwallAUTHWALL_SEED
Bootstrap users created at startup. Authwall creates missing users and adds missing username or email identities for existing users. Entries with neither a username nor a valid email are logged and skipped.
- Type: compact string or JSON array
- Default: none
Compact format — username:password:emails, with multiple users separated by ;:
:separates the three fields per entry:username,password,emails.,separates multiple emails within the third field.;separates entries.- Either
usernameoremailsmay be empty, but not both.
AUTHWALL_SEED='admin:change-me:admin@myapp.test;ops:change-me:ops1@myapp.test,ops2@myapp.test'JSON format — an array of objects:
username— string, optional if at least one email is present.password— string used when the user is created.emails— string or array of strings; optional ifusernameis present.display_name— optional string shown in the profile.
AUTHWALL_SEED='[{"username":"admin","password":"change-me","display_name":"Admin","emails":["admin@myapp.test"]}]'
Session cookie
Configures the session cookie Authwall sets after sign-in.
AUTHWALL_COOKIE_DOMAIN—Domainattribute. Default: unset; the cookie is scoped to the exact host of the response.AUTHWALL_COOKIE_PATH—Pathattribute. Default:/. Values that do not start with/are normalized to/.AUTHWALL_COOKIE_SAMESITE—SameSiteattribute. Values:lax,strict,none. Default:lax.AUTHWALL_COOKIE_SECURE—Secureattribute. Values:yes,no,true,false. Default:truewhenAUTHWALL_PUBLIC_URLstarts withhttps://, otherwisefalse.
Modern browsers reject SameSite=None cookies that are not also Secure.
If
AUTHWALL_COOKIE_SAMESITE=noneis set withoutAUTHWALL_COOKIE_SECURE=true, Authwall refuses to start.
The cookie's Max-Age is fixed at 30 days and cannot be changed via env vars.
Example:
AUTHWALL_COOKIE_DOMAIN=myapp.test
AUTHWALL_COOKIE_PATH=/
AUTHWALL_COOKIE_SAMESITE=lax
AUTHWALL_COOKIE_SECURE=true
Access rules
Restricts which email addresses may sign in. All four variables are comma-separated lists. Comparison is case-insensitive (values are normalized to lowercase). Empty lists are ignored.
The four lists are checked in a fixed priority order, where each higher-priority list can override the next:
AUTHWALL_DENIED_EMAILS— highest priority. Listed addresses are always denied.AUTHWALL_ALLOWED_EMAILS— next. Listed addresses are always allowed, which is how you make per-address exceptions toAUTHWALL_DENIED_DOMAINS.AUTHWALL_DENIED_DOMAINS— block whole domains.AUTHWALL_ALLOWED_DOMAINS— allow whole domains.
When neither allow list is set, only the deny lists are enforced and everything else is allowed. When either allow list is set, the default flips to deny — addresses not matched by any rule are rejected.
Implementation:
async function authorize_email(email_normalized)
{
const [_, domain] = email_normalized.split('@');
const has_allowed_emails = config.access.allowed_emails.length > 0;
const has_allowed_domains = config.access.allowed_domains.length > 0;
if (config.access.denied_emails.includes(email_normalized)) {
throw new UserFriendlyError('Email is not allowed');
}
if (config.access.allowed_emails.includes(email_normalized)) {
return;
}
// denylist (always enforced)
if (config.access.denied_domains.includes(domain)) {
throw new UserFriendlyError('Email domain is not allowed');
}
if (has_allowed_domains && config.access.allowed_domains.includes(domain)) {
return;
}
// allowlist default deny
if (has_allowed_domains) {
throw new UserFriendlyError('Email domain is not allowed');
}
if (has_allowed_emails) {
throw new UserFriendlyError('Email is not allowed');
}
}Examples:
Only one address can sign in; everyone else is denied:
AUTHWALL_ALLOWED_EMAILS=admin@myapp.testA small allowlist — these three addresses can sign in, nobody else:
AUTHWALL_ALLOWED_EMAILS=alice@myapp.test,bob@myapp.test,carol@myapp.testAnyone at myapp.test can sign in, except one banned address — DENIED_EMAILS overrides ALLOWED_DOMAINS:
AUTHWALL_ALLOWED_DOMAINS=myapp.test
AUTHWALL_DENIED_EMAILS=fired@myapp.test
Email confirmation
Controls whether Authwall asks users to confirm an email address, and how the confirmation message is delivered.
AUTHWALL_CONFIRM_EMAIL_REQUIRED— whether a confirmed email is required to reach the protected app. Type: boolean flag. Values:yes,no,true,false,on,off. Default: unset, which resolves to enabled whenever the email sign-in flow is enabled. When enabled, users without a confirmed email are held at the confirmation step instead of being proxied upstream.AUTHWALL_CONFIRM_EMAIL— confirmation delivery mode. Type: enum. Values:auto,off,disabled,link,code,link_and_code. Default:auto.
How each AUTHWALL_CONFIRM_EMAIL value behaves:
auto— enabled when a mailer is configured, otherwise disabled. The default channel islink_and_code.off/disabled— email confirmation is disabled.link— confirmation emails contain only a clickable link.code— confirmation emails contain only a one-time code that the user types into the confirmation page.link_and_code— confirmation emails contain both.
Any value outside the list above disables confirmation and logs a warning.
If
AUTHWALL_CONFIRM_EMAILis one oflink,code, orlink_and_codebut no mailer is configured, Authwall refuses to start.
If
AUTHWALL_CONFIRM_EMAIL_REQUIREDis enabled while the email sign-in flow is disabled, Authwall refuses to start.
A few related knobs are not exposed as environment variables and are tuned in config/settings.yaml under confirm_email: expires_minutes (default 15), code_length (default 6), max_attempts (default 5), and resend_cooldown_seconds (default 60).
Example:
AUTHWALL_CONFIRM_EMAIL_REQUIRED=true
AUTHWALL_CONFIRM_EMAIL=codeAUTHWALL_MAILER
Selects which mailer Authwall uses to send sign-in, confirmation, password-reset, magic-link, and notification emails.
- Type: enum
- Values:
auto,fake,resend,mailjet,ses - Default:
auto
How each value behaves:
auto— picks the first provider whose required env vars are all set, in this order: Resend, Mailjet, Amazon SES. If none is configured, falls back tofake.fake— drops every email instead of sending. Suitable for local development and tests; not safe for production because users will not receive confirmation or password-reset emails.resend/mailjet/ses— uses the named provider explicitly.
When a provider is requested explicitly but not fully configured, Authwall refuses to start.
Example:
AUTHWALL_MAILER=resend
Resend
Configures the Resend mailer. Both variables are required together; the provider is usable only when both are set.
AUTHWALL_RESEND_KEY— Resend API key. Treat it as a secret: do not commit it or expose it to clients.AUTHWALL_RESEND_FROM— Sender address used in theFromheader. Typically formatted as"Display Name <noreply@myapp.test>". The domain must be verified in Resend.
With AUTHWALL_MAILER=auto (the default), Resend is selected automatically when both are set.
If
AUTHWALL_MAILER=resendis requested explicitly without both, Authwall refuses to start.
Obtain the API key from Resend → API Keys.
Example:
AUTHWALL_RESEND_KEY=re_...
AUTHWALL_RESEND_FROM="Authwall <noreply@myapp.test>"
Mailjet
Configures the Mailjet mailer. All three variables are required together; the provider is usable only when all of them are set.
AUTHWALL_MAILJET_KEY— Mailjet API key.AUTHWALL_MAILJET_SECRET— Mailjet API secret. Treat it as a secret: do not commit it or expose it to clients.AUTHWALL_MAILJET_FROM— Sender address used in theFromheader. Typically formatted as"Display Name <noreply@myapp.test>". The sender must be verified in Mailjet.
With AUTHWALL_MAILER=auto (the default), Mailjet is selected automatically when all three are set and Resend is not configured.
If
AUTHWALL_MAILER=mailjetis requested explicitly without all three, Authwall refuses to start.
Obtain credentials from Mailjet → Account Settings → API Key Management.
Example:
AUTHWALL_MAILJET_KEY=...
AUTHWALL_MAILJET_SECRET=...
AUTHWALL_MAILJET_FROM="Authwall <noreply@myapp.test>"
Amazon SES
Configures the Amazon SES mailer.
AUTHWALL_SES_KEY, AUTHWALL_SES_SECRET, and AUTHWALL_SES_FROM are required together; AUTHWALL_SES_REGION and AUTHWALL_SES_SESSION_TOKEN are optional.
AUTHWALL_SES_KEY— AWS access key ID.AUTHWALL_SES_SECRET— AWS secret access key. Treat it as a secret: do not commit it or expose it to clients.AUTHWALL_SES_FROM— Sender address used in theFromheader. Typically formatted as"Display Name <noreply@myapp.test>". The sender (or its domain) must be verified in SES.AUTHWALL_SES_REGION— AWS region for the SES endpoint. Defaults tous-east-1.AUTHWALL_SES_SESSION_TOKEN— Optional AWS session token for temporary credentials (e.g. STS / assumed roles). Omit when using long-lived IAM access keys.
With AUTHWALL_MAILER=auto (the default), SES is selected automatically when AUTHWALL_SES_KEY, AUTHWALL_SES_SECRET, and AUTHWALL_SES_FROM are all set and neither Resend nor Mailjet is configured.
If
AUTHWALL_MAILER=sesis requested explicitly withoutAUTHWALL_SES_KEY,AUTHWALL_SES_SECRET, andAUTHWALL_SES_FROM, Authwall refuses to start.
Obtain credentials from AWS IAM; verify the sender or its domain in the SES console for the chosen region.
Example:
AUTHWALL_SES_KEY=AKIA...
AUTHWALL_SES_SECRET=...
AUTHWALL_SES_REGION=us-east-1
AUTHWALL_SES_FROM="Authwall <noreply@myapp.test>"AUTHWALL_FLOWS
Selects which sign-in flows Authwall offers.
This is the last step of configuration: every other variable (mailer, OAuth credentials, password options, magic-link mode) is resolved first, and AUTHWALL_FLOWS then chooses among the flows those prior settings made available.
- Type:
autoor comma-separated list of flow names - Values:
auto, or any combination ofusername,email,magic_link,magic_code,magic_link_and_code,google,github,microsoft,facebook,twitter,discord - Default:
auto
How each value behaves:
auto— every flow whose prerequisites are already in place is enabled. Configure flows via their own env vars, and they show up automatically.- comma-separated list — only the listed flows are enabled, and each one must already be fully configured.
The magic_link_and_code value is shorthand for both magic_link and magic_code together.
When an explicit list names a flow that is missing its prerequisites, Authwall refuses to start.
Example — only password sign-in by username and Google:
AUTHWALL_FLOWS=username,googleAUTHWALL_MAGIC_LINK
Controls whether magic-link sign-in is enabled and which channel users get.
- Type: enum
- Values:
auto,off,disabled,link,code,link_and_code - Default:
auto
How each value behaves:
auto— enabled when a mailer is configured, otherwise disabled. The default channel islink_and_code.off/disabled— magic-link sign-in is disabled.link— emails contain only a clickable link.code— emails contain only a one-time code that the user types into the browser.link_and_code— emails contain both.
Any value outside the list above disables the flow and logs a warning.
If the value is one of
link,code, orlink_and_codebut no mailer is configured, Authwall refuses to start.
The magic-code retry limit is not exposed as an environment variable and is tuned in config/settings.yaml under flows.magic_link: max_attempts (default 5) — the number of code guesses allowed per issued code before it is rejected.
Example:
AUTHWALL_MAGIC_LINK=code
Google OAuth
Configures sign-in with Google. All three variables are required together; the flow is enabled only when all of them are set.
AUTHWALL_GOOGLE_CLIENT_ID— OAuth 2.0 client identifier issued by Google.AUTHWALL_GOOGLE_CLIENT_SECRET— OAuth 2.0 client secret. Treat it as a secret: do not commit it or expose it to clients.AUTHWALL_GOOGLE_REDIRECT_URL— Callback URL Google redirects to after the user authorizes Authwall. It must match an Authorized Redirect URI registered on the OAuth client in Google Cloud Console; otherwise Google rejects the request. Authwall handles the callback at/auth/google/callback, so this is normally<AUTHWALL_PUBLIC_URL>/auth/google/callback.
If only some of the three are set, Authwall logs a warning and disables the Google flow.
If
AUTHWALL_FLOWS=googleis requested explicitly without all three, Authwall refuses to start.
Obtain values from Google Cloud Console → APIs & Services → Credentials.
Example:
AUTHWALL_GOOGLE_CLIENT_ID=1234567890-abc.apps.googleusercontent.com
AUTHWALL_GOOGLE_CLIENT_SECRET=GOCSPX-...
AUTHWALL_GOOGLE_REDIRECT_URL=https://myapp.test/auth/google/callback
GitHub OAuth
Configures sign-in with GitHub. All three variables are required together; the flow is enabled only when all of them are set.
AUTHWALL_GITHUB_CLIENT_ID— OAuth client identifier issued by GitHub.AUTHWALL_GITHUB_CLIENT_SECRET— OAuth client secret. Treat it as a secret: do not commit it or expose it to clients.AUTHWALL_GITHUB_REDIRECT_URL— Callback URL GitHub redirects to after the user authorizes Authwall. It must match the Authorization callback URL registered on the OAuth App in GitHub; otherwise GitHub rejects the request. Authwall handles the callback at/auth/github/callback, so this is normally<AUTHWALL_PUBLIC_URL>/auth/github/callback.
If only some of the three are set, Authwall logs a warning and disables the GitHub flow.
If
AUTHWALL_FLOWS=githubis requested explicitly without all three, Authwall refuses to start.
Obtain values from GitHub → Settings → Developer settings → OAuth Apps.
Example:
AUTHWALL_GITHUB_CLIENT_ID=Iv1.abcdef1234567890
AUTHWALL_GITHUB_CLIENT_SECRET=ghs_...
AUTHWALL_GITHUB_REDIRECT_URL=https://myapp.test/auth/github/callback
Facebook OAuth
Configures sign-in with Facebook. All three variables are required together; the flow is enabled only when all of them are set.
AUTHWALL_FACEBOOK_CLIENT_ID— App ID issued by Meta.AUTHWALL_FACEBOOK_CLIENT_SECRET— App Secret. Treat it as a secret: do not commit it or expose it to clients.AUTHWALL_FACEBOOK_REDIRECT_URL— Callback URL Facebook redirects to after the user authorizes Authwall. It must match a Valid OAuth Redirect URI configured on the app in Meta for Developers; otherwise Facebook rejects the request. Authwall handles the callback at/auth/facebook/callback, so this is normally<AUTHWALL_PUBLIC_URL>/auth/facebook/callback.
If only some of the three are set, Authwall logs a warning and disables the Facebook flow.
If
AUTHWALL_FLOWS=facebookis requested explicitly without all three, Authwall refuses to start.
Obtain values from Meta for Developers → My Apps → your app → Facebook Login → Settings.
Example:
AUTHWALL_FACEBOOK_CLIENT_ID=1234567890123456
AUTHWALL_FACEBOOK_CLIENT_SECRET=...
AUTHWALL_FACEBOOK_REDIRECT_URL=https://myapp.test/auth/facebook/callback
Microsoft OAuth
Configures sign-in with Microsoft. All three variables are required together; the flow is enabled only when all of them are set.
AUTHWALL_MICROSOFT_CLIENT_ID— Application (client) ID from the app registration.AUTHWALL_MICROSOFT_CLIENT_SECRET— Client secret value. Treat it as a secret: do not commit it or expose it to clients.AUTHWALL_MICROSOFT_REDIRECT_URL— Callback URL Microsoft redirects to after the user authorizes Authwall. It must match a Redirect URI registered on the app registration in Microsoft Entra; otherwise Microsoft rejects the request. Authwall handles the callback at/auth/microsoft/callback, so this is normally<AUTHWALL_PUBLIC_URL>/auth/microsoft/callback.
If only some of the three are set, Authwall logs a warning and disables the Microsoft flow.
If
AUTHWALL_FLOWS=microsoftis requested explicitly without all three, Authwall refuses to start.
Obtain values from Microsoft Entra admin center → Identity → Applications → App registrations.
Example:
AUTHWALL_MICROSOFT_CLIENT_ID=00000000-0000-0000-0000-000000000000
AUTHWALL_MICROSOFT_CLIENT_SECRET=...
AUTHWALL_MICROSOFT_REDIRECT_URL=https://myapp.test/auth/microsoft/callback
X OAuth
Configures sign-in with X (formerly Twitter). All three variables are required together; the flow is enabled only when all of them are set.
The variables keep their TWITTER names for compatibility, even though the product is now called X.
AUTHWALL_TWITTER_CLIENT_ID— OAuth 2.0 Client ID from the X app.AUTHWALL_TWITTER_CLIENT_SECRET— OAuth 2.0 Client Secret. Treat it as a secret: do not commit it or expose it to clients.AUTHWALL_TWITTER_REDIRECT_URL— Callback URL X redirects to after the user authorizes Authwall. It must match a Callback URI registered on the OAuth 2.0 client in the X Developer Portal; otherwise X rejects the request. Authwall handles the callback at/auth/twitter/callback, so this is normally<AUTHWALL_PUBLIC_URL>/auth/twitter/callback.
If only some of the three are set, Authwall logs a warning and disables the X flow.
If
AUTHWALL_FLOWS=twitteris requested explicitly without all three, Authwall refuses to start.
Obtain values from X Developer Portal → Projects & Apps → your app → User authentication settings.
Example:
AUTHWALL_TWITTER_CLIENT_ID=...
AUTHWALL_TWITTER_CLIENT_SECRET=...
AUTHWALL_TWITTER_REDIRECT_URL=https://myapp.test/auth/twitter/callback
Discord OAuth
Configures sign-in with Discord. All three variables are required together; the flow is enabled only when all of them are set.
AUTHWALL_DISCORD_CLIENT_ID— Application's Client ID.AUTHWALL_DISCORD_CLIENT_SECRET— Application's Client Secret. Treat it as a secret: do not commit it or expose it to clients.AUTHWALL_DISCORD_REDIRECT_URL— Callback URL Discord redirects to after the user authorizes Authwall. It must match a Redirect registered on the application in the Discord Developer Portal; otherwise Discord rejects the request. Authwall handles the callback at/auth/discord/callback, so this is normally<AUTHWALL_PUBLIC_URL>/auth/discord/callback.
If only some of the three are set, Authwall logs a warning and disables the Discord flow.
If
AUTHWALL_FLOWS=discordis requested explicitly without all three, Authwall refuses to start.
Obtain values from Discord Developer Portal → Applications → your application → OAuth2.
Example:
AUTHWALL_DISCORD_CLIENT_ID=1234567890123456789
AUTHWALL_DISCORD_CLIENT_SECRET=...
AUTHWALL_DISCORD_REDIRECT_URL=https://myapp.test/auth/discord/callback