Skip to content

fix(auth): pass Auth.js variables to the container and fix redirect origins - #23

Merged
kunjhirapara merged 1 commit into
mainfrom
fix/authjs-runtime-config
Sep 13, 2026
Merged

kunjhirapara merged 1 commit into
mainfrom
fix/authjs-runtime-config

Conversation

@kunjhirapara

Copy link
Copy Markdown
Owner

Three faults, all found from one screenshot of a failed Google sign-in on production. Sign-in could not have worked with any of them present.

1. The container never received the Auth.js variables

docker-compose.yml passed no AUTH_* variable to the app service. Compose gives a container nothing that isn't named in its environment: block, so setting them in the VM's .env would have changed nothing — and would have looked like the values were wrong rather than absent.

This is the one that made the outage unfixable from the operator's side. All nine are now passed, with the AUTH_ADAPTER_SECRET note about it needing to match the Convex deployment.

2. Redirects went to https://0.0.0.0:3000

Auth.js builds redirect and error URLs from the request origin. The Dockerfile sets HOSTNAME=0.0.0.0, and the VM's nginx does not forward the Host header, so that origin resolved to the container's bind address. Every failed sign-in sent the browser to https://0.0.0.0:3000/signin?error=Configuration and died on ERR_ADDRESS_INVALID — so the one page that could have explained the failure was the one page the user could not reach.

The runner stage now defaults AUTH_URL from the NEXT_PUBLIC_APP_URL build arg. No new variable to set, no proxy cooperation required — the fix docs/HANDOFF.md already prefers over relying on proxy_set_header Host $host. Compose can still override it.

3. ?error= rendered as nothing

pages.error points at /signin, so a failed OAuth round trip comes back with a code and no other signal — and the page looked exactly like a fresh visit. Indistinguishable from the button being broken.

Codes now map to sentences. Two matter:

  • Configuration says the fault is ours and marks itself non-retryable, because telling someone to try again against a misconfigured server is a loop that cannot succeed. The provider buttons and submit are disabled for it.
  • AccessDenied is what our own signIn callback returns when it refuses to link an OAuth identity to an existing account. It explains the way forward rather than reading as a generic failure.

Unknown codes fall back rather than printing a raw code at a user.

Verification

  • 263/263 tests pass (13 new), tsc --noEmit clean, production build green.
  • docker-compose.yml and the workflow YAML both parse.

Deploying this

Still needs the values themselves set in the VM's .env — this PR makes them reach the app, it does not invent them. AUTH_ADAPTER_SECRET must additionally be set on Convex with npx convex env set AUTH_ADAPTER_SECRET <same value> --prod, and the OAuth redirect URIs registered: /api/auth/callback/google, /api/auth/callback/github.

After this image rolls out, /.well-known/jwks.json returning a key instead of 503 and /api/health reporting "auth": true are the two quickest confirmations.

…ct origins

Three faults, found from one screenshot of a failed Google sign-in on
production. Sign-in could not have worked with any of them present.

docker-compose.yml never passed a single AUTH_* variable to the app service.
Compose gives a container nothing that is not named in its environment block, so
setting them in the VM's .env would have changed nothing and looked like the
values were wrong rather than absent. This is the one that made the outage
unfixable from the operator's side.

Auth.js built its redirect and error URLs from the request origin, which behind
the VM's nginx resolves to the container's bind address -- the Dockerfile sets
HOSTNAME=0.0.0.0 and that proxy does not forward Host. Every failed sign-in sent
the browser to https://0.0.0.0:3000/signin?error=... and died on
ERR_ADDRESS_INVALID, so the one page that could have explained the failure was
the one page unreachable. The runner stage now defaults AUTH_URL from the
NEXT_PUBLIC_APP_URL build arg, which needs no new variable and no proxy
cooperation -- the fix docs/HANDOFF.md already prefers over relying on
`proxy_set_header Host $host`. Compose can still override it.

The sign-in page rendered ?error= as nothing at all. pages.error points here, so
a failed OAuth round trip returns with a code and no other signal, and the page
looked exactly like a fresh visit -- indistinguishable from the button being
broken. Codes are now mapped to sentences, with two that matter: Configuration
says the fault is ours and is not retryable, because telling someone to try
again against a misconfigured server is a loop that cannot succeed; and
AccessDenied, which is what our own signIn callback returns when it refuses to
link an OAuth identity to an existing account, explains the way forward instead
of reading as a generic failure. Unknown codes fall back rather than printing
the raw code at a user.

The provider buttons and the submit are disabled when the error is one retrying
cannot clear.
Copilot AI lite review requested due to automatic review settings September 13, 2026 15:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@kunjhirapara
kunjhirapara merged commit 7227ab3 into main Sep 13, 2026
1 check passed
@kunjhirapara
kunjhirapara deleted the fix/authjs-runtime-config branch September 13, 2026 15:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants