Files
redefined-designs/docs/ops/google-sign-in.md
T
synAdminandClaude Opus 5 f5a29127fb
Linting / lint (pull_request) Successful in 3m44s
SonarQube Analysis / sonarqube (pull_request) Failing after 28m29s
docs(auth): correct the claim that QA could never run Google sign-in
It can, and it does. Registering the QA callback under Authorized redirect URIs was all it took.

The claim was that qa-redefined-designs.bermudalamb.synology.me could never be registered, because Google requires a redirect URI's host to sit under a domain whose ownership has been proved by DNS, and Synology owns the domain above that one. It was inferred from #285, where Cloudflare's free tier genuinely cannot be applied to that hostname, and asserted with far more confidence than the inference supported. What was actually established is narrower: localhost is exempt from the authorized-domain rules, and a domain listed as an authorized domain has to be verified in Search Console. Whether either applied here was never checked.

It was not a harmless error. On the strength of it, QA testing of this feature was documented as blocked behind #313, the QA compose file hardcoded its credentials to empty rather than reading the stack, #345 recorded it as a constraint, and #332 closed with it written into the summary. A QA deploy was spent on it.

So the correction is left in place rather than the wrong sentences quietly deleted. A document that silently stops saying something teaches nobody why it said it, and this is the second time in this feature that a confident inference about somebody else's platform has cost a day — the first being the assumption that a passing local build said anything about another machine.

What replaces it is the thing that was always true and never written down plainly: every environment sends a redirect URI derived from its own PUBLIC_URL, and each one has to exist verbatim in the console. There is now one table listing all four, including the localhost:5173 entry that local development needs and that Phase 0 originally omitted — the omission that cost an hour of redirect_uri_mismatch before any of this.

The QA compose comment now says which URL to register rather than why it cannot be. The ops document gains the steps QA actually took, in order, with a note on why registering before setting the variables is the order that matters: a button that appears before its callback exists fails at Google, where nothing in the storefront logs explains it.

Verified: backend tsc clean, the QA compose file still parses, and no file in the tree still claims the hostname is unusable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 14:48:46 -05:00

6.1 KiB

Google sign-in

What has to be true outside the repository for the Google button to work, and what to do at the domain cutover. The code side is #332 and the six issues under it; this is only the parts that live in a browser tab at Google.

Where it is configured

The Google Auth Platform in the Google Cloud Console, in one project. There is one consent screen per project and every OAuth client in it shares that screen, so what appears there is the production identity even while testing.

Section What it holds
Branding App name, support email, authorized domains, the three app links
Audience External, publishing status, test users
Clients The OAuth client, its redirect URIs, the id and secret
Data Access Exactly openid, email, profile
Verification Center Nothing to submit, and it should stay that way

Redirect URIs, one per environment

Every environment sends a redirect URI derived from its own PUBLIC_URL, and each one has to exist verbatim under Authorized redirect URIs on the client this app uses. Google compares them as strings and answers a mismatch with redirect_uri_mismatch, which is accurate and says nothing about which half is wrong.

Environment Redirect URI
Local, Vite dev server http://localhost:5173/api/auth/google/callback
Local, backend serving a build http://localhost:3000/api/auth/google/callback
QA https://qa-redefined-designs.bermudalamb.synology.me/api/auth/google/callback
Production https://redefined-designs.com/api/auth/google/callback

Local development needs the 5173 entry, because that is where the dev server serves the app. The 3000 one applies only when the backend serves a built frontend, which local development does not produce.

A correction

An earlier version of this document said the QA hostname could never be registered, because it sits under a domain Synology owns rather than one we do. That was wrong. Adding the URI works.

The claim is recorded here rather than quietly removed, because of what it cost. It was inferred from #285, where Cloudflare genuinely cannot be applied to that hostname, and asserted with far more confidence than the inference supported. On the strength of it, QA testing of Google sign-in was documented as blocked behind #313, the QA compose file hardcoded its credentials to empty, and two issues recorded it as fact.

What is true, and is all that was ever established: localhost is exempt from the authorized-domain rules, and a domain listed as an authorized domain has to be verified in Search Console. Whether either of those actually applied to this hostname, and how, was never checked.

Scopes, and why publishing needs no review

openid produces the id token carrying the subject claim, which is the identity stored. email carries the address and the email_verified flag the linking policy turns on. profile carries the names used when an account is created.

All three are non-sensitive. Requesting only them is what lets the app publish without verification and without customers seeing an unverified-app warning. Add one sensitive scope and publishing becomes a review with a video walkthrough and a wait measured in weeks. Nothing in this feature needs one.

Uploading an app logo also triggers a brand review, which is why Branding has none.

Turning it on in QA

Already done, and recorded here because the order matters.

  1. Register the QA callback under Clients, Authorized redirect URIs: https://qa-redefined-designs.bermudalamb.synology.me/api/auth/google/callback
  2. Set QA_GOOGLE_CLIENT_ID and QA_GOOGLE_CLIENT_SECRET on the QA stack. Both or neither — the backend refuses to start on one without the other, because the failure would otherwise arrive the moment a customer presses the button.
  3. Redeploy.

Registering first is the point. Setting the variables makes the button appear, and a button that appears before its callback exists fails at Google rather than in the storefront, where nothing in the logs explains it.

The cutover checklist, for #313

  1. Point QA at qa.redefined-designs.com and set its PUBLIC_URL to match.
  2. In Clients, add the new QA callback: https://qa.redefined-designs.com/api/auth/google/callback
  3. Confirm the production callback is registered: https://redefined-designs.com/api/auth/google/callback
  4. In Audience, move the publishing status from Testing to In production. Do it once the domain resolves, so the home page and privacy links Google shows actually answer.

The old QA callback can be left registered until the hostname is retired. An extra entry costs nothing and removing it early breaks QA for no gain.

No code changes at any step. The redirect URI is derived from PUBLIC_URL, so the environment variable and the console entry are the whole of it.

Leaving it in Testing is the failure to watch for. Only listed test users can sign in, and the refusal happens on Google's own page, so nothing reaches the storefront and nothing appears in its logs. A customer reports a broken button and the logs are silent.

The production smoke test

The consent screen, the redirect and the domain are all environment-specific, so QA proves the flow and not the configuration. After the cutover:

  1. Sign in with a Google account that has never been used on the site. A new customer is created and lands on the consent step.
  2. Sign in again with the same account. It reaches the same customer rather than a second one.
  3. Check the account page lists Google under connected accounts.

What is not offered, and why

Unlinking. A customer cannot detach their Google account. Removing the only way into an account is guarded for passkeys and the same guard would be needed here first. Worth its own issue when somebody actually asks.

Apple. A separate decision with a materially different cost, set out on #332: a paid developer programme, a client secret that expires every six months, no localhost redirect URIs at all, and a name and email returned exactly once. Apple is required for iOS apps offering third-party sign-in, and this is a website, so that rule does not apply here.